From 773055b142b3651dac450d0c27864b720446fcd4 Mon Sep 17 00:00:00 2001 From: daikoku-github-actions Date: Mon, 5 Aug 2024 13:28:32 +0000 Subject: [PATCH] Format sources before release --- .../app/controllers/AdminApiController.scala | 14 +- daikoku/app/controllers/ApiController.scala | 20 ++- daikoku/app/controllers/HomeController.scala | 34 +++- daikoku/app/controllers/LoginController.scala | 70 ++++++-- .../OtoroshiSettingsController.scala | 38 +++-- daikoku/app/controllers/TeamController.scala | 7 +- daikoku/app/controllers/UsersController.scala | 13 +- daikoku/app/domain/SchemaDefinition.scala | 12 +- daikoku/app/domain/json.scala | 82 ++++----- daikoku/app/domain/tenantEntities.scala | 41 ++++- daikoku/app/env/env.scala | 30 ++-- daikoku/app/jobs/OtoroshiVerifierJob.scala | 105 ++++++------ daikoku/app/jobs/QueueJob.scala | 64 +++++--- daikoku/app/login/api.scala | 3 +- daikoku/app/login/oauth.scala | 7 +- daikoku/app/utils/ApiService.scala | 114 ++++++++----- daikoku/app/utils/DeletionService.scala | 13 +- daikoku/app/utils/errors.scala | 8 +- daikoku/app/utils/otoroshi.scala | 14 +- daikoku/app/utils/request.scala | 3 +- .../src/components/utils/tenantUtils.ts | 20 ++- daikoku/javascript/src/services/index.ts | 10 +- daikoku/javascript/src/types/api.ts | 4 +- daikoku/javascript/src/types/context.ts | 2 +- daikoku/javascript/src/types/tenant.ts | 1 - .../test/daikoku/AdminApiControllerSpec.scala | 6 +- daikoku/test/daikoku/ApiControllerSpec.scala | 155 ++++++++++-------- daikoku/test/daikoku/suites.scala | 7 +- 28 files changed, 581 insertions(+), 316 deletions(-) diff --git a/daikoku/app/controllers/AdminApiController.scala b/daikoku/app/controllers/AdminApiController.scala index c197934ad..2abd49911 100644 --- a/daikoku/app/controllers/AdminApiController.scala +++ b/daikoku/app/controllers/AdminApiController.scala @@ -526,11 +526,15 @@ class ApiAdminApiController( .findOne( Json.obj( "_id" -> Json.obj("$ne" -> entity.id.asJson), - "name" -> entity.name, - ) ++ entity.parent.map(p => Json.obj("_id" -> p.asJson)).getOrElse(Json.obj()) + "name" -> entity.name + ) ++ entity.parent + .map(p => Json.obj("_id" -> p.asJson)) + .getOrElse(Json.obj()) ) .map { - case Some(api) if entity.parent.contains(api.id) || api.parent.contains(entity.id) => + case Some(api) + if entity.parent.contains(api.id) || api.parent + .contains(entity.id) => Right(()) case Some(_) => Left(AppError.ParsingPayloadError("Api name already exists")) @@ -545,7 +549,9 @@ class ApiAdminApiController( Json.obj( "_id" -> Json.obj("$ne" -> entity.id.asJson), "name" -> entity.name - ) ++ entity.parent.map(p => Json.obj("_id" -> p.asJson)).getOrElse(Json.obj()) + ) ++ entity.parent + .map(p => Json.obj("_id" -> p.asJson)) + .getOrElse(Json.obj()) ) .map { case None => diff --git a/daikoku/app/controllers/ApiController.scala b/daikoku/app/controllers/ApiController.scala index 01d7587f1..1f3a9b12c 100644 --- a/daikoku/app/controllers/ApiController.scala +++ b/daikoku/app/controllers/ApiController.scala @@ -10,11 +10,19 @@ import cats.data.EitherT import cats.implicits.{catsSyntaxOptionId, toTraverseOps} import controllers.AppError import controllers.AppError._ -import fr.maif.otoroshi.daikoku.actions.{DaikokuAction, DaikokuActionContext, DaikokuActionMaybeWithGuest, DaikokuActionMaybeWithoutUser} +import fr.maif.otoroshi.daikoku.actions.{ + DaikokuAction, + DaikokuActionContext, + DaikokuActionMaybeWithGuest, + DaikokuActionMaybeWithoutUser +} import fr.maif.otoroshi.daikoku.audit.AuditTrailEvent import fr.maif.otoroshi.daikoku.audit.config.ElasticAnalyticsConfig import fr.maif.otoroshi.daikoku.ctrls.authorizations.async._ -import fr.maif.otoroshi.daikoku.domain.NotificationAction.{ApiAccess, ApiSubscriptionDemand} +import fr.maif.otoroshi.daikoku.domain.NotificationAction.{ + ApiAccess, + ApiSubscriptionDemand +} import fr.maif.otoroshi.daikoku.domain.UsagePlanVisibility.Private import fr.maif.otoroshi.daikoku.domain._ import fr.maif.otoroshi.daikoku.domain.json._ @@ -85,7 +93,9 @@ class ApiController( def fetchSwagger(api: Api): EitherT[Future, AppError, Result] = { api.swagger match { case Some(SwaggerAccess(_, Some(content), _, _, _)) => - val contentType = if(content.startsWith("{")) "application/json" else "application/yaml" + val contentType = + if (content.startsWith("{")) "application/json" + else "application/yaml" EitherT.pure[Future, AppError](Ok(content).as(contentType)) case Some(SwaggerAccess(Some(url), None, headers, _, _)) => val finalUrl = @@ -97,7 +107,9 @@ class ApiController( .withHttpHeaders(headers.toSeq: _*) .get() .map { resp => - val contentType = if(resp.body.startsWith("{")) "application/json" else "application/yaml" + val contentType = + if (resp.body.startsWith("{")) "application/json" + else "application/yaml" Right( Ok(resp.body).as( resp diff --git a/daikoku/app/controllers/HomeController.scala b/daikoku/app/controllers/HomeController.scala index 66d6bfe69..ae41609ab 100644 --- a/daikoku/app/controllers/HomeController.scala +++ b/daikoku/app/controllers/HomeController.scala @@ -4,12 +4,24 @@ import com.github.blemale.scaffeine.{Cache, Scaffeine} import com.nimbusds.jose.util.StandardCharset import controllers.Assets import daikoku.BuildInfo -import fr.maif.otoroshi.daikoku.actions.{DaikokuAction, DaikokuActionMaybeWithGuest, DaikokuActionMaybeWithoutUser, DaikokuActionMaybeWithoutUserContext} +import fr.maif.otoroshi.daikoku.actions.{ + DaikokuAction, + DaikokuActionMaybeWithGuest, + DaikokuActionMaybeWithoutUser, + DaikokuActionMaybeWithoutUserContext +} import fr.maif.otoroshi.daikoku.audit.AuditTrailEvent -import fr.maif.otoroshi.daikoku.ctrls.authorizations.async.{DaikokuAdminOrSelf, TenantAdminOnly} +import fr.maif.otoroshi.daikoku.ctrls.authorizations.async.{ + DaikokuAdminOrSelf, + TenantAdminOnly +} import fr.maif.otoroshi.daikoku.ctrls.authorizations.sync.TeamMemberOnly import fr.maif.otoroshi.daikoku.domain._ -import fr.maif.otoroshi.daikoku.domain.json.{CmsFileFormat, CmsPageFormat, CmsRequestRenderingFormat} +import fr.maif.otoroshi.daikoku.domain.json.{ + CmsFileFormat, + CmsPageFormat, + CmsRequestRenderingFormat +} import fr.maif.otoroshi.daikoku.env.Env import fr.maif.otoroshi.daikoku.logger.AppLogger import fr.maif.otoroshi.daikoku.utils.{Errors, IdGenerator, diff_match_patch} @@ -113,12 +125,20 @@ class HomeController( def getConnectedUser() = { DaikokuActionMaybeWithoutUser { ctx => - Ok( Json.obj( - "connectedUser" -> ctx.user.map(_.toUiPayload()).getOrElse(JsNull).as[JsValue], - "impersonator" -> ctx.session.map(_.impersonatorJson()).getOrElse(JsNull).as[JsValue], - "session" -> ctx.session.map(_.asSimpleJson).getOrElse(JsNull).as[JsValue], + "connectedUser" -> ctx.user + .map(_.toUiPayload()) + .getOrElse(JsNull) + .as[JsValue], + "impersonator" -> ctx.session + .map(_.impersonatorJson()) + .getOrElse(JsNull) + .as[JsValue], + "session" -> ctx.session + .map(_.asSimpleJson) + .getOrElse(JsNull) + .as[JsValue], "tenant" -> ctx.tenant.toUiPayload(env), "isTenantAdmin" -> ctx.isTenantAdmin, "apiCreationPermitted" -> ctx.apiCreationPermitted, diff --git a/daikoku/app/controllers/LoginController.scala b/daikoku/app/controllers/LoginController.scala index f25b5a4cb..f4314f644 100644 --- a/daikoku/app/controllers/LoginController.scala +++ b/daikoku/app/controllers/LoginController.scala @@ -2,7 +2,12 @@ package fr.maif.otoroshi.daikoku.ctrls import com.eatthepath.otp.TimeBasedOneTimePasswordGenerator import controllers.Assets -import fr.maif.otoroshi.daikoku.actions.{DaikokuAction, DaikokuActionMaybeWithoutUser, DaikokuTenantAction, DaikokuTenantActionContext} +import fr.maif.otoroshi.daikoku.actions.{ + DaikokuAction, + DaikokuActionMaybeWithoutUser, + DaikokuTenantAction, + DaikokuTenantActionContext +} import fr.maif.otoroshi.daikoku.audit.{AuditTrailEvent, AuthorizationLevel} import fr.maif.otoroshi.daikoku.domain.TeamPermission.Administrator import fr.maif.otoroshi.daikoku.domain._ @@ -97,12 +102,13 @@ class LoginController( DaikokuActionMaybeWithoutUser { _ => Ok( Json.obj( - "action" -> fr.maif.otoroshi.daikoku.ctrls.routes.LoginController.login(provider).url + "action" -> fr.maif.otoroshi.daikoku.ctrls.routes.LoginController + .login(provider) + .url ) ) } - def loginPage(provider: String) = DaikokuTenantAction.async { ctx => AuthProvider(provider) match { @@ -146,7 +152,12 @@ class LoginController( s"redirect" -> redirect.getOrElse("/") ) ) - case _ if env.config.isDev => FastFuture.successful(Redirect(env.getDaikokuUrl(ctx.tenant, s"/auth/${p.name}/login"))) + case _ if env.config.isDev => + FastFuture.successful( + Redirect( + env.getDaikokuUrl(ctx.tenant, s"/auth/${p.name}/login") + ) + ) case _ => assets.at("index.html").apply(ctx.request) } } @@ -547,7 +558,10 @@ class LoginController( ctx.tenant, Map( "tenant" -> ctx.tenant.name, - "link" -> env.getDaikokuUrl(ctx.tenant, s"/account/validate?id=$randomId") + "link" -> env.getDaikokuUrl( + ctx.tenant, + s"/account/validate?id=$randomId" + ) ) ) } yield { @@ -644,8 +658,12 @@ class LoginController( } yield () userCreation.map { _ => Status(302)( - Json.obj("Location" -> "/?message=user.validated.success") - ).withHeaders("Location" -> "/?message=user.validated.success") + Json.obj( + "Location" -> "/?message=user.validated.success" + ) + ).withHeaders( + "Location" -> "/?message=user.validated.success" + ) } } case _ => @@ -701,8 +719,9 @@ class LoginController( ctx.tenant.defaultLanguage.getOrElse("en") implicit val language: String = user.defaultLanguage.getOrElse(tenantLanguage) - val link = env.getDaikokuUrl(ctx.tenant, s"/account/reset?id=$randomId") - ctx.tenant.mailer + val link = + env.getDaikokuUrl(ctx.tenant, s"/account/reset?id=$randomId") + ctx.tenant.mailer .send( s"Reset your ${ctx.tenant.name} account password", Seq(email), @@ -735,7 +754,10 @@ class LoginController( ctx.request.getQueryString("id") match { case None => FastFuture.successful( - Redirect(env.getDaikokuUrl(ctx.tenant, "/reset?error=bad.creation.id"))) + Redirect( + env.getDaikokuUrl(ctx.tenant, "/reset?error=bad.creation.id") + ) + ) case Some(id) => { env.dataStore.passwordResetRepo .findOneNotDeleted(Json.obj("randomId" -> id)) @@ -745,7 +767,12 @@ class LoginController( env.dataStore.passwordResetRepo .deleteByIdLogically(pwdReset.id.value) .map { _ => - Redirect(env.getDaikokuUrl(ctx.tenant, "/reset?error=not.valid.anymore")) + Redirect( + env.getDaikokuUrl( + ctx.tenant, + "/reset?error=not.valid.anymore" + ) + ) } case Some(pwdReset) if pwdReset.validUntil.isAfter(DateTime.now()) => @@ -759,7 +786,13 @@ class LoginController( .flatMap { case None => FastFuture.successful( - Redirect(env.getDaikokuUrl(ctx.tenant, "/reset?error=user.not.found"))) + Redirect( + env.getDaikokuUrl( + ctx.tenant, + "/reset?error=user.not.found" + ) + ) + ) case Some(user) => env.dataStore.userRepo .save(user.copy(password = Some(pwdReset.password))) @@ -767,13 +800,22 @@ class LoginController( env.dataStore.passwordResetRepo .deleteByIdLogically(pwdReset.id.value) .map { _ => - Redirect(env.getDaikokuUrl(ctx.tenant, "/?message=password.reset.successfull")) + Redirect( + env.getDaikokuUrl( + ctx.tenant, + "/?message=password.reset.successfull" + ) + ) } } } case _ => FastFuture.successful( - Redirect(env.getDaikokuUrl(ctx.tenant, "/reset?error=bad.creation.id"))) + Redirect( + env + .getDaikokuUrl(ctx.tenant, "/reset?error=bad.creation.id") + ) + ) } } } diff --git a/daikoku/app/controllers/OtoroshiSettingsController.scala b/daikoku/app/controllers/OtoroshiSettingsController.scala index ddcf0daaa..e2f3da746 100644 --- a/daikoku/app/controllers/OtoroshiSettingsController.scala +++ b/daikoku/app/controllers/OtoroshiSettingsController.scala @@ -584,7 +584,8 @@ class OtoroshiSettingsController( if (previousSettings != actualSettings) { for { _ <- otoroshiClient.deleteApiKey(key.clientId)(previousSettings) - newKey <-EitherT(otoroshiClient.createApiKey(key)(actualSettings)) + newKey <- + EitherT(otoroshiClient.createApiKey(key)(actualSettings)) } yield newKey } else { EitherT(otoroshiClient.updateApiKey(key)(previousSettings)) @@ -661,16 +662,25 @@ class OtoroshiSettingsController( val otoroshiSettingsOpt = (ctx.request.body \ "otoroshiSettings").asOpt[String] - val clientIdOpt = (ctx.request.body \ "clientId").asOpt[String] (for { - otoroshiSettingsId <- EitherT.fromOption[Future](otoroshiSettingsOpt, AppError.EntityNotFound("Otoroshi settings")) - clientId <- EitherT.fromOption[Future](clientIdOpt, AppError.EntityNotFound("clientId settings")) - otoroshiSettings <- EitherT.fromOption[Future](ctx.tenant.otoroshiSettings - .find(s => s.id.value == otoroshiSettingsId), AppError.EntityNotFound("Otoroshi settings")) - _ <- otoroshiClient - .deleteApiKey(clientId)(otoroshiSettings) + otoroshiSettingsId <- EitherT.fromOption[Future]( + otoroshiSettingsOpt, + AppError.EntityNotFound("Otoroshi settings") + ) + clientId <- EitherT.fromOption[Future]( + clientIdOpt, + AppError.EntityNotFound("clientId settings") + ) + otoroshiSettings <- EitherT.fromOption[Future]( + ctx.tenant.otoroshiSettings + .find(s => s.id.value == otoroshiSettingsId), + AppError.EntityNotFound("Otoroshi settings") + ) + _ <- + otoroshiClient + .deleteApiKey(clientId)(otoroshiSettings) } yield Ok(Json.obj("done" -> true))) .leftMap(_.render()) .merge @@ -713,7 +723,8 @@ class OtoroshiSettingsController( .find(_._2 == s"fake-${api.id.value}") val headerOpt = headers.find(_._2 == s"fake-${api.id.value}") val finalUrl = api.testing match { - case Some(Testing(_, auth, _, username, _, _)) if queryOpt.isDefined && auth.name == TestingAuth.ApiKey.name => + case Some(Testing(_, auth, _, username, _, _)) + if queryOpt.isDefined && auth.name == TestingAuth.ApiKey.name => url .replace( s"&${queryOpt.get._1}=fake-${api.id.value}", @@ -727,9 +738,12 @@ class OtoroshiSettingsController( } val finalHeaders: Map[String, String] = api.testing match { - case Some(Testing(_, auth, _, username, _, _)) if auth.name == TestingAuth.ApiKey.name && headerOpt.isDefined => - headers - headerOpt.get._1 + (headerOpt.get._1 -> username.getOrElse("")) - case Some(Testing(_, auth, _, username, password, _)) if auth.name == TestingAuth.Basic.name => + case Some(Testing(_, auth, _, username, _, _)) + if auth.name == TestingAuth.ApiKey.name && headerOpt.isDefined => + headers - headerOpt.get._1 + (headerOpt.get._1 -> username + .getOrElse("")) + case Some(Testing(_, auth, _, username, password, _)) + if auth.name == TestingAuth.Basic.name => headers - "Authorization" + ("Authorization" -> s"Basic ${Base64 .encodeBase64String(s"${username.getOrElse("")}:$password".getBytes(Charsets.UTF_8))}") case _ => headers diff --git a/daikoku/app/controllers/TeamController.scala b/daikoku/app/controllers/TeamController.scala index 285c4636a..080d652a3 100644 --- a/daikoku/app/controllers/TeamController.scala +++ b/daikoku/app/controllers/TeamController.scala @@ -848,8 +848,11 @@ class TeamController( implicit val language: String = tenant.defaultLanguage.getOrElse("en") - val link = env.getDaikokuUrl(ctx.tenant, s"/join?token=${invitedUser.invitation.get.token}") - tenant.mailer + val link = env.getDaikokuUrl( + ctx.tenant, + s"/join?token=${invitedUser.invitation.get.token}" + ) + tenant.mailer .send( s"Join ${team.name}", Seq(email), diff --git a/daikoku/app/controllers/UsersController.scala b/daikoku/app/controllers/UsersController.scala index 677fc2c07..d0b60a991 100644 --- a/daikoku/app/controllers/UsersController.scala +++ b/daikoku/app/controllers/UsersController.scala @@ -4,7 +4,11 @@ import cats.data.EitherT import org.apache.pekko.http.scaladsl.util.FastFuture import com.eatthepath.otp.TimeBasedOneTimePasswordGenerator import controllers.AppError -import fr.maif.otoroshi.daikoku.actions.{DaikokuAction, DaikokuActionMaybeWithGuest, DaikokuActionMaybeWithoutUser} +import fr.maif.otoroshi.daikoku.actions.{ + DaikokuAction, + DaikokuActionMaybeWithGuest, + DaikokuActionMaybeWithoutUser +} import fr.maif.otoroshi.daikoku.audit.AuditTrailEvent import fr.maif.otoroshi.daikoku.ctrls.authorizations.async._ import fr.maif.otoroshi.daikoku.domain.TeamPermission.Administrator @@ -17,7 +21,12 @@ import org.apache.commons.codec.binary.Base32 import org.joda.time.{DateTime, Hours} import org.mindrot.jbcrypt.BCrypt import play.api.libs.json.{JsArray, JsError, JsSuccess, Json} -import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents} +import play.api.mvc.{ + AbstractController, + Action, + AnyContent, + ControllerComponents +} import java.time.Instant import java.util.Base64 diff --git a/daikoku/app/domain/SchemaDefinition.scala b/daikoku/app/domain/SchemaDefinition.scala index 49630ab84..c23495066 100644 --- a/daikoku/app/domain/SchemaDefinition.scala +++ b/daikoku/app/domain/SchemaDefinition.scala @@ -1712,7 +1712,11 @@ object SchemaDefinition { ), ReplaceField( "specificationType", - Field("specificationType", StringType, resolve = _.value.specificationType.name) + Field( + "specificationType", + StringType, + resolve = _.value.specificationType.name + ) ) ) lazy val ApiDocumentationPageType = deriveObjectType[ @@ -2326,7 +2330,11 @@ object SchemaDefinition { ), Field("isDefault", BooleanType, resolve = _.value.isDefault), Field("lastUpdate", DateTimeUnitype, resolve = _.value.lastUpdate), - Field("testing", OptionType(TestingType), resolve = _.value.testing), + Field( + "testing", + OptionType(TestingType), + resolve = _.value.testing + ), Field( "documentation", ApiDocumentationType, diff --git a/daikoku/app/domain/json.scala b/daikoku/app/domain/json.scala index 55c0a9726..98afebe58 100644 --- a/daikoku/app/domain/json.scala +++ b/daikoku/app/domain/json.scala @@ -191,7 +191,7 @@ object json { override def reads(json: JsValue): JsResult[SpecificationType] = json.as[String] match { case "openapi" => JsSuccess(SpecificationType.OpenApi) - case "asyncapi" => JsSuccess(SpecificationType.AsyncApi) + case "asyncapi" => JsSuccess(SpecificationType.AsyncApi) case str => JsError(s"Bad specification type value: $str") } @@ -201,26 +201,28 @@ object json { val TestingFormat = new Format[Testing] { override def reads(json: JsValue): JsResult[Testing] = { json match { - case JsObject(_) => Try { - JsSuccess( - Testing( - enabled = (json \ "enabled").asOpt[Boolean].getOrElse(false), - auth = (json \ "auth").asOpt[String].filter(_.trim.nonEmpty) match { - case Some("ApiKey") => TestingAuth.ApiKey - case Some("Basic") => TestingAuth.Basic - case _ => TestingAuth.Basic - }, - name = (json \ "name").asOpt[String].filter(_.trim.nonEmpty), - username = - (json \ "username").asOpt[String].filter(_.trim.nonEmpty), - password = - (json \ "password").asOpt[String].filter(_.trim.nonEmpty), - config = (json \ "config").asOpt(TestingConfigFormat) + case JsObject(_) => + Try { + JsSuccess( + Testing( + enabled = (json \ "enabled").asOpt[Boolean].getOrElse(false), + auth = + (json \ "auth").asOpt[String].filter(_.trim.nonEmpty) match { + case Some("ApiKey") => TestingAuth.ApiKey + case Some("Basic") => TestingAuth.Basic + case _ => TestingAuth.Basic + }, + name = (json \ "name").asOpt[String].filter(_.trim.nonEmpty), + username = + (json \ "username").asOpt[String].filter(_.trim.nonEmpty), + password = + (json \ "password").asOpt[String].filter(_.trim.nonEmpty), + config = (json \ "config").asOpt(TestingConfigFormat) + ) ) - ) - } recover { - case e => JsError(e.getMessage) - } get + } recover { + case e => JsError(e.getMessage) + } get case _ => JsError() } @@ -1810,25 +1812,26 @@ object json { val SwaggerAccessFormat = new Format[SwaggerAccess] { override def reads(json: JsValue): JsResult[SwaggerAccess] = { json match { - case JsObject(_) => Try { - JsSuccess( - SwaggerAccess( - url = (json \ "url").asOpt[String], - content = (json \ "content").asOpt[String], - headers = (json \ "headers") - .asOpt[Map[String, String]] - .getOrElse(Map.empty[String, String]), - additionalConf = (json \ "additionalConf").asOpt[JsObject], - specificationType = (json \ "specificationType") - .asOpt(SpecificationTypeFormat) - .getOrElse(SpecificationType.OpenApi) + case JsObject(_) => + Try { + JsSuccess( + SwaggerAccess( + url = (json \ "url").asOpt[String], + content = (json \ "content").asOpt[String], + headers = (json \ "headers") + .asOpt[Map[String, String]] + .getOrElse(Map.empty[String, String]), + additionalConf = (json \ "additionalConf").asOpt[JsObject], + specificationType = (json \ "specificationType") + .asOpt(SpecificationTypeFormat) + .getOrElse(SpecificationType.OpenApi) + ) ) - ) - } recover { - case e => - AppLogger.error(e.getMessage, e) - JsError(e.getMessage) - } get + } recover { + case e => + AppLogger.error(e.getMessage, e) + JsError(e.getMessage) + } get case _ => JsError() } @@ -2564,8 +2567,7 @@ object json { .asOpt(SeqVersionFormat) .map(_.toSet) .getOrElse(Set.empty), - testing = - (json \ "testing").asOpt(TestingFormat), + testing = (json \ "testing").asOpt(TestingFormat), documentation = (json \ "documentation") .as(ApiDocumentationFormat), swagger = (json \ "swagger").asOpt(SwaggerAccessFormat), diff --git a/daikoku/app/domain/tenantEntities.scala b/daikoku/app/domain/tenantEntities.scala index 6014482b2..27c8376f1 100644 --- a/daikoku/app/domain/tenantEntities.scala +++ b/daikoku/app/domain/tenantEntities.scala @@ -5,10 +5,16 @@ import com.github.jknack.handlebars.{Context, Handlebars, Options} import controllers.AppError.toJson import controllers.{AppError, Assets} import domain.JsonNodeValueResolver -import fr.maif.otoroshi.daikoku.actions.{DaikokuActionContext, DaikokuActionMaybeWithoutUserContext} +import fr.maif.otoroshi.daikoku.actions.{ + DaikokuActionContext, + DaikokuActionMaybeWithoutUserContext +} import fr.maif.otoroshi.daikoku.audit.config.{ElasticAnalyticsConfig, Webhook} import fr.maif.otoroshi.daikoku.audit.{AuditTrailEvent, KafkaConfig} -import fr.maif.otoroshi.daikoku.ctrls.authorizations.async.{_TeamMemberOnly, _UberPublicUserAccess} +import fr.maif.otoroshi.daikoku.ctrls.authorizations.async.{ + _TeamMemberOnly, + _UberPublicUserAccess +} import fr.maif.otoroshi.daikoku.env.Env import fr.maif.otoroshi.daikoku.login.AuthProvider import fr.maif.otoroshi.daikoku.utils.StringImplicits.BetterString @@ -453,14 +459,33 @@ case class Tenant( "environments" -> JsArray(environments.map(JsString.apply).toSeq), "loginProvider" -> authProvider.name, "cmsRedirections" -> JsArray(cmsRedirections.map(JsString.apply).toSeq), - "colorTheme" -> style.map(_.colorTheme).map(JsString.apply).getOrElse(JsNull).as[JsValue], - "css" -> style.map(_.css).map(JsString.apply).getOrElse(JsNull).as[JsValue], - "cssUrl" -> style.flatMap(_.cssUrl).map(JsString.apply).getOrElse(JsNull).as[JsValue], - "jsUrl" -> style.flatMap(_.jsUrl).map(JsString.apply).getOrElse(JsNull).as[JsValue], + "colorTheme" -> style + .map(_.colorTheme) + .map(JsString.apply) + .getOrElse(JsNull) + .as[JsValue], + "css" -> style + .map(_.css) + .map(JsString.apply) + .getOrElse(JsNull) + .as[JsValue], + "cssUrl" -> style + .flatMap(_.cssUrl) + .map(JsString.apply) + .getOrElse(JsNull) + .as[JsValue], + "jsUrl" -> style + .flatMap(_.jsUrl) + .map(JsString.apply) + .getOrElse(JsNull) + .as[JsValue], "js" -> style.map(_.js).map(JsString.apply).getOrElse(JsNull).as[JsValue], "faviconUrl" -> favicon(), - "fontFamilyUrl" -> style.flatMap(_.fontFamilyUrl).map(JsString.apply).getOrElse(JsNull).as[JsValue], - + "fontFamilyUrl" -> style + .flatMap(_.fontFamilyUrl) + .map(JsString.apply) + .getOrElse(JsNull) + .as[JsValue] ) } def favicon(): String = { diff --git a/daikoku/app/env/env.scala b/daikoku/app/env/env.scala index 96c82d0bc..c7d3635cd 100644 --- a/daikoku/app/env/env.scala +++ b/daikoku/app/env/env.scala @@ -10,7 +10,12 @@ import com.auth0.jwt.{JWT, JWTVerifier} import fr.maif.otoroshi.daikoku.audit.AuditActorSupervizer import fr.maif.otoroshi.daikoku.domain.TeamPermission.Administrator import fr.maif.otoroshi.daikoku.domain.UsagePlan.FreeWithoutQuotas -import fr.maif.otoroshi.daikoku.domain.{DatastoreId, ReportsInfo, TeamApiKeyVisibility, Tenant} +import fr.maif.otoroshi.daikoku.domain.{ + DatastoreId, + ReportsInfo, + TeamApiKeyVisibility, + Tenant +} import fr.maif.otoroshi.daikoku.logger.AppLogger import fr.maif.otoroshi.daikoku.login.LoginFilter import fr.maif.otoroshi.daikoku.utils._ @@ -526,13 +531,17 @@ class DaikokuEnv( defaultLanguage = None ) val initialDataFu = for { - _ <- Future.sequence(evolutions.list.map(e => dataStore.evolutionRepo.save( - Evolution( - id = DatastoreId(IdGenerator.token(32)), - version = e.version, - applied = true + _ <- Future.sequence( + evolutions.list.map(e => + dataStore.evolutionRepo.save( + Evolution( + id = DatastoreId(IdGenerator.token(32)), + version = e.version, + applied = true + ) + ) ) - ))) + ) _ <- dataStore.tenantRepo.save(tenant) _ <- dataStore.teamRepo.forTenant(tenant.id).save(team) _ <- @@ -543,9 +552,10 @@ class DaikokuEnv( dataStore.apiRepo .forTenant(tenant.id) .save(adminApiDefaultTenant) - _ <- dataStore.usagePlanRepo - .forTenant(tenant.id) - .save(adminApiDefaultPlan) + _ <- + dataStore.usagePlanRepo + .forTenant(tenant.id) + .save(adminApiDefaultPlan) _ <- dataStore.userRepo.save(user) } yield () diff --git a/daikoku/app/jobs/OtoroshiVerifierJob.scala b/daikoku/app/jobs/OtoroshiVerifierJob.scala index 563c2a851..21c982ece 100644 --- a/daikoku/app/jobs/OtoroshiVerifierJob.scala +++ b/daikoku/app/jobs/OtoroshiVerifierJob.scala @@ -7,7 +7,10 @@ import cats.data.EitherT import cats.syntax.option._ import controllers.AppError import fr.maif.otoroshi.daikoku.audit.{ApiKeyRotationEvent, JobEvent} -import fr.maif.otoroshi.daikoku.domain.NotificationAction.{OtoroshiSyncApiError, OtoroshiSyncSubscriptionError} +import fr.maif.otoroshi.daikoku.domain.NotificationAction.{ + OtoroshiSyncApiError, + OtoroshiSyncSubscriptionError +} import fr.maif.otoroshi.daikoku.domain._ import fr.maif.otoroshi.daikoku.domain.json.ApiSubscriptionyRotationFormat import fr.maif.otoroshi.daikoku.env.Env @@ -93,9 +96,9 @@ class OtoroshiVerifierJob( ) def getListFromMeta( - key: String, - metadata: Map[String, String] - ): Set[String] = { + key: String, + metadata: Map[String, String] + ): Set[String] = { metadata .get(key) .map(_.split('|').toSeq.map(_.trim).toSet) @@ -103,35 +106,35 @@ class OtoroshiVerifierJob( } def mergeMetaValue( - key: String, - meta1: Map[String, String], - meta2: Map[String, String] - ): String = { + key: String, + meta1: Map[String, String], + meta2: Map[String, String] + ): String = { val list1 = getListFromMeta(key, meta1) val list2 = getListFromMeta(key, meta2) (list1 ++ list2).mkString(" | ") } case class SyncInformation( - parent: ApiSubscription, - childs: Seq[ApiSubscription], - team: Team, - parentApi: Api, - apk: ActualOtoroshiApiKey, - otoroshiSettings: OtoroshiSettings, - tenant: Tenant, - tenantAdminTeam: Team - ) + parent: ApiSubscription, + childs: Seq[ApiSubscription], + team: Team, + parentApi: Api, + apk: ActualOtoroshiApiKey, + otoroshiSettings: OtoroshiSettings, + tenant: Tenant, + tenantAdminTeam: Team + ) case class ComputedInformation( - parent: ApiSubscription, - childs: Seq[ApiSubscription], - apk: ActualOtoroshiApiKey, - computedApk: ActualOtoroshiApiKey, - otoroshiSettings: OtoroshiSettings, - tenant: Tenant, - tenantAdminTeam: Team - ) + parent: ApiSubscription, + childs: Seq[ApiSubscription], + apk: ActualOtoroshiApiKey, + computedApk: ActualOtoroshiApiKey, + otoroshiSettings: OtoroshiSettings, + tenant: Tenant, + tenantAdminTeam: Team + ) def start(): Unit = { if ( @@ -237,8 +240,8 @@ class OtoroshiVerifierJob( } private def computeAPIKey( - infos: SyncInformation - ): Future[ComputedInformation] = { + infos: SyncInformation + ): Future[ComputedInformation] = { (infos.childs :+ infos.parent) .map(subscription => { for { @@ -403,12 +406,16 @@ class OtoroshiVerifierJob( } }) - .foldLeft(infos.apk.copy( - authorizedEntities = AuthorizedEntities(), - tags = Set.empty, - metadata = Map.empty, - restrictions = ApiKeyRestrictions(), - ).future)((_apikey1, either) => { + .foldLeft( + infos.apk + .copy( + authorizedEntities = AuthorizedEntities(), + tags = Set.empty, + metadata = Map.empty, + restrictions = ApiKeyRestrictions() + ) + .future + )((_apikey1, either) => { either.value.flatMap { case Left(_) => _apikey1 case Right(apikey2) => @@ -462,8 +469,8 @@ class OtoroshiVerifierJob( otoroshiSettings = infos.otoroshiSettings, tenant = infos.tenant, tenantAdminTeam = infos.tenantAdminTeam - )} - ) + ) + }) } @@ -564,9 +571,11 @@ class OtoroshiVerifierJob( synclogger.error(e.getErrorMessage()) } } else { - FastFuture.successful(synclogger.info( - s"No need to update api key: ${infos.apk.clientName} on ${infos.otoroshiSettings.host}" - )) + FastFuture.successful( + synclogger.info( + s"No need to update api key: ${infos.apk.clientName} on ${infos.otoroshiSettings.host}" + ) + ) } } @@ -704,8 +713,8 @@ class OtoroshiVerifierJob( */ private def synchronizeApikeys( - query: JsObject = Json.obj("parent" -> JsNull) - ): Future[Done] = { + query: JsObject = Json.obj("parent" -> JsNull) + ): Future[Done] = { import cats.implicits._ val r = for { @@ -747,10 +756,12 @@ class OtoroshiVerifierJob( } yield subscriptions - val _allParentSubscriptions = r.leftMap(e => { - synclogger.error(e.getErrorMessage()) - Seq.empty[ApiSubscription] - }).merge + val _allParentSubscriptions = r + .leftMap(e => { + synclogger.error(e.getErrorMessage()) + Seq.empty[ApiSubscription] + }) + .merge Source .futureSource(_allParentSubscriptions.map(Source(_))) @@ -857,10 +868,8 @@ class OtoroshiVerifierJob( sendErrorNotification( NotificationAction.OtoroshiSyncSubscriptionError( subscription, - s"Unable to fetch apikey from otoroshi: ${ - Json - .stringify(AppError.toJson(e)) - }" + s"Unable to fetch apikey from otoroshi: ${Json + .stringify(AppError.toJson(e))}" ), parentApi.team, parentApi.tenant, diff --git a/daikoku/app/jobs/QueueJob.scala b/daikoku/app/jobs/QueueJob.scala index f97d26603..1fb50909c 100644 --- a/daikoku/app/jobs/QueueJob.scala +++ b/daikoku/app/jobs/QueueJob.scala @@ -536,35 +536,45 @@ class QueueJob( def deleteFirstOperation(): Future[Unit] = { val value: EitherT[Future, Unit, Unit] = for { - alreadyRunning <- EitherT.liftF(env.dataStore.operationRepo.forAllTenant() - .exists(Json.obj("Status" -> OperationStatus.InProgress.name))) + alreadyRunning <- EitherT.liftF( + env.dataStore.operationRepo + .forAllTenant() + .exists(Json.obj("Status" -> OperationStatus.InProgress.name)) + ) _ <- EitherT.cond[Future][Unit, Unit](!alreadyRunning, (), ()) - firstOperation <- EitherT.fromOptionF[Future, Unit, Operation](env.dataStore.operationRepo - .forAllTenant() - .findOne( - Json.obj( - "$and" -> Json.arr( - Json.obj("status" -> Json.obj("$ne" -> OperationStatus.Error.name)), - Json.obj("status" -> OperationStatus.Idle.name) + firstOperation <- EitherT.fromOptionF[Future, Unit, Operation]( + env.dataStore.operationRepo + .forAllTenant() + .findOne( + Json.obj( + "$and" -> Json.arr( + Json.obj( + "status" -> Json.obj("$ne" -> OperationStatus.Error.name) + ), + Json.obj("status" -> OperationStatus.Idle.name) + ) ) - ) - ), ()) - _ <- EitherT.liftF((firstOperation.itemType, firstOperation.action) match { - case (ItemType.Subscription, OperationAction.Delete) => - deleteSubscription(firstOperation) - case (ItemType.Api, OperationAction.Delete) => deleteApi(firstOperation) - case (ItemType.Team, OperationAction.Delete) => - deleteTeam(firstOperation) - case (ItemType.User, OperationAction.Delete) => - deleteUser(firstOperation) - case (ItemType.ThirdPartySubscription, OperationAction.Delete) => - deleteThirdPartySubscription(firstOperation) - case (ItemType.ThirdPartyProduct, OperationAction.Delete) => - deleteThirdPartyProduct(firstOperation) - case (ItemType.ApiKeyConsumption, OperationAction.Sync) => - syncConsumption(firstOperation) - case (_, _) => FastFuture.successful(()) - }) + ), + () + ) + _ <- + EitherT.liftF((firstOperation.itemType, firstOperation.action) match { + case (ItemType.Subscription, OperationAction.Delete) => + deleteSubscription(firstOperation) + case (ItemType.Api, OperationAction.Delete) => + deleteApi(firstOperation) + case (ItemType.Team, OperationAction.Delete) => + deleteTeam(firstOperation) + case (ItemType.User, OperationAction.Delete) => + deleteUser(firstOperation) + case (ItemType.ThirdPartySubscription, OperationAction.Delete) => + deleteThirdPartySubscription(firstOperation) + case (ItemType.ThirdPartyProduct, OperationAction.Delete) => + deleteThirdPartyProduct(firstOperation) + case (ItemType.ApiKeyConsumption, OperationAction.Sync) => + syncConsumption(firstOperation) + case (_, _) => FastFuture.successful(()) + }) _ <- EitherT.liftF(deleteFirstOperation()) } yield () value.merge diff --git a/daikoku/app/login/api.scala b/daikoku/app/login/api.scala index f87991fee..b5a307946 100644 --- a/daikoku/app/login/api.scala +++ b/daikoku/app/login/api.scala @@ -186,8 +186,7 @@ object TenantHelper { Results.NotFound, request, None, - env, - + env ) case Some(tenant) if !tenant.enabled => Errors.craftResponseResult( diff --git a/daikoku/app/login/oauth.scala b/daikoku/app/login/oauth.scala index 857de3b72..5ab4404a4 100644 --- a/daikoku/app/login/oauth.scala +++ b/daikoku/app/login/oauth.scala @@ -241,9 +241,12 @@ object OAuth2Support { .getOrElse("no.name@foo.bar") val picture = (userFromOauth \ authConfig.pictureField).asOpt[String] - val maybeDaikokuAdmin = (userFromOauth \ "daikokuAdmin").asOpt[Boolean] + val maybeDaikokuAdmin = + (userFromOauth \ "daikokuAdmin").asOpt[Boolean] - val isDaikokuAdmin = maybeDaikokuAdmin.getOrElse(authConfig.daikokuAdmins.contains(email)) + val isDaikokuAdmin = maybeDaikokuAdmin.getOrElse( + authConfig.daikokuAdmins.contains(email) + ) _env.dataStore.userRepo .findOne(Json.obj("_deleted" -> false, "email" -> email)) diff --git a/daikoku/app/utils/ApiService.scala b/daikoku/app/utils/ApiService.scala index 3c3e83519..8e981fad2 100644 --- a/daikoku/app/utils/ApiService.scala +++ b/daikoku/app/utils/ApiService.scala @@ -425,53 +425,85 @@ class ApiService( } def updateSubscription( - tenant: Tenant, - subscription: ApiSubscription, - plan: UsagePlan - ): Future[Either[AppError, JsObject]] = { + tenant: Tenant, + subscription: ApiSubscription, + plan: UsagePlan + ): Future[Either[AppError, JsObject]] = { import cats.implicits._ - val maybeTarget = plan.otoroshiTarget.map(_.otoroshiSettings).flatMap { id => - tenant.otoroshiSettings.find(_.id == id) + val maybeTarget = plan.otoroshiTarget.map(_.otoroshiSettings).flatMap { + id => + tenant.otoroshiSettings.find(_.id == id) } val r = for { - otoSettings <- EitherT.fromOption[Future][AppError, OtoroshiSettings](maybeTarget, AppError.OtoroshiSettingsNotFound) - authorizedEntities <- EitherT.fromOption[Future][AppError, AuthorizedEntities](plan.otoroshiTarget.flatMap(_.authorizedEntities), AppError.ApiNotLinked) - _ <- EitherT.cond[Future][AppError, Unit](!authorizedEntities.isEmpty, (), AppError.ApiNotLinked) - apiKey <- EitherT[Future, AppError, ActualOtoroshiApiKey](otoroshiClient.getApikey(subscription.apiKey.clientId)(otoSettings)) - - aggregatedSubs <- EitherT.liftF[Future, AppError, Seq[ApiSubscription]](env.dataStore.apiSubscriptionRepo.forTenant(tenant) - .findNotDeleted(Json.obj("parent" -> subscription.id.asJson))) - aggregatedPlan <- EitherT.liftF[Future, AppError, Seq[UsagePlan]](env.dataStore.usagePlanRepo.forTenant(tenant) - .findNotDeleted(Json.obj("_id" -> Json.obj("$in" -> JsArray(aggregatedSubs.map(_.plan.asJson)))))) - aggregatedAuthorizedEntities <- EitherT.pure[Future, AppError](aggregatedPlan - .map(_.otoroshiTarget.flatMap(_.authorizedEntities).getOrElse(AuthorizedEntities()))) - - _authorizedEntities = aggregatedAuthorizedEntities.fold(authorizedEntities)((acc, curr) => { - AuthorizedEntities( - services = acc.services ++ curr.services, - groups = acc.groups ++ curr.groups, - routes = acc.routes ++ curr.routes + otoSettings <- EitherT.fromOption[Future][AppError, OtoroshiSettings]( + maybeTarget, + AppError.OtoroshiSettingsNotFound + ) + authorizedEntities <- + EitherT.fromOption[Future][AppError, AuthorizedEntities]( + plan.otoroshiTarget.flatMap(_.authorizedEntities), + AppError.ApiNotLinked ) - }) + _ <- EitherT.cond[Future][AppError, Unit]( + !authorizedEntities.isEmpty, + (), + AppError.ApiNotLinked + ) + apiKey <- EitherT[Future, AppError, ActualOtoroshiApiKey]( + otoroshiClient.getApikey(subscription.apiKey.clientId)(otoSettings) + ) - _ <- EitherT[Future, AppError, ActualOtoroshiApiKey](otoroshiClient.updateApiKey( - apiKey.copy( - authorizedEntities = _authorizedEntities, - throttlingQuota = subscription.customMaxPerSecond - .getOrElse(apiKey.throttlingQuota), - dailyQuota = - subscription.customMaxPerDay.getOrElse(apiKey.dailyQuota), - monthlyQuota = subscription.customMaxPerMonth - .getOrElse(apiKey.monthlyQuota), - metadata = apiKey.metadata ++ subscription.customMetadata - .flatMap(_.asOpt[Map[String, String]]) - .getOrElse(Map.empty[String, String]), - readOnly = - subscription.customReadOnly.getOrElse(apiKey.readOnly) - ) - )(otoSettings) + aggregatedSubs <- EitherT.liftF[Future, AppError, Seq[ApiSubscription]]( + env.dataStore.apiSubscriptionRepo + .forTenant(tenant) + .findNotDeleted(Json.obj("parent" -> subscription.id.asJson)) + ) + aggregatedPlan <- EitherT.liftF[Future, AppError, Seq[UsagePlan]]( + env.dataStore.usagePlanRepo + .forTenant(tenant) + .findNotDeleted( + Json.obj( + "_id" -> Json + .obj("$in" -> JsArray(aggregatedSubs.map(_.plan.asJson))) + ) + ) + ) + aggregatedAuthorizedEntities <- EitherT.pure[Future, AppError]( + aggregatedPlan + .map( + _.otoroshiTarget + .flatMap(_.authorizedEntities) + .getOrElse(AuthorizedEntities()) + ) + ) + + _authorizedEntities = + aggregatedAuthorizedEntities.fold(authorizedEntities)((acc, curr) => { + AuthorizedEntities( + services = acc.services ++ curr.services, + groups = acc.groups ++ curr.groups, + routes = acc.routes ++ curr.routes + ) + }) + + _ <- EitherT[Future, AppError, ActualOtoroshiApiKey]( + otoroshiClient.updateApiKey( + apiKey.copy( + authorizedEntities = _authorizedEntities, + throttlingQuota = subscription.customMaxPerSecond + .getOrElse(apiKey.throttlingQuota), + dailyQuota = + subscription.customMaxPerDay.getOrElse(apiKey.dailyQuota), + monthlyQuota = subscription.customMaxPerMonth + .getOrElse(apiKey.monthlyQuota), + metadata = apiKey.metadata ++ subscription.customMetadata + .flatMap(_.asOpt[Map[String, String]]) + .getOrElse(Map.empty[String, String]), + readOnly = subscription.customReadOnly.getOrElse(apiKey.readOnly) + ) + )(otoSettings) ) _ <- EitherT.liftF[Future, AppError, Boolean]( env.dataStore.apiSubscriptionRepo @@ -483,8 +515,6 @@ class ApiService( r.value } - - def deleteApiKey( tenant: Tenant, subscription: ApiSubscription, diff --git a/daikoku/app/utils/DeletionService.scala b/daikoku/app/utils/DeletionService.scala index 278095269..8bd178184 100644 --- a/daikoku/app/utils/DeletionService.scala +++ b/daikoku/app/utils/DeletionService.scala @@ -89,9 +89,16 @@ class DeletionService( tenant: Tenant ): EitherT[Future, AppError, Unit] = { for { - target <- EitherT.fromOption[Future](plan.otoroshiTarget, AppError.EntityNotFound("Otoroshi settings")) - settings <- EitherT.fromOption[Future](tenant.otoroshiSettings.find(s => s.id == target.otoroshiSettings), AppError.EntityNotFound("Otoroshi settings")) - _ <- otoroshiClient.deleteApiKey(apiSubscription.apiKey.clientId)(settings) + target <- EitherT.fromOption[Future]( + plan.otoroshiTarget, + AppError.EntityNotFound("Otoroshi settings") + ) + settings <- EitherT.fromOption[Future]( + tenant.otoroshiSettings.find(s => s.id == target.otoroshiSettings), + AppError.EntityNotFound("Otoroshi settings") + ) + _ <- + otoroshiClient.deleteApiKey(apiSubscription.apiKey.clientId)(settings) } yield () } diff --git a/daikoku/app/utils/errors.scala b/daikoku/app/utils/errors.scala index 22a76deb1..20d155f2b 100644 --- a/daikoku/app/utils/errors.scala +++ b/daikoku/app/utils/errors.scala @@ -26,13 +26,15 @@ object Errors { env: Env ): Future[Result] = { - val accept = req.headers.get("Accept").getOrElse("text/html").split(",").toSeq + val accept = + req.headers.get("Accept").getOrElse("text/html").split(",").toSeq if (accept.contains("text/html")) { val msg = Base64.getEncoder.encodeToString(message.getBytes) FastFuture.successful( - Redirect(s"${req.theProtocol}://${req.domain}:${env.config.exposedPort}/error#$msg") - .withHeaders( + Redirect( + s"${req.theProtocol}://${req.domain}:${env.config.exposedPort}/error#$msg" + ).withHeaders( "x-error" -> "true", "x-error-msg" -> message // TODO: handled by otoroshi filter ? diff --git a/daikoku/app/utils/otoroshi.scala b/daikoku/app/utils/otoroshi.scala index 583268497..b0125a646 100644 --- a/daikoku/app/utils/otoroshi.scala +++ b/daikoku/app/utils/otoroshi.scala @@ -290,10 +290,20 @@ class OtoroshiClient(env: Env) { def deleteApiKey( clientId: String - )(implicit otoroshiSettings: OtoroshiSettings): EitherT[Future, AppError, Unit] = { + )(implicit + otoroshiSettings: OtoroshiSettings + ): EitherT[Future, AppError, Unit] = { for { resp <- EitherT.liftF(client(s"/api/apikeys/$clientId").delete()) - _ <- EitherT.cond[Future][AppError, Unit](resp.status == 200, (), AppError.OtoroshiError(Json.obj("error" -> s"Error while deleting otoroshi apikey: ${resp.status} - ${resp.body}"))) + _ <- EitherT.cond[Future][AppError, Unit]( + resp.status == 200, + (), + AppError.OtoroshiError( + Json.obj( + "error" -> s"Error while deleting otoroshi apikey: ${resp.status} - ${resp.body}" + ) + ) + ) } yield () } diff --git a/daikoku/app/utils/request.scala b/daikoku/app/utils/request.scala index 421728bd9..9785062c8 100644 --- a/daikoku/app/utils/request.scala +++ b/daikoku/app/utils/request.scala @@ -55,7 +55,8 @@ object RequestImplicits { .get("Otoroshi-Proxied-Host") .orElse(requestHeader.headers.get("X-Forwarded-Host")) .getOrElse(requestHeader.host) - .split(':').head + .split(':') + .head } def getLanguage(tenant: Tenant): String = { diff --git a/daikoku/javascript/src/components/utils/tenantUtils.ts b/daikoku/javascript/src/components/utils/tenantUtils.ts index 104171bc6..8c909b051 100644 --- a/daikoku/javascript/src/components/utils/tenantUtils.ts +++ b/daikoku/javascript/src/components/utils/tenantUtils.ts @@ -24,20 +24,24 @@ export const injectFavicon = (src) => { var link = document.createElement('link'); link.id = 'favicon'; link.rel = 'shortcut icon'; - link.href = src + link.href = src; document.head.appendChild(link); -} +}; export const injectFontFamily = (ffUrl) => { - injectCSS("\ + injectCSS( + "\ @font-face {\ font-family: Custom;\ - src: url('" + ffUrl + "') format('yourFontFormat');\ + src: url('" + + ffUrl + + "') format('yourFontFormat');\ }\ -"); -} +" + ); +}; export const parseAsHtml = (element: string): DocumentFragment => { const parse = Range.prototype.createContextualFragment.bind(document.createRange()); - return parse(element) -} \ No newline at end of file + return parse(element); +}; diff --git a/daikoku/javascript/src/services/index.ts b/daikoku/javascript/src/services/index.ts index 2a35abeb8..fbb767a95 100644 --- a/daikoku/javascript/src/services/index.ts +++ b/daikoku/javascript/src/services/index.ts @@ -59,7 +59,8 @@ export const me = (): PromiseWithError => customFetch('/api/me'); export const myOwnTeam = () => customFetch('/api/me/teams/own'); export const oneOfMyTeam = (id: any) => customFetch(`/api/me/teams/${id}`); export const getUserContext = (): PromiseWithError => customFetch('/api/me/context'); -export const getAuthContext = (provider: string): PromiseWithError => customFetch(`/api/auth/${provider}/context`); +export const getAuthContext = (provider: string): PromiseWithError => + customFetch(`/api/auth/${provider}/context`); export const getVisibleApiWithId = (id: string): PromiseWithError => customFetch(`/api/me/visible-apis/${id}`); @@ -1018,7 +1019,12 @@ function updateQueryStringParameter(uri, key, value) { } } -export const login = (username: string, password: string, action: string, redirect?: string | null) => { +export const login = ( + username: string, + password: string, + action: string, + redirect?: string | null +) => { const body = new URLSearchParams(); body.append('username', username); body.append('password', password); diff --git a/daikoku/javascript/src/types/api.ts b/daikoku/javascript/src/types/api.ts index 9423d8e1e..edf8614ee 100644 --- a/daikoku/javascript/src/types/api.ts +++ b/daikoku/javascript/src/types/api.ts @@ -147,14 +147,14 @@ export interface IImportingDocumentation { export enum SpecificationType { openapi = 'openapi', - asyncapi = 'asyncapi' + asyncapi = 'asyncapi', } export interface ISwagger { url?: string; content?: string; headers: { [key: string]: string }; additionalConf?: object; - specificationType: SpecificationType + specificationType: SpecificationType; } export type IValidationStepType = 'teamAdmin' | 'email' | 'payment' | 'httpRequest'; diff --git a/daikoku/javascript/src/types/context.ts b/daikoku/javascript/src/types/context.ts index b2dca27d9..40d6a0758 100644 --- a/daikoku/javascript/src/types/context.ts +++ b/daikoku/javascript/src/types/context.ts @@ -14,7 +14,7 @@ export interface IStateModal { } export interface IAuthContext { - action: string + action: string; } export interface IStateContext { diff --git a/daikoku/javascript/src/types/tenant.ts b/daikoku/javascript/src/types/tenant.ts index 4a8311048..caae5260a 100644 --- a/daikoku/javascript/src/types/tenant.ts +++ b/daikoku/javascript/src/types/tenant.ts @@ -189,7 +189,6 @@ export interface ITenant { jsUrl?: string; faviconUrl?: string; fontFamilyUrl?: string; - } export interface ITenantFull extends ITenant { diff --git a/daikoku/test/daikoku/AdminApiControllerSpec.scala b/daikoku/test/daikoku/AdminApiControllerSpec.scala index ddbc5dbeb..3d53d1ed6 100644 --- a/daikoku/test/daikoku/AdminApiControllerSpec.scala +++ b/daikoku/test/daikoku/AdminApiControllerSpec.scala @@ -1235,7 +1235,11 @@ class AdminApiControllerSpec respCreateKo.status mustBe 400 val some = defaultApi.api - .copy(id = ApiId(IdGenerator.token), parent = defaultApi.api.id.some, currentVersion = Version("vTest")) + .copy( + id = ApiId(IdGenerator.token), + parent = defaultApi.api.id.some, + currentVersion = Version("vTest") + ) .asJson .some diff --git a/daikoku/test/daikoku/ApiControllerSpec.scala b/daikoku/test/daikoku/ApiControllerSpec.scala index 3c7101ed1..aba173475 100644 --- a/daikoku/test/daikoku/ApiControllerSpec.scala +++ b/daikoku/test/daikoku/ApiControllerSpec.scala @@ -9,13 +9,19 @@ import com.github.tomakehurst.wiremock.client.WireMock._ import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig import controllers.AppError import controllers.AppError.SubscriptionAggregationDisabled -import fr.maif.otoroshi.daikoku.domain.NotificationAction.{ApiAccess, ApiSubscriptionDemand} +import fr.maif.otoroshi.daikoku.domain.NotificationAction.{ + ApiAccess, + ApiSubscriptionDemand +} import fr.maif.otoroshi.daikoku.domain.NotificationType.AcceptOrReject import fr.maif.otoroshi.daikoku.domain.TeamPermission.Administrator import fr.maif.otoroshi.daikoku.domain.UsagePlan._ import fr.maif.otoroshi.daikoku.domain.UsagePlanVisibility.{Private, Public} import fr.maif.otoroshi.daikoku.domain._ -import fr.maif.otoroshi.daikoku.domain.json.{ApiFormat, SeqApiSubscriptionFormat} +import fr.maif.otoroshi.daikoku.domain.json.{ + ApiFormat, + SeqApiSubscriptionFormat +} import fr.maif.otoroshi.daikoku.logger.AppLogger import fr.maif.otoroshi.daikoku.tests.utils.DaikokuSpecHelper import fr.maif.otoroshi.daikoku.utils.IdGenerator @@ -37,21 +43,26 @@ class ApiControllerSpec() extends PlaySpec with DaikokuSpecHelper with IntegrationPatience - with BeforeAndAfterEach - with BeforeAndAfter - with ForAllTestContainer { + with BeforeAndAfterEach + with BeforeAndAfter + with ForAllTestContainer { val pwd = System.getProperty("user.dir"); - lazy val wireMockServer = new WireMockServer(wireMockConfig() - .port(stubPort)) + lazy val wireMockServer = new WireMockServer( + wireMockConfig() + .port(stubPort) + ) override val container = GenericContainer( "maif/otoroshi", exposedPorts = Seq(8080), fileSystemBind = Seq( - FileSystemBind(s"$pwd/test/daikoku/otoroshi.json", - "/home/user/otoroshi.json", - BindMode.READ_ONLY)), + FileSystemBind( + s"$pwd/test/daikoku/otoroshi.json", + "/home/user/otoroshi.json", + BindMode.READ_ONLY + ) + ), env = Map("APP_IMPORT_FROM" -> "/home/user/otoroshi.json") ) @@ -2818,7 +2829,8 @@ class ApiControllerSpec() val sessionUser = loginWithBlocking(user, tenant) val subAdminResp = httpJsonCallBlocking( - path = s"/api/me/subscriptions/${defaultApi.api.id.value}/${defaultApi.api.currentVersion.value}" + path = + s"/api/me/subscriptions/${defaultApi.api.id.value}/${defaultApi.api.currentVersion.value}" )(tenant, sessionAdmin) logger.warn(Json.prettyPrint(subAdminResp.json)) subAdminResp.status mustBe 200 @@ -5190,7 +5202,6 @@ class ApiControllerSpec() aggregationApiKeysSecurity = Some(true) ) - val parentApi = defaultApi.api.copy( id = ApiId("parent-id"), name = "parent API", @@ -5203,7 +5214,7 @@ class ApiControllerSpec() name = "child API", team = teamOwnerId, possibleUsagePlans = Seq(UsagePlanId("child.dev")), - defaultUsagePlan = UsagePlanId("child.dev").some, + defaultUsagePlan = UsagePlanId("child.dev").some ) val parentSub = ApiSubscription( @@ -5234,25 +5245,28 @@ class ApiControllerSpec() parent = Some(parentSub.id) ) - setupEnvBlocking( - tenants = Seq(tenant.copy( - aggregationApiKeysSecurity = Some(true), - otoroshiSettings = Set( - OtoroshiSettings( - id = containerizedOtoroshi, - url = s"http://otoroshi.oto.tools:${container.mappedPort(8080)}", - host = "otoroshi-api.oto.tools", - clientSecret = otoroshiAdminApiKey.clientSecret, - clientId = otoroshiAdminApiKey.clientId - ), - ))), + tenants = Seq( + tenant.copy( + aggregationApiKeysSecurity = Some(true), + otoroshiSettings = Set( + OtoroshiSettings( + id = containerizedOtoroshi, + url = + s"http://otoroshi.oto.tools:${container.mappedPort(8080)}", + host = "otoroshi-api.oto.tools", + clientSecret = otoroshiAdminApiKey.clientSecret, + clientId = otoroshiAdminApiKey.clientId + ) + ) + ) + ), users = Seq(userAdmin), teams = Seq(teamOwner, teamConsumer), usagePlans = Seq(parentPlan, childPlan), apis = Seq(parentApi, childApi), - subscriptions = Seq(parentSub, childSub)) - + subscriptions = Seq(parentSub, childSub) + ) // AppLogger.info(container.logs) val session = loginWithBlocking(userAdmin, tenant) @@ -5260,9 +5274,12 @@ class ApiControllerSpec() path = s"/api/teams/${teamOwnerId.value}/subscriptions/${parentSub.id.value}", method = "PUT", - body = parentSub.copy( - customMetadata = Json.obj("foo"-> "bar").some - ).asJson.some + body = parentSub + .copy( + customMetadata = Json.obj("foo" -> "bar").some + ) + .asJson + .some )(tenant, session) resp.status mustBe 200 @@ -5282,8 +5299,8 @@ class ApiControllerSpec() val authorizations = (respVerif.json \ "authorizations").as[JsArray] val strings = authorizations.value.map(value => (value \ "id").as[String]) strings.size mustBe 2 - strings.contains(childRouteId) mustBe true - strings.contains(parentRouteId) mustBe true + strings.contains(childRouteId) mustBe true + strings.contains(parentRouteId) mustBe true } "update plan in aggregated APIkey do not erase authorizedEntities" in { val parentPlan = FreeWithoutQuotas( @@ -5333,7 +5350,6 @@ class ApiControllerSpec() aggregationApiKeysSecurity = Some(true) ) - val parentApi = defaultApi.api.copy( id = ApiId("parent-id"), name = "parent API", @@ -5346,7 +5362,7 @@ class ApiControllerSpec() name = "child API", team = teamOwnerId, possibleUsagePlans = Seq(UsagePlanId("child.dev")), - defaultUsagePlan = UsagePlanId("child.dev").some, + defaultUsagePlan = UsagePlanId("child.dev").some ) val parentSub = ApiSubscription( @@ -5377,35 +5393,38 @@ class ApiControllerSpec() parent = Some(parentSub.id) ) - setupEnvBlocking( - tenants = Seq(tenant.copy( - aggregationApiKeysSecurity = Some(true), - otoroshiSettings = Set( - OtoroshiSettings( - id = containerizedOtoroshi, - url = s"http://otoroshi.oto.tools:${container.mappedPort(8080)}", - host = "otoroshi-api.oto.tools", - clientSecret = otoroshiAdminApiKey.clientSecret, - clientId = otoroshiAdminApiKey.clientId - ), - ))), + tenants = Seq( + tenant.copy( + aggregationApiKeysSecurity = Some(true), + otoroshiSettings = Set( + OtoroshiSettings( + id = containerizedOtoroshi, + url = + s"http://otoroshi.oto.tools:${container.mappedPort(8080)}", + host = "otoroshi-api.oto.tools", + clientSecret = otoroshiAdminApiKey.clientSecret, + clientId = otoroshiAdminApiKey.clientId + ) + ) + ) + ), users = Seq(userAdmin), teams = Seq(teamOwner, teamConsumer, defaultAdminTeam), usagePlans = Seq(parentPlan, childPlan, adminApiPlan), apis = Seq(parentApi, childApi, adminApi), - subscriptions = Seq(parentSub, childSub)) - + subscriptions = Seq(parentSub, childSub) + ) // AppLogger.info(container.logs) val session = loginWithBlocking(userAdmin, tenant) - val resp = httpJsonCallBlocking( path = s"/api/teams/${teamOwnerId.value}/apis/${parentApi.id.value}/${parentApi.currentVersion.value}/plan/${parentPlan.id.value}", method = "PUT", - body = parentPlan.copy( + body = parentPlan + .copy( otoroshiTarget = OtoroshiTarget( containerizedOtoroshi, Some( @@ -5414,7 +5433,9 @@ class ApiControllerSpec() ) ) ).some - ).asJson.some + ) + .asJson + .some )(tenant, session) resp.status mustBe 200 @@ -5434,24 +5455,29 @@ class ApiControllerSpec() val authorizations = (respVerif.json \ "authorizations").as[JsArray] val strings = authorizations.value.map(value => (value \ "id").as[String]) strings.size mustBe 2 - strings.contains(otherRouteId) mustBe true - strings.contains(childRouteId) mustBe true - + strings.contains(otherRouteId) mustBe true + strings.contains(childRouteId) mustBe true httpJsonCallBlocking( path = s"/api/teams/${teamOwnerId.value}/apis/${childApi.id.value}/${childApi.currentVersion.value}/plan/${childPlan.id.value}", method = "PUT", - body = childPlan.copy( - otoroshiTarget = OtoroshiTarget( - containerizedOtoroshi, - Some( - AuthorizedEntities( - routes = Set(OtoroshiRouteId(parentRouteId), OtoroshiRouteId(childRouteId)) + body = childPlan + .copy( + otoroshiTarget = OtoroshiTarget( + containerizedOtoroshi, + Some( + AuthorizedEntities( + routes = Set( + OtoroshiRouteId(parentRouteId), + OtoroshiRouteId(childRouteId) + ) + ) ) - ) - ).some - ).asJson.some + ).some + ) + .asJson + .some )(tenant, session) val respVerif2 = httpJsonCallBlocking( @@ -5467,7 +5493,8 @@ class ApiControllerSpec() AppLogger.info(Json.prettyPrint(respVerif2.json)) val authorizations2 = (respVerif2.json \ "authorizations").as[JsArray] - val strings2 = authorizations2.value.map(value => (value \ "id").as[String]) + val strings2 = + authorizations2.value.map(value => (value \ "id").as[String]) strings2.size mustBe 3 } } diff --git a/daikoku/test/daikoku/suites.scala b/daikoku/test/daikoku/suites.scala index aa0c33428..0f38eedc8 100644 --- a/daikoku/test/daikoku/suites.scala +++ b/daikoku/test/daikoku/suites.scala @@ -752,11 +752,14 @@ object utils { val otoroshiAdminApiKey = OtoroshiApiKey( clientName = "Otoroshi Backoffice ApiKey", clientId = "admin-api-apikey-id", - clientSecret= "admin-api-apikey-secret") + clientSecret = "admin-api-apikey-secret" + ) val parentApiKey = OtoroshiApiKey( clientName = "daikoku_test_parent_key", clientId = "5w24yl2ly3dlnn92", - clientSecret= "8iwm9fhbns0rmybnyul5evq9l1o4dxza0rh7rt4flay69jolw3okbz1owfl6w2db") + clientSecret = + "8iwm9fhbns0rmybnyul5evq9l1o4dxza0rh7rt4flay69jolw3okbz1owfl6w2db" + ) val parentRouteId = "route_d74ea8b27-b8be-4177-82d9-c50722416c50" val childRouteId = "route_8ce030cbd-6c07-43d4-9c61-4a330ae0975d"