diff --git a/build-base/src/main/groovy/readonlyrest.base-common-conventions.gradle b/build-base/src/main/groovy/readonlyrest.base-common-conventions.gradle index 3a36cbd8a9..4c61cbccb8 100644 --- a/build-base/src/main/groovy/readonlyrest.base-common-conventions.gradle +++ b/build-base/src/main/groovy/readonlyrest.base-common-conventions.gradle @@ -32,6 +32,13 @@ dependencies { implementation group: 'org.scala-lang', name: 'scala-library', version: '2.13.13' } +tasks.withType(ScalaCompile).configureEach { + scalaCompileOptions.forkOptions.with { + memoryMaximumSize = '1g' + jvmArgs = ['-XX:MaxMetaspaceSize=512m'] + } +} + test { reports { junitXml.getRequired().set(true) diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/definitions/ExternalAuthorizationService.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/definitions/ExternalAuthorizationService.scala index 043d21c5ae..5af051e25b 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/definitions/ExternalAuthorizationService.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/definitions/ExternalAuthorizationService.scala @@ -26,7 +26,8 @@ import eu.timepit.refined.types.string.NonEmptyString import monix.eval.Task import org.apache.logging.log4j.scala.Logging import tech.beshu.ror.accesscontrol.blocks.definitions.ExternalAuthorizationService.Name -import tech.beshu.ror.accesscontrol.blocks.definitions.HttpExternalAuthorizationService.AuthTokenSendMethod.{UsingHeader, UsingQueryParam} +import tech.beshu.ror.accesscontrol.blocks.definitions.HttpExternalAuthorizationService.Config.AuthTokenSendMethod.{UsingHeader, UsingQueryParam} +import tech.beshu.ror.accesscontrol.blocks.definitions.HttpExternalAuthorizationService.Config._ import tech.beshu.ror.accesscontrol.blocks.definitions.HttpExternalAuthorizationService._ import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId import tech.beshu.ror.accesscontrol.domain._ @@ -34,11 +35,11 @@ import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory.HttpClient import tech.beshu.ror.accesscontrol.factory.decoders.definitions.Definitions.Item import tech.beshu.ror.accesscontrol.show.logs._ import tech.beshu.ror.accesscontrol.utils.CacheableAction -import tech.beshu.ror.com.jayway.jsonpath.JsonPath +import tech.beshu.ror.utils.json.JsonPath import tech.beshu.ror.utils.uniquelist.UniqueList -import scala.jdk.CollectionConverters._ import scala.concurrent.duration.FiniteDuration +import scala.jdk.CollectionConverters._ import scala.util.{Failure, Success, Try} trait ExternalAuthorizationService extends Item { @@ -58,16 +59,10 @@ object ExternalAuthorizationService { } } -class HttpExternalAuthorizationService(override val id: ExternalAuthorizationService#Id, - uri: Uri, - method: SupportedHttpMethod, - tokenName: AuthTokenName, - groupsJsonPath: JsonPath, - authTokenSendMethod: AuthTokenSendMethod, - defaultHeaders: Set[Header], - defaultQueryParams: Set[QueryParam], - override val serviceTimeout: Refined[FiniteDuration, Positive], - httpClient: HttpClient) +final class HttpExternalAuthorizationService(override val id: ExternalAuthorizationService#Id, + override val serviceTimeout: Refined[FiniteDuration, Positive], + val config: HttpExternalAuthorizationService.Config, + httpClient: HttpClient) extends ExternalAuthorizationService with Logging { @@ -85,8 +80,8 @@ class HttpExternalAuthorizationService(override val id: ExternalAuthorizationSer } private def createRequest(userId: User.Id) = { - val uriWithParams = uri.params(queryParams(userId)) - method match { + val uriWithParams = config.uri.params(queryParams(userId)) + config.method match { case SupportedHttpMethod.Get => sttp .get(uriWithParams) @@ -98,16 +93,25 @@ class HttpExternalAuthorizationService(override val id: ExternalAuthorizationSer } } + private def queryParams(userId: User.Id): Map[String, String] = { + config.defaultQueryParams.map(p => (autoUnwrap(p.name), autoUnwrap(p.value))).toMap ++ + (config.authTokenSendMethod match { + case UsingQueryParam => Map(config.tokenName.value.value -> userId.value.value) + case UsingHeader => Map.empty[String, String] + }) + } + + private def headersMap(userId: User.Id): Map[String, String] = { + config.defaultHeaders.map(h => (h.name.value.value, h.value.value)).toMap ++ + (config.authTokenSendMethod match { + case UsingHeader => Map(config.tokenName.value.value -> userId.value.value) + case UsingQueryParam => Map.empty + }) + } + private def groupsFromResponseBody(body: String): UniqueList[Group] = { - val groupsFromPath = - Try(groupsJsonPath.read[java.util.List[String]](body)) - .map( - _.asScala - .flatMap(NonEmptyString.from(_).toOption) - .map(GroupId.apply) - .map(Group.from) - ) - groupsFromPath match { + val groupsFromBody = groupsFrom(body) + groupsFromBody match { case Success(groups) => logger.debug(s"Groups returned by groups provider '${id.show}': ${groups.map(_.show).mkString(",")}") UniqueList.fromIterable(groups) @@ -117,44 +121,104 @@ class HttpExternalAuthorizationService(override val id: ExternalAuthorizationSer } } - private def queryParams(userId: User.Id): Map[String, String] = { - defaultQueryParams.map(p => (autoUnwrap(p.name), autoUnwrap(p.value))).toMap ++ - (authTokenSendMethod match { - case UsingQueryParam => Map(tokenName.value.value -> userId.value.value) - case UsingHeader => Map.empty[String, String] - }) + private def groupsFrom(body: String): Try[List[Group]] = { + for { + rawGroupIds <- groupIdsFrom(body) + groups <- groupsFrom(body, rawGroupIds) + } yield groups } - private def headersMap(userId: User.Id): Map[String, String] = { - defaultHeaders.map(h => (h.name.value.value, h.value.value)).toMap ++ - (authTokenSendMethod match { - case UsingHeader => Map(tokenName.value.value -> userId.value.value) - case UsingQueryParam => Map.empty - }) + private def groupIdsFrom(body: String) = { + config.groupsConfig.idsConfig.jsonPath.read[java.util.List[String]](body) + .map { + _.asScala.toList + } } + + private def groupsFrom(body: String, rawGroupIds: List[String]): Try[List[Group]] = { + config.groupsConfig.namesConfig match { + case Some(namesConfig) => + groupNamesFrom(body, namesConfig) + .flatMap { + case rawGroupNames if rawGroupNames.size == rawGroupIds.size => + Success(formGroups(groupIdsWithNames = rawGroupIds.zip(rawGroupNames))) + case rawGroupNames => + Failure(new IllegalArgumentException( + s"Group names array extracted from the response at json path ${namesConfig.jsonPath.rawPath} has different size [size=${rawGroupNames.size}] than " + + s"the group IDs array extracted from the response at json path ${config.groupsConfig.idsConfig.jsonPath.rawPath} [size=${rawGroupIds.size}]" + )) + } + case None => + Success( + rawGroupIds + .flatMap(toGroupId) + .map(Group.from) + ) + } + } + + private def groupNamesFrom(body: String, namesConfig: GroupsConfig.GroupNamesConfig): Try[List[String]] = { + namesConfig.jsonPath.read[java.util.List[String]](body) + .map { + _.asScala.toList + } + } + + private def formGroups(groupIdsWithNames: List[(String, String)]) = { + groupIdsWithNames.flatMap { case (groupId, groupName) => + toGroupId(groupId) + .map(id => Group(id, toGroupName(value = groupName, fallback = GroupName.from(id)))) + } + } + + private def toGroupId(value: String): Option[GroupId] = NonEmptyString.unapply(value).map(GroupId.apply) + + private def toGroupName(value: String, fallback: GroupName) = + NonEmptyString + .unapply(value) + .map(GroupName.apply) + .getOrElse(fallback) } object HttpExternalAuthorizationService { - final case class QueryParam(name: NonEmptyString, value: NonEmptyString) - final case class AuthTokenName(value: NonEmptyString) - sealed trait AuthTokenSendMethod - object AuthTokenSendMethod { - case object UsingHeader extends AuthTokenSendMethod - case object UsingQueryParam extends AuthTokenSendMethod - } + final case class Config(uri: Uri, + method: SupportedHttpMethod, + tokenName: AuthTokenName, + groupsConfig: GroupsConfig, + authTokenSendMethod: AuthTokenSendMethod, + defaultHeaders: Set[Header], + defaultQueryParams: Set[QueryParam]) + + object Config { + sealed trait SupportedHttpMethod + object SupportedHttpMethod { + case object Get extends SupportedHttpMethod + case object Post extends SupportedHttpMethod + } - sealed trait SupportedHttpMethod - object SupportedHttpMethod { - case object Get extends SupportedHttpMethod - case object Post extends SupportedHttpMethod + final case class AuthTokenName(value: NonEmptyString) + + final case class GroupsConfig(idsConfig: GroupsConfig.GroupIdsConfig, namesConfig: Option[GroupsConfig.GroupNamesConfig]) + object GroupsConfig { + final case class GroupIdsConfig(jsonPath: JsonPath) + final case class GroupNamesConfig(jsonPath: JsonPath) + } + + final case class QueryParam(name: NonEmptyString, value: NonEmptyString) + + sealed trait AuthTokenSendMethod + object AuthTokenSendMethod { + case object UsingHeader extends AuthTokenSendMethod + case object UsingQueryParam extends AuthTokenSendMethod + } } final case class InvalidResponse(message: String) extends Exception(message) } -class CacheableExternalAuthorizationServiceDecorator(underlying: ExternalAuthorizationService, - ttl: FiniteDuration Refined Positive) +final class CacheableExternalAuthorizationServiceDecorator(val underlying: ExternalAuthorizationService, + val ttl: FiniteDuration Refined Positive) extends ExternalAuthorizationService { private val cacheableGrantsFor = new CacheableAction[User.Id, UniqueList[Group]](ttl, underlying.grantsFor) diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/definitions/JwtDef.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/definitions/JwtDef.scala index 9396d15806..f8b30b061a 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/definitions/JwtDef.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/definitions/JwtDef.scala @@ -19,7 +19,7 @@ package tech.beshu.ror.accesscontrol.blocks.definitions import java.security.PublicKey import cats.{Eq, Show} import eu.timepit.refined.types.string.NonEmptyString -import tech.beshu.ror.accesscontrol.blocks.definitions.JwtDef.{Name, SignatureCheckMethod} +import tech.beshu.ror.accesscontrol.blocks.definitions.JwtDef.{GroupsConfig, Name, SignatureCheckMethod} import tech.beshu.ror.accesscontrol.domain.{AuthorizationTokenDef, Jwt} import tech.beshu.ror.accesscontrol.factory.decoders.definitions.Definitions.Item @@ -27,7 +27,7 @@ final case class JwtDef(id: Name, authorizationTokenDef: AuthorizationTokenDef, checkMethod: SignatureCheckMethod, userClaim: Option[Jwt.ClaimName], - groupsClaim: Option[Jwt.ClaimName]) + groupsConfig: Option[GroupsConfig]) extends Item { override type Id = Name @@ -44,6 +44,8 @@ object JwtDef { final case class Ec(pubKey: PublicKey) extends SignatureCheckMethod } + final case class GroupsConfig(idsClaim: Jwt.ClaimName, namesClaim: Option[Jwt.ClaimName]) + implicit val nameEq: Eq[Name] = Eq.fromUniversalEquals implicit val nameShow: Show[Name] = Show.show(_.value.value) } \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/metadata/MetadataValue.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/metadata/MetadataValue.scala index f3a6fc7cc3..386285e71c 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/metadata/MetadataValue.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/metadata/MetadataValue.scala @@ -22,7 +22,7 @@ import cats.implicits._ import tech.beshu.ror.accesscontrol.domain.Json._ import tech.beshu.ror.accesscontrol.domain.KibanaAllowedApiPath.AllowedHttpMethod import tech.beshu.ror.accesscontrol.domain.KibanaAllowedApiPath.AllowedHttpMethod.HttpMethod -import tech.beshu.ror.accesscontrol.domain.{CorrelationId, KibanaAccess, KibanaApp} +import tech.beshu.ror.accesscontrol.domain.{CorrelationId, Group, KibanaAccess, KibanaApp} import scala.jdk.CollectionConverters._ @@ -121,7 +121,7 @@ object MetadataValue { private def availableGroups(userMetadata: UserMetadata) = { NonEmptyList .fromList(userMetadata.availableGroups.toList) - .map(groups => ("x-ror-available-groups", MetadataList(groups.map(_.id.value.value)))) + .map(groups => ("x-ror-available-groups", MetadataListOfMaps(groups.map(serializeGroup)))) .toMap } @@ -134,13 +134,19 @@ object MetadataValue { } private def currentGroup(userMetadata: UserMetadata) = { - userMetadata.currentGroupId.map(g => ("x-ror-current-group", MetadataString(g.value.value))).toMap + userMetadata + .findCurrentGroup + .map(serializeGroup) + .map(group => ("x-ror-current-group", MetadataObject(group.asJava))) + .toMap } private def loggedUser(userMetadata: UserMetadata) = { userMetadata.loggedUser.map(u => ("x-ror-username", MetadataString(u.id.value.value))).toMap } + private def serializeGroup(group: Group) = Map("id" -> group.id.value.value, "name" -> group.name.value.value) + private implicit val kibanaAccessShow: Show[KibanaAccess] = Show { case KibanaAccess.RO => "ro" case KibanaAccess.ROStrict => "ro_strict" diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/auth/JwtAuthRule.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/auth/JwtAuthRule.scala index 958a6bff0f..feb00b7ee7 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/auth/JwtAuthRule.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/auth/JwtAuthRule.scala @@ -126,12 +126,16 @@ final class JwtAuthRule(val settings: JwtAuthRule.Settings, groups: Option[ClaimSearchResult[UniqueList[Group]]]): Unit = { (settings.jwt.userClaim, user) match { case (Some(userClaim), Some(u)) => - logger.debug(s"JWT resolved user for claim ${userClaim.name.getPath}: ${u.show}") + logger.debug(s"JWT resolved user for claim ${userClaim.name.rawPath}: ${u.show}") case _ => } - (settings.jwt.groupsClaim, groups) match { - case (Some(groupsClaim), Some(g)) => - logger.debug(s"JWT resolved groups for claim ${groupsClaim.name.getPath}: ${g.show}") + (settings.jwt.groupsConfig, groups) match { + case (Some(groupsConfig), Some(g)) => + val claimsDescription = groupsConfig.namesClaim match { + case Some(namesClaim) => s"claims (id:'${groupsConfig.idsClaim.name.show}',name:'${namesClaim.name.show}')" + case None => s"claim '${groupsConfig.idsClaim.name.show}'" + } + logger.debug(s"JWT resolved groups for $claimsDescription: ${g.show}") case _ => } } @@ -179,7 +183,9 @@ final class JwtAuthRule(val settings: JwtAuthRule.Settings, } private def groupsFrom(payload: Jwt.Payload) = { - settings.jwt.groupsClaim.map(payload.claims.groupsClaim) + settings.jwt.groupsConfig.map(groupsConfig => + payload.claims.groupsClaim(groupsConfig.idsClaim, groupsConfig.namesClaim) + ) } private def handleUserClaimSearchResult[B <: BlockContext : BlockContextUpdater](blockContext: B, diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/auth/RorKbnAuthRule.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/auth/RorKbnAuthRule.scala index b41c3a06c8..e880175cf2 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/auth/RorKbnAuthRule.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/auth/RorKbnAuthRule.scala @@ -35,7 +35,7 @@ import tech.beshu.ror.accesscontrol.request.RequestContextOps._ import tech.beshu.ror.accesscontrol.show.logs._ import tech.beshu.ror.accesscontrol.utils.ClaimsOps.ClaimSearchResult.{Found, NotFound} import tech.beshu.ror.accesscontrol.utils.ClaimsOps._ -import tech.beshu.ror.com.jayway.jsonpath.JsonPath +import tech.beshu.ror.utils.json.JsonPath import tech.beshu.ror.utils.uniquelist.{UniqueList, UniqueNonEmptyList} import scala.util.Try @@ -107,7 +107,7 @@ final class RorKbnAuthRule(val settings: Settings, ( tokenPayload, tokenPayload.claims.userIdClaim(RorKbnAuthRule.userClaimName), - tokenPayload.claims.groupsClaim(RorKbnAuthRule.groupsClaimName), + tokenPayload.claims.groupsClaim(groupIdsClaimName = RorKbnAuthRule.groupIdsClaimName, groupNamesClaimName = None), tokenPayload.claims.headerNameClaim(Header.Name.xUserOrigin) ) } @@ -181,6 +181,6 @@ object RorKbnAuthRule { final case class Defined(groupsLogic: GroupsLogic) extends Groups } - private val userClaimName = Jwt.ClaimName(JsonPath.compile("user")) - private val groupsClaimName = Jwt.ClaimName(JsonPath.compile("groups")) + private val userClaimName = Jwt.ClaimName(JsonPath("user").get) + private val groupIdsClaimName = Jwt.ClaimName(JsonPath("groups").get) } diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/variables/runtime/RuntimeResolvableVariable.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/variables/runtime/RuntimeResolvableVariable.scala index 7dc3969047..7b963974a2 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/variables/runtime/RuntimeResolvableVariable.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/variables/runtime/RuntimeResolvableVariable.scala @@ -31,7 +31,7 @@ import tech.beshu.ror.accesscontrol.show.logs._ import tech.beshu.ror.accesscontrol.utils.ClaimsOps.ClaimSearchResult.{Found, NotFound} import tech.beshu.ror.accesscontrol.utils.ClaimsOps.CustomClaimValue.{CollectionValue, SingleValue} import tech.beshu.ror.accesscontrol.utils.ClaimsOps._ -import tech.beshu.ror.com.jayway.jsonpath.JsonPath +import tech.beshu.ror.utils.json.JsonPath private[runtime] trait RuntimeResolvableVariable[VALUE] { diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/variables/runtime/RuntimeResolvableVariableCreator.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/variables/runtime/RuntimeResolvableVariableCreator.scala index 678c613bd2..ccd8753a70 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/variables/runtime/RuntimeResolvableVariableCreator.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/variables/runtime/RuntimeResolvableVariableCreator.scala @@ -26,16 +26,16 @@ import tech.beshu.ror.accesscontrol.blocks.variables.Tokenizer.Token import tech.beshu.ror.accesscontrol.blocks.variables.Tokenizer.Token.Transformation import tech.beshu.ror.accesscontrol.blocks.variables.runtime.MultiExtractable.SingleExtractableWrapper import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariable.Convertible +import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariableCreator._ import tech.beshu.ror.accesscontrol.blocks.variables.transformation.TransformationCompiler import tech.beshu.ror.accesscontrol.blocks.variables.transformation.TransformationCompiler.CompilationError +import tech.beshu.ror.accesscontrol.blocks.variables.transformation.domain.Function import tech.beshu.ror.accesscontrol.blocks.variables.{Tokenizer, runtime} import tech.beshu.ror.accesscontrol.domain.Header -import tech.beshu.ror.com.jayway.jsonpath.JsonPath -import RuntimeResolvableVariableCreator._ -import tech.beshu.ror.accesscontrol.blocks.variables.transformation.domain.Function +import tech.beshu.ror.utils.json.JsonPath import scala.util.matching.Regex -import scala.util.{Failure, Success, Try} +import scala.util.{Failure, Success} class RuntimeResolvableVariableCreator(transformationCompiler: TransformationCompiler) extends Logging { @@ -136,7 +136,7 @@ class RuntimeResolvableVariableCreator(transformationCompiler: TransformationCom } private def createJwtExtractable(jsonPathStr: String, maybeTransformation: Option[Function], `type`: ExtractableType): Either[CreationError, `type`.TYPE] = { - Try(JsonPath.compile(jsonPathStr)) match { + JsonPath(jsonPathStr) match { case Success(compiledPath) => Right(`type`.createJwtVariableExtractable(compiledPath, maybeTransformation)) case Failure(ex) => diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/domain/security.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/domain/security.scala index b74e8697d8..41949b54d8 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/domain/security.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/domain/security.scala @@ -21,8 +21,8 @@ import cats.implicits._ import eu.timepit.refined.types.string.NonEmptyString import io.jsonwebtoken.Claims import org.apache.logging.log4j.scala.Logging -import tech.beshu.ror.com.jayway.jsonpath.JsonPath import tech.beshu.ror.utils.ScalaOps.StringOps +import tech.beshu.ror.utils.json.JsonPath import java.nio.charset.StandardCharsets.UTF_8 import java.util.Base64 @@ -89,17 +89,7 @@ final case class Token(value: NonEmptyString) final case class AuthorizationToken(value: NonEmptyString) object Jwt { - final case class ClaimName(name: JsonPath) { - - override def equals(other: Any): Boolean = { - other match { - case that: ClaimName => that.name.getPath.equals(this.name.getPath) - case _ => false - } - } - - override def hashCode: Int = name.getPath.hashCode - } + final case class ClaimName(name: JsonPath) final case class Token(value: NonEmptyString) diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/common.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/common.scala index 763f1153a7..9df1d6c844 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/common.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/common.scala @@ -34,7 +34,7 @@ import tech.beshu.ror.accesscontrol.blocks.variables.runtime.{RuntimeMultiResolv import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId import tech.beshu.ror.accesscontrol.domain.Json.ResolvableJsonRepresentation import tech.beshu.ror.accesscontrol.domain.User.UserIdPattern -import tech.beshu.ror.accesscontrol.domain.{Address, ClusterIndexName, GroupIdLike, GroupsLogic, Header, KibanaAccess, KibanaApp, KibanaIndexName, PermittedGroupIds, User} +import tech.beshu.ror.accesscontrol.domain._ import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message @@ -43,10 +43,10 @@ import tech.beshu.ror.accesscontrol.refined._ import tech.beshu.ror.accesscontrol.show.logs._ import tech.beshu.ror.accesscontrol.utils.CirceOps._ import tech.beshu.ror.accesscontrol.utils.SyncDecoderCreator -import tech.beshu.ror.com.jayway.jsonpath.JsonPath import tech.beshu.ror.utils.LoggerOps._ import tech.beshu.ror.utils.ScalaOps._ import tech.beshu.ror.utils.js.JsCompiler +import tech.beshu.ror.utils.json.JsonPath import tech.beshu.ror.utils.uniquelist.UniqueNonEmptyList import java.net.URI @@ -128,7 +128,18 @@ object common extends Logging { DecoderHelpers.decodeStringLikeNonEmpty.map(GroupIdLike.from) implicit val groupIdDecoder: Decoder[GroupId] = - DecoderHelpers.decodeStringLikeNonEmpty.map(GroupId.apply) + DecoderHelpers.decodeStringLikeNonEmpty + .map(GroupId.apply) + .toSyncDecoder + .withError(ValueLevelCreationError(Message(s"Group ID cannot be an empty string"))) + .decoder + + implicit val groupNameDecoder: Decoder[GroupName] = + DecoderHelpers.decodeStringLikeNonEmpty + .map(GroupName.apply) + .toSyncDecoder + .withError(ValueLevelCreationError(Message(s"Group name cannot be an empty string"))) + .decoder implicit val userIdDecoder: Decoder[User.Id] = DecoderHelpers.decodeStringLikeNonEmpty.map(User.Id.apply) @@ -145,7 +156,7 @@ object common extends Logging { implicit val groupIdLikesUniqueNonEmptyListDecoder: Decoder[UniqueNonEmptyList[GroupIdLike]] = SyncDecoderCreator .from(DecoderHelpers.decoderStringLikeOrUniqueNonEmptyList[GroupIdLike]) - .withError(ValueLevelCreationError(Message("Non empty list of group IDs or/and patters is required"))) + .withError(ValueLevelCreationError(Message("Non empty list of group IDs or/and patterns is required"))) .decoder implicit val permittedGroupIdsDecoder: Decoder[PermittedGroupIds] = @@ -282,7 +293,7 @@ object common extends Logging { SyncDecoderCreator .from(Decoder.decodeString) .emapE[JsonPath] { jsonPathStr => - Try(JsonPath.compile(jsonPathStr)) + JsonPath(jsonPathStr) .toEither .left .map { ex => diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ExternalAuthorizationServicesDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ExternalAuthorizationServicesDecoder.scala index 72c49b9c69..f37febd70c 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ExternalAuthorizationServicesDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ExternalAuthorizationServicesDecoder.scala @@ -22,24 +22,20 @@ import com.softwaremill.sttp.Uri import eu.timepit.refined.api.Refined import eu.timepit.refined.numeric.Positive import io.circe.Decoder -import org.apache.logging.log4j.scala.Logging -import tech.beshu.ror.accesscontrol.domain.Header -import tech.beshu.ror.accesscontrol.blocks.definitions.HttpExternalAuthorizationService.SupportedHttpMethod.Get -import tech.beshu.ror.accesscontrol.blocks.definitions.HttpExternalAuthorizationService.{AuthTokenName, AuthTokenSendMethod, QueryParam, SupportedHttpMethod} +import tech.beshu.ror.accesscontrol.blocks.definitions.HttpExternalAuthorizationService.Config._ import tech.beshu.ror.accesscontrol.blocks.definitions._ -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.domain.Header import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.decoders.common.decoderTupleListDecoder +import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, Reason} +import tech.beshu.ror.accesscontrol.factory.decoders.common._ import tech.beshu.ror.accesscontrol.utils.CirceOps._ +import tech.beshu.ror.accesscontrol.utils.{ADecoder, SyncDecoder, SyncDecoderCreator} +import tech.beshu.ror.utils.json.JsonPath import scala.concurrent.duration.FiniteDuration -import tech.beshu.ror.accesscontrol.factory.decoders.common._ -import tech.beshu.ror.accesscontrol.utils.{ADecoder, SyncDecoder, SyncDecoderCreator} -import tech.beshu.ror.com.jayway.jsonpath.JsonPath -object ExternalAuthorizationServicesDecoder extends Logging { +object ExternalAuthorizationServicesDecoder { def instance(httpClientFactory: HttpClientsFactory): ADecoder[Id, Definitions[ExternalAuthorizationService]] = { implicit val serviceDecoder: SyncDecoder[ExternalAuthorizationService] = SyncDecoderCreator @@ -50,6 +46,70 @@ object ExternalAuthorizationServicesDecoder extends Logging { implicit val serviceNameDecoder: Decoder[ExternalAuthorizationService.Name] = DecoderHelpers.decodeStringLikeNonEmpty.map(ExternalAuthorizationService.Name.apply) + private implicit def externalAuthorizationServiceDecoder(implicit httpClientFactory: HttpClientsFactory): Decoder[ExternalAuthorizationService] = { + SyncDecoderCreator + .instance { c => + for { + name <- c.downField("name").as[ExternalAuthorizationService.Name] + url <- c.downFields("groups_endpoint", "url").as[Uri] + authTokenName <- c.downField("auth_token_name").as[AuthTokenName] + sendUsing <- c.downField("auth_token_passed_as").as[AuthTokenSendMethod] + httpMethod <- c.downField("http_method").as[Option[SupportedHttpMethod]] + groupsConfig <- c.as[GroupsConfig](groupsConfigDecoder(name)) + defaultQueryParams <- c.downField("default_query_parameters").as[Option[Set[QueryParam]]] + defaultHeaders <- c.downField("default_headers").as[Option[Set[Header]]] + cacheTtl <- c.downFields("cache_ttl_in_sec", "cache_ttl").as[Option[FiniteDuration Refined Positive]] + httpClientConfig <- c.as[ValidatedHttpClientConfig].map(_.config) + } yield { + val httpClient = httpClientFactory.create(httpClientConfig) + val externalAuthService: ExternalAuthorizationService = + new HttpExternalAuthorizationService( + id = name, + serviceTimeout = httpClientConfig.requestTimeout, + config = HttpExternalAuthorizationService.Config( + uri = url, + method = httpMethod.getOrElse(defaults.httpMethod), + tokenName = authTokenName, + groupsConfig = groupsConfig, + authTokenSendMethod = sendUsing, + defaultHeaders = defaultHeaders.getOrElse(defaults.headers), + defaultQueryParams = defaultQueryParams.getOrElse(defaults.queryParams) + ), + httpClient = httpClient + ) + cacheTtl.foldLeft(externalAuthService) { + case (cacheableAuthService, ttl) => + new CacheableExternalAuthorizationServiceDecorator(cacheableAuthService, ttl) + } + } + } + .mapError(DefinitionsLevelCreationError.apply) + .decoder + } + + private def groupsConfigDecoder(name: ExternalAuthorizationService.Name): Decoder[GroupsConfig] = { + SyncDecoderCreator + .instance { c => + for { + groupIdsConfig <- c.downField("response_group_ids_json_path").as[Option[JsonPath]] + .map(_.map(GroupsConfig.GroupIdsConfig.apply)) + groupIdsDeprecatedConfig <- c.downField("response_groups_json_path").as[Option[JsonPath]] + .map(_.map(GroupsConfig.GroupIdsConfig.apply)) + groupNamesConfig <- + c.downField("response_group_names_json_path").as[Option[JsonPath]] + .map(_.map(GroupsConfig.GroupNamesConfig.apply)) + } yield (groupIdsConfig, groupIdsDeprecatedConfig, groupNamesConfig) + } + .emapE { + case (Some(groupIdsConfig), None, groupNamesConfig) => Right(GroupsConfig(groupIdsConfig, groupNamesConfig)) + case (None, Some(groupIdsDeprecatedConfig), _) => Right(GroupsConfig(groupIdsDeprecatedConfig, None)) + case (None, None, _) => Left(DefinitionsLevelCreationError(Reason.Message(s"External authorization service '${name.show}' configuration is missing the 'response_group_ids_json_path' attribute"))) + case (Some(_), Some(_), _) => Left(DefinitionsLevelCreationError(Reason.Message(s"External authorization service '${name.show}' configuration cannot have the 'response_groups_json_path' and 'response_group_ids_json_path' attributes defined at the same time"))) + } + .mapError(DefinitionsLevelCreationError.apply) + .decoder + } + private implicit val authTokenNameDecoder: Decoder[AuthTokenName] = DecoderHelpers.decodeStringLikeNonEmpty.map(AuthTokenName.apply) @@ -57,20 +117,20 @@ object ExternalAuthorizationServicesDecoder extends Logging { SyncDecoderCreator .from(Decoder.decodeString) .emapE[AuthTokenSendMethod] { - case "HEADER" => Right(AuthTokenSendMethod.UsingHeader) - case "QUERY_PARAM" => Right(AuthTokenSendMethod.UsingQueryParam) - case unknown => Left(DefinitionsLevelCreationError(Message(s"Unknown value '$unknown' of 'auth_token_passed_as' attribute"))) - } + case "HEADER" => Right(AuthTokenSendMethod.UsingHeader) + case "QUERY_PARAM" => Right(AuthTokenSendMethod.UsingQueryParam) + case unknown => Left(DefinitionsLevelCreationError(Message(s"Unknown value '$unknown' of 'auth_token_passed_as' attribute. Supported: 'HEADER', 'QUERY_PARAM'"))) + } .decoder private implicit val supportedHttpMethodDecoder: Decoder[SupportedHttpMethod] = SyncDecoderCreator .from(Decoder.decodeString) .emapE[SupportedHttpMethod] { - case "POST" | "post" => Right(SupportedHttpMethod.Post) - case "GET" | "resolve" => Right(Get) - case unknown => Left(DefinitionsLevelCreationError(Message(s"Unknown value '$unknown' of 'http_method' attribute"))) - } + case "POST" | "post" => Right(SupportedHttpMethod.Post) + case "GET" | "get" => Right(SupportedHttpMethod.Get) + case unknown => Left(DefinitionsLevelCreationError(Message(s"Unknown value '$unknown' of 'http_method' attribute. Supported: 'GET', 'POST'"))) + } .decoder private implicit val headerSetDecoder: Decoder[Set[Header]] = @@ -79,62 +139,35 @@ object ExternalAuthorizationServicesDecoder extends Logging { private implicit val queryParamSetDecoder: Decoder[Set[QueryParam]] = decoderTupleListDecoder.map(_.map { case (fst, snd) => QueryParam(fst, snd) }.toSet) - private implicit def externalAuthorizationServiceDecoder(implicit httpClientFactory: HttpClientsFactory): Decoder[ExternalAuthorizationService] = { + private implicit val validatedHttpClientConfigDecoder: Decoder[ValidatedHttpClientConfig] = { SyncDecoderCreator .instance { c => for { - name <- c.downField("name").as[ExternalAuthorizationService.Name] - url <- c.downFields("groups_endpoint", "url").as[Uri] - authTokenName <- c.downField("auth_token_name").as[AuthTokenName] - sendUsing <- c.downField("auth_token_passed_as").as[AuthTokenSendMethod] - httpMethod <- c.downField("http_method").as[Option[SupportedHttpMethod]] - groupsJsonPath <- c.downField("response_groups_json_path").as[JsonPath] - defaultQueryParams <- c.downField("default_query_parameters").as[Option[Set[QueryParam]]] - defaultHeaders <- c.downField("default_headers").as[Option[Set[Header]]] - cacheTtl <- c.downFields("cache_ttl_in_sec", "cache_ttl").as[Option[FiniteDuration Refined Positive]] validate <- c.downField("validate").as[Option[Boolean]] httpClientConfig <- c.downField("http_connection_settings").as[Option[HttpClientsFactory.Config]] - } yield (name, url, authTokenName, sendUsing, httpMethod, groupsJsonPath, defaultQueryParams, defaultHeaders, cacheTtl, validate, httpClientConfig) + } yield (validate, httpClientConfig) } - .emapE { case (name, url, authTokenName, sendUsing, httpMethod, groupsJsonPath, defaultQueryParams, defaultHeaders, cacheTtl, validateOpt, httpClientConfigOpt) => - val httpClientConfig = (validateOpt, httpClientConfigOpt) match { - case (Some(_), Some(_)) => - Left(CoreCreationError.RulesLevelCreationError(Message("If 'http_connection_settings' are used, 'validate' should be placed in that section"))) - case (Some(validate), None) => - Right(HttpClientsFactory.Config.default.copy(validate = validate)) - case (None, Some(config)) => - Right(config) - case (None, None) => - Right(HttpClientsFactory.Config.default) - } - httpClientConfig.map { config => - val httpClient = httpClientFactory.create(config) - val externalAuthService: ExternalAuthorizationService = - new HttpExternalAuthorizationService( - name, - url, - httpMethod.getOrElse(defaults.httpMethod), - authTokenName, - groupsJsonPath, - sendUsing, - defaultHeaders.getOrElse(Set.empty), - defaultQueryParams.getOrElse(Set.empty), - config.requestTimeout, - httpClient - ) - cacheTtl.foldLeft(externalAuthService) { - case (cacheableAuthService, ttl) => - new CacheableExternalAuthorizationServiceDecorator(cacheableAuthService, ttl) - } - } + .emapE { + case (Some(_), Some(_)) => + Left(DefinitionsLevelCreationError(Message("If 'http_connection_settings' are used, 'validate' should be placed in that section"))) + case (Some(validate), None) => + Right(HttpClientsFactory.Config.default.copy(validate = validate)) + case (None, Some(config)) => + Right(config) + case (None, None) => + Right(HttpClientsFactory.Config.default) } + .map(ValidatedHttpClientConfig.apply) .mapError(DefinitionsLevelCreationError.apply) .decoder } + private final case class ValidatedHttpClientConfig(config: HttpClientsFactory.Config) + private object defaults { - val httpMethod: SupportedHttpMethod = Get - val validate = true + val httpMethod: SupportedHttpMethod = SupportedHttpMethod.Get + val headers: Set[Header] = Set.empty + val queryParams: Set[QueryParam] = Set.empty } } diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/JwtDefinitionsDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/JwtDefinitionsDecoder.scala index a4bb6e8190..3b28f8e8eb 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/JwtDefinitionsDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/JwtDefinitionsDecoder.scala @@ -18,7 +18,7 @@ package tech.beshu.ror.accesscontrol.factory.decoders.definitions import io.circe.{Decoder, HCursor, Json} import tech.beshu.ror.accesscontrol.domain.{AuthorizationTokenDef, Header, Jwt} -import tech.beshu.ror.accesscontrol.blocks.definitions.JwtDef.{Name, SignatureCheckMethod} +import tech.beshu.ror.accesscontrol.blocks.definitions.JwtDef.{GroupsConfig, Name, SignatureCheckMethod} import tech.beshu.ror.accesscontrol.blocks.definitions.{ExternalAuthenticationService, JwtDef} import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError @@ -43,8 +43,6 @@ object JwtDefinitionsDecoder { implicit val jwtDefNameDecoder: Decoder[Name] = DecoderHelpers.decodeStringLikeNonEmpty.map(Name.apply) - private implicit val claimDecoder: Decoder[Jwt.ClaimName] = jsonPathDecoder.map(Jwt.ClaimName.apply) - private def jwtDefDecoder(implicit httpClientFactory: HttpClientsFactory, variableCreator: RuntimeResolvableVariableCreator): Decoder[JwtDef] = { SyncDecoderCreator @@ -55,16 +53,16 @@ object JwtDefinitionsDecoder { headerName <- c.downField("header_name").as[Option[Header.Name]] authTokenPrefix <- c.downField("header_prefix").as[Option[String]] userClaim <- c.downField("user_claim").as[Option[Jwt.ClaimName]] - groupsClaim <- c.downFields("roles_claim", "groups_claim").as[Option[Jwt.ClaimName]] + groupsConfig <- c.as[Option[GroupsConfig]] } yield JwtDef( - name, - AuthorizationTokenDef( + id = name, + authorizationTokenDef = AuthorizationTokenDef( headerName.getOrElse(Header.Name.authorization), authTokenPrefix.getOrElse("Bearer ") ), - checkMethod, - userClaim, - groupsClaim + checkMethod = checkMethod, + userClaim = userClaim, + groupsConfig = groupsConfig ) } .mapError(DefinitionsLevelCreationError.apply) @@ -135,4 +133,15 @@ object JwtDefinitionsDecoder { } } yield checkMethod } + + private implicit val claimDecoder: Decoder[Jwt.ClaimName] = jsonPathDecoder.map(Jwt.ClaimName.apply) + + private implicit val groupsConfigDecoder: Decoder[Option[GroupsConfig]] = Decoder.instance { c => + for { + groupIdsClaim + <- c.downFields("roles_claim", "groups_claim", "group_ids_claim").as[Option[Jwt.ClaimName]] + groupNamesClaim + <- c.downFields("group_names_claim").as[Option[Jwt.ClaimName]] + } yield groupIdsClaim.map(GroupsConfig(_, groupNamesClaim)) + } } \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/UsersDefinitionsDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/UsersDefinitionsDecoder.scala index 60ce49bedb..ab16b23dbf 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/UsersDefinitionsDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/UsersDefinitionsDecoder.scala @@ -17,6 +17,7 @@ package tech.beshu.ror.accesscontrol.factory.decoders.definitions import cats.Id +import cats.data.NonEmptyList import cats.implicits._ import io.circe.{ACursor, Decoder, HCursor, Json} import tech.beshu.ror.accesscontrol.blocks.definitions.UserDef.Mode.WithGroupsMapping.Auth @@ -29,7 +30,7 @@ import tech.beshu.ror.accesscontrol.blocks.rules.Rule.{AuthRule, AuthenticationR import tech.beshu.ror.accesscontrol.blocks.rules.auth.GroupsOrRule import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId import tech.beshu.ror.accesscontrol.domain.User.UserIdPattern -import tech.beshu.ror.accesscontrol.domain.{Group, GroupIdLike, UserIdPatterns} +import tech.beshu.ror.accesscontrol.domain.{Group, GroupIdLike, GroupName, UserIdPatterns} import tech.beshu.ror.accesscontrol.factory.GlobalSettings import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, ValueLevelCreationError} @@ -44,6 +45,8 @@ import tech.beshu.ror.utils.uniquelist.UniqueNonEmptyList object UsersDefinitionsDecoder { + import tech.beshu.ror.accesscontrol.factory.decoders.definitions.UsersDefinitionsDecoder.GroupsDecoder._ + def instance(authenticationServiceDefinitions: Definitions[ExternalAuthenticationService], authorizationServiceDefinitions: Definitions[ExternalAuthorizationService], authProxyDefinitions: Definitions[ProxyAuth], @@ -59,7 +62,7 @@ object UsersDefinitionsDecoder { val usernameKey = "username" val groupsKey = "groups" for { - usernamePatterns <- c.downField(usernameKey).as[UserIdPatterns] + usernamePatterns <- c.downFieldAs[UserIdPatterns](usernameKey) rules <- { val rulesDecoder = userDefRulesDecoder( usernamePatterns, @@ -88,44 +91,6 @@ object UsersDefinitionsDecoder { private implicit val userIdPatternsDecoder: Decoder[UserIdPatterns] = Decoder[UniqueNonEmptyList[UserIdPattern]].map(UserIdPatterns.apply) - private implicit val localGroupToExternalGroupsMappingDecoder: Decoder[GroupMappings.Advanced.Mapping] = - Decoder - .instance { c => - c.keys.map(_.toList) match { - case Some(key :: Nil) => - for { - localGroup <- Decoder[GroupId].tryDecode(HCursor.fromJson(Json.fromString(key))).map(Group.from) - externalGroups <- c.downField(key).as[UniqueNonEmptyList[GroupIdLike]] - } yield { - GroupMappings.Advanced.Mapping(localGroup, externalGroups) - } - case Some(Nil) | None => - failure(Message(s"Groups mapping should have exactly one YAML key")) - case Some(keys) => - failure(Message(s"Groups mapping should have exactly one YAML key, but several were defined: [${keys.mkString(",")}]")) - } - } - - private val simpleGroupMappingsDecoder: Decoder[GroupMappings] = - groupIdsUniqueNonEmptyListDecoder.map( - groupIds => UniqueNonEmptyList.unsafeFromIterable(groupIds.toList.map(Group.from)) - ).map(GroupMappings.Simple.apply) - - private val advancedGroupMappingsDecoder: Decoder[GroupMappings] = - Decoder[List[GroupMappings.Advanced.Mapping]] - .toSyncDecoder - .emapE { list => - UniqueNonEmptyList.fromIterable(list) match { - case Some(mappings) => Right(GroupMappings.Advanced(mappings)) - case None => Left(ValueLevelCreationError(Message("Non empty list of groups or groups mappings are required"))) - } - } - .map[GroupMappings](identity) - .decoder - - private implicit val groupMappingsDecoder: Decoder[GroupMappings] = - advancedGroupMappingsDecoder or simpleGroupMappingsDecoder - private def userDefRulesDecoder(usernamePatterns: UserIdPatterns, authenticationServiceDefinitions: Definitions[ExternalAuthenticationService], authorizationServiceDefinitions: Definitions[ExternalAuthorizationService], @@ -202,8 +167,8 @@ object UsersDefinitionsDecoder { case r: AuthRule => Decoder[GroupMappings].map(UserDef.Mode.WithGroupsMapping(Auth.SingleRule(r), _)) case r: AuthenticationRule => - Decoder[UniqueNonEmptyList[GroupId]] - .map(groupIds => UniqueNonEmptyList.unsafeFromIterable(groupIds.toList.map(Group.from))) + GroupsDecoder + .groupsDecoder .map(UserDef.Mode.WithoutGroupsMapping(r, _)) case other => failed(DefinitionsLevelCreationError(Message(s"Cannot use '${other.name.show}' rule in users definition section"))) @@ -220,10 +185,13 @@ object UsersDefinitionsDecoder { case (r1: AuthorizationRule, r2: AuthRule) => errorFor(r2, r1) case (r1: AuthenticationRule, r2: AuthorizationRule) => - groupMappingsDecoder.map(a => - UserDef.Mode.WithGroupsMapping(Auth.SeparateRules(r1, r2), a)) + Decoder[GroupMappings].map(mappings => + UserDef.Mode.WithGroupsMapping(Auth.SeparateRules(r1, r2), mappings) + ) case (r1: AuthorizationRule, r2: AuthenticationRule) => - Decoder[GroupMappings].map(UserDef.Mode.WithGroupsMapping(Auth.SeparateRules(r2, r1), _)) + Decoder[GroupMappings].map(mappings => + UserDef.Mode.WithGroupsMapping(Auth.SeparateRules(r2, r1), mappings) + ) case (r1, r2) => errorFor(r1, r2) } @@ -257,4 +225,133 @@ object UsersDefinitionsDecoder { private def failure(msg: Message) = Left(decodingFailure(msg)) private def decodingFailure(msg: Message) = DecodingFailureOps.fromError(DefinitionsLevelCreationError(msg)) + + private object GroupsDecoder { + + private object GroupMappingKeys { + val id: String = "id" + val name: String = "name" + val localGroup: String = "local_group" + val externalGroups: String = "external_group_ids" + + val simpleMappingRequiredKeys: Set[String] = Set(localGroup) + val advancedMappingRequiredKeys: Set[String] = Set(localGroup, externalGroups) + } + + implicit lazy val groupsDecoder: Decoder[UniqueNonEmptyList[Group]] = { + groupsSimpleDecoder.or(structuredGroupsDecoder) + } + + // supported formats for 'groups' key: + // * array of strings (local group IDs) - simple groups mapping + // * array of objects (object with one key as local group ID - value is array of strings (external group IDs)) - advanced group mapping + // * array of objects (object with 'id' and 'name' keys) - simple groups mapping + // * array of objects (object with `local_group` and 'external_group_ids' keys) -> advanced group mapping + implicit lazy val groupMappingsDecoder: Decoder[GroupMappings] = Decoder.instance { c => + for { + mappingsJsons <- c.values + .toRight("Unknown format of `groups`") + .flatMap(values => NonEmptyList.fromList(values.toList).toRight("Non empty list of group mappings is required")) + .leftMap(msg => decodingFailure(Message(msg))) + mappingsDecoder = mappingsJsons match { + case groupMappings if haveSimpleFormatWithGroupIds(groupMappings) => + groupsSimpleDecoder + .map(GroupMappings.Simple) + .widen[GroupMappings] + case groupMappings if haveAdvancedFormatWithStructuredGroups(groupMappings) => + advancedGroupMappingsDecoder(structuredLocalGroupToExternalGroupsMappingDecoder) + .widen[GroupMappings] + case groupMappings if haveSimpleFormatWithStructuredGroups(groupMappings) => + structuredGroupsDecoder + .map(GroupMappings.Simple) + .widen[GroupMappings] + case _ => + advancedGroupMappingsDecoder(localGroupToExternalGroupsMappingDecoder) + .widen[GroupMappings] + } + mappings <- mappingsDecoder.apply(c) + } yield mappings + } + + private def haveSimpleFormatWithGroupIds(groupMappings: NonEmptyList[Json]): Boolean = { + groupMappings.forall(_.isString) + } + + private def haveSimpleFormatWithStructuredGroups(groupMappings: NonEmptyList[Json]): Boolean = { + groupMappings.forall { mapping => + objectContainsKeys(mapping, GroupMappingKeys.simpleMappingRequiredKeys) + } + } + + private def haveAdvancedFormatWithStructuredGroups(groupMappings: NonEmptyList[Json]): Boolean = { + groupMappings.forall { mapping => + objectContainsKeys(mapping, GroupMappingKeys.advancedMappingRequiredKeys) + } + } + + private def objectContainsKeys(json: Json, keys: Set[String]): Boolean = { + json.isObject && json.hcursor.keys.forall(objectKeys => keys.subsetOf(objectKeys.toSet)) + } + + private def advancedGroupMappingsDecoder(implicit mappingDecoder: Decoder[GroupMappings.Advanced.Mapping]): Decoder[GroupMappings.Advanced] = + Decoder[List[GroupMappings.Advanced.Mapping]] + .toSyncDecoder + .emapE { list => + UniqueNonEmptyList.fromIterable(list) match { + case Some(mappings) => Right(GroupMappings.Advanced(mappings)) + case None => Left(ValueLevelCreationError(Message("Non empty list of groups mappings is required"))) + } + } + .decoder + + private val structuredLocalGroupToExternalGroupsMappingDecoder: Decoder[GroupMappings.Advanced.Mapping] = + Decoder + .instance { c => + for { + id <- c.downField(GroupMappingKeys.localGroup).downFieldAs[GroupId](GroupMappingKeys.id) + name <- c.downField(GroupMappingKeys.localGroup).downFieldAs[GroupName](GroupMappingKeys.name) + externalGroupIds <- c.downFieldAs[UniqueNonEmptyList[GroupIdLike]](GroupMappingKeys.externalGroups) + } yield { + val localGroup = Group(id, name) + GroupMappings.Advanced.Mapping(localGroup, externalGroupIds) + } + } + + private val localGroupToExternalGroupsMappingDecoder: Decoder[GroupMappings.Advanced.Mapping] = + Decoder + .instance { c => + c.keys.map(_.toList) match { + case Some(key :: Nil) => + for { + localGroup <- Decoder[GroupId].tryDecode(HCursor.fromJson(Json.fromString(key))).map(Group.from) + externalGroups <- c.downFieldAs[UniqueNonEmptyList[GroupIdLike]](key) + } yield { + GroupMappings.Advanced.Mapping(localGroup, externalGroups) + } + case Some(Nil) | None => + failure(Message(s"Groups mapping should have exactly one YAML key")) + case Some(keys) => + failure(Message(s"Groups mapping should have exactly one YAML key, but several were defined: [${keys.mkString(",")}]")) + } + } + + private val structuredGroupsDecoder: Decoder[UniqueNonEmptyList[Group]] = { + implicit val groupDecoder: Decoder[Group] = Decoder.instance { c => + for { + id <- c.downFieldAs[GroupId](GroupMappingKeys.id) + name <- c.downFieldAs[GroupName](GroupMappingKeys.name) + } yield Group(id, name) + } + + SyncDecoderCreator + .from(DecoderHelpers.decodeUniqueNonEmptyList[Group]) + .withError(ValueLevelCreationError(Message("Non empty list of groups is required"))) + .decoder + } + + private val groupsSimpleDecoder: Decoder[UniqueNonEmptyList[Group]] = { + Decoder[UniqueNonEmptyList[GroupId]] + .map(groupIds => UniqueNonEmptyList.unsafeFromIterable(groupIds.toList.map(Group.from))) + } + } } diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/ops.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/ops.scala index 7058b2ca7d..fc0d856adf 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/ops.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/ops.scala @@ -58,10 +58,10 @@ import tech.beshu.ror.accesscontrol.domain._ import tech.beshu.ror.accesscontrol.factory.BlockValidator.BlockValidationError import tech.beshu.ror.accesscontrol.factory.BlockValidator.BlockValidationError.{KibanaRuleTogetherWith, KibanaUserDataRuleTogetherWith} import tech.beshu.ror.accesscontrol.header.{FromHeaderValue, ToHeaderValue} -import tech.beshu.ror.com.jayway.jsonpath.JsonPath import tech.beshu.ror.providers.EnvVarProvider.EnvVarName import tech.beshu.ror.providers.PropertiesProvider.PropName import tech.beshu.ror.utils.ScalaOps._ +import tech.beshu.ror.utils.json.JsonPath import tech.beshu.ror.utils.uniquelist.UniqueNonEmptyList import java.util.Base64 @@ -164,7 +164,7 @@ object show { } implicit val ipShow: Show[Ip] = Show.show(_.value.toString()) implicit val methodShow: Show[Method] = Show.show(_.m) - implicit val jsonPathShow: Show[JsonPath] = Show.show(_.getPath) + implicit val jsonPathShow: Show[JsonPath] = Show.show(_.rawPath) implicit val uriShow: Show[Uri] = Show.show(_.toJavaUri.toString()) implicit val lemonUriShow: Show[LemonUri] = Show.show(_.toString()) implicit val headerNameShow: Show[Header.Name] = Show.show(_.value.value) @@ -197,7 +197,7 @@ object show { implicit val aliasPlaceholderShow: Show[AliasPlaceholder] = Show.show(_.alias.show) implicit val externalAuthenticationServiceNameShow: Show[ExternalAuthenticationService.Name] = Show.show(_.value.value) implicit val groupIdShow: Show[GroupId] = Show.show(_.value.value) - implicit val groupShow: Show[Group] = Show.show(_.id.show) + implicit val groupShow: Show[Group] = Show.show(group => s"(id=${group.id.show},name=${group.name.value.value})") implicit val tokenShow: Show[AuthorizationToken] = Show.show(_.value.value) implicit val jwtTokenShow: Show[Jwt.Token] = Show.show(_.value.value) implicit val uriPathShow: Show[UriPath] = Show.show(_.value.value) @@ -219,7 +219,7 @@ object show { Show.show { bc => (showOption("user", bc.userMetadata.loggedUser) :: showOption("group", bc.userMetadata.currentGroupId) :: - showNamedIterable("av_groups", bc.userMetadata.availableGroups) :: + showNamedIterable("av_groups", bc.userMetadata.availableGroups.toList.map(_.id)) :: showNamedIterable("indices", bc.indices) :: showOption("kibana_idx", bc.userMetadata.kibanaIndex) :: showOption("fls", bc.fieldLevelSecurity) :: @@ -253,7 +253,7 @@ object show { implicit val userMetadataShow: Show[UserMetadata] = Show.show { u => (showOption("user", u.loggedUser) :: showOption("curr_group", u.currentGroupId) :: - showNamedIterable("av_groups", u.availableGroups) :: + showNamedIterable("av_groups", u.availableGroups.toList.map(_.id)) :: showOption("kibana_idx", u.kibanaIndex) :: showNamedIterable("hidden_apps", u.hiddenKibanaApps) :: showNamedIterable("allowed_api_paths", u.allowedKibanaApiPaths) :: diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/CirceOps.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/CirceOps.scala index 0160d9d870..005859ce86 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/CirceOps.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/CirceOps.scala @@ -252,6 +252,27 @@ object CirceOps { def aclCreationError: Option[CoreCreationError] = parse(decodingFailure.message).flatMap(Decoder[CoreCreationError].decodeJson).toOption + + def modifyError(updateErrorMessage: String => String): DecodingFailure = { + aclCreationError + .map { error => + val updatedReason = error.reason match { + case Message(value) => Message(updateErrorMessage(value)) + case MalformedValue(value) => MalformedValue(updateErrorMessage(value)) + } + val updatedError = error match { + case e: CoreCreationError.GeneralReadonlyrestSettingsError => e.copy(updatedReason) + case e: CoreCreationError.DefinitionsLevelCreationError => e.copy(updatedReason) + case e: CoreCreationError.BlocksLevelCreationError => e.copy(updatedReason) + case e: CoreCreationError.RulesLevelCreationError => e.copy(updatedReason) + case e: ValueLevelCreationError => e.copy(updatedReason) + case e: CoreCreationError.AuditingSettingsCreationError => e.copy(updatedReason) + } + decodingFailure.withMessage(stringify(updatedError)) + } + .getOrElse(decodingFailure.withMessage(updateErrorMessage(decodingFailure.message))) + } + } object DecodingFailureOps { @@ -304,6 +325,12 @@ object CirceOps { downFields(name).asWithError[Option[NonEmptyString]](s"Field $name cannot be empty") } + def downFieldAs[T: Decoder](name: String): Decoder.Result[T] = { + value.downField(name).as[T].adaptError { + case error: DecodingFailure => error.modifyError(errorMessage => s"Error for field '$name': $errorMessage") + } + } + def withoutKeys(keys: Set[String]): ACursor = { value.withFocus(_.mapObject(_.filterKeys(key => !keys.contains(key)))) } diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/ClaimsOps.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/ClaimsOps.scala index 505e1ff67f..3fd3e6d133 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/ClaimsOps.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/ClaimsOps.scala @@ -16,21 +16,22 @@ */ package tech.beshu.ror.accesscontrol.utils -import cats.implicits._ import cats.Show import cats.data.NonEmptyList +import cats.implicits._ import eu.timepit.refined.types.string.NonEmptyString import io.jsonwebtoken.Claims import org.apache.logging.log4j.scala.Logging import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId -import tech.beshu.ror.accesscontrol.domain.{Group, Header, Jwt, User} +import tech.beshu.ror.accesscontrol.domain._ +import tech.beshu.ror.accesscontrol.show.logs._ import tech.beshu.ror.accesscontrol.utils.ClaimsOps.ClaimSearchResult._ import tech.beshu.ror.accesscontrol.utils.ClaimsOps.{ClaimSearchResult, CustomClaimValue} import tech.beshu.ror.utils.uniquelist.UniqueList import scala.jdk.CollectionConverters._ import scala.language.implicitConversions -import scala.util.Try +import scala.util.{Success, Try} class ClaimsOps(val claims: Claims) extends Logging { @@ -42,8 +43,30 @@ class ClaimsOps(val claims: Claims) extends Logging { } } + def customClaim(claimName: Jwt.ClaimName): ClaimSearchResult[CustomClaimValue] = { + claimName.name.read[Any](claims) + .map { + case value: String => + Found(CustomClaimValue.SingleValue(value)) + case collection: java.util.Collection[_] => + val items = collection.asScala + .collect { + case value: String => value + case value: Long => value.toString + } + .toList + NonEmptyList.fromList(items) match { + case Some(nel) => Found(CustomClaimValue.CollectionValue(nel)) + case None => NotFound + } + case _ => + NotFound + } + .fold(_ => NotFound, identity) + } + def userIdClaim(claimName: Jwt.ClaimName): ClaimSearchResult[User.Id] = { - Try(claimName.name.read[Any](claims)) + claimName.name.read[Any](claims) .map { case value: String => NonEmptyString.from(value) match { @@ -55,52 +78,96 @@ class ClaimsOps(val claims: Claims) extends Logging { .fold(_ => NotFound, identity) } - def groupsClaim(claimName: Jwt.ClaimName): ClaimSearchResult[UniqueList[Group]] = { - Try(claimName.name.read[Any](claims)) - .map { - case value: String => - Found(UniqueList.fromIterable((value :: Nil).flatMap(toGroup))) - case collection: java.util.Collection[_] => - Found { - UniqueList.fromIterable { - collection.asScala - .collect { - case value: String => value - case value: Long => value.toString - } - .flatMap(toGroup) - } + def groupsClaim(groupIdsClaimName: Jwt.ClaimName, + groupNamesClaimName: Option[Jwt.ClaimName]): ClaimSearchResult[UniqueList[Group]] = { + + (for { + groupIds <- readGroupIds(groupIdsClaimName) + groupNames <- readGroupNames(groupNamesClaimName) + searchResult = groupIds match { + case Found(ids) => ClaimSearchResult.Found { + groupNames match { + case Found(names) if names.size == ids.size => + val idsWithNames = ids.zip(names) + createGroupsFrom(idsWithNames = idsWithNames) + case Found(names) => + logger.debug( + s"Group names array extracted from the JWT at json path '${groupNamesClaimName.map(_.name.show).getOrElse("")}' has different size [size=${names.size}] than " + + s"the group IDs array extracted from the JWT at json path '${groupIdsClaimName.name.show}' [size=${ids.size}]. " + + s"Both array's size has to be equal. Only group IDs will be used for further processing.." + ) + createGroupsFromIds(ids) + case ClaimSearchResult.NotFound => + createGroupsFromIds(ids) } - case _ => - NotFound + } + case ClaimSearchResult.NotFound => + ClaimSearchResult.NotFound } + } yield searchResult) .fold(_ => NotFound, identity) } - def customClaim(claimName: Jwt.ClaimName): ClaimSearchResult[CustomClaimValue] = { - Try(claimName.name.read[Any](claims)) + private def readGroupIds(claimName: Jwt.ClaimName) = { + readStringLikeOrIterable(claimName) + } + + private def readGroupNames(claimName: Option[Jwt.ClaimName]) = { + claimName match { + case Some(groupNamesClaimName) => + readStringLikeOrIterable(groupNamesClaimName) + .recover { + case _: Throwable => NotFound + } + case None => + Success(ClaimSearchResult.NotFound) + } + } + + private def readStringLikeOrIterable(claimName: Jwt.ClaimName): Try[ClaimSearchResult[Iterable[Any]]] = { + claimName.name.read[Any](claims) .map { case value: String => - Found(CustomClaimValue.SingleValue(value)) + Found(List(value)) case collection: java.util.Collection[_] => - val items = collection.asScala - .collect { - case value: String => value - case value: Long => value.toString - } - .toList - NonEmptyList.fromList(items) match { - case Some(nel) => Found(CustomClaimValue.CollectionValue(nel)) - case None => NotFound - } + Found(collection.asScala) case _ => NotFound } - .fold(_ => NotFound, identity) } - private def toGroup(value: String) = { - NonEmptyString.unapply(value).map(GroupId.apply).map(Group.from) + private def createGroupsFromIds(ids: Iterable[Any]): UniqueList[Group] = UniqueList.fromIterable { + ids + .flatMap(nonEmptyStringFrom) + .map(GroupId.apply) + .map(Group.from) + } + + private def createGroupsFrom(idsWithNames: Iterable[(Any, Any)]): UniqueList[Group] = UniqueList.fromIterable { + idsWithNames + .flatMap { case (id, name) => + nonEmptyStringFrom(id) + .map(GroupId.apply) + .map { groupId => + val groupName = groupNameFrom(name, groupId) + Group(groupId, groupName) + } + } + } + + private def groupNameFrom(name: Any, groupId: GroupId) = { + nonEmptyStringFrom(name) + .map(GroupName.apply) + .getOrElse { + logger.debug(s"Unable to create a group name from '$name'. The group ID '${groupId.show}' will be used as a group name for further processing..") + GroupName.from(groupId) + } + } + + private val nonEmptyStringFrom: Any => Option[NonEmptyString] = { + case value: String => NonEmptyString.unapply(value) + case value: Long => NonEmptyString.unapply(value.toString) + case _ => None } } diff --git a/core/src/main/scala/tech/beshu/ror/api/AuthMockApi.scala b/core/src/main/scala/tech/beshu/ror/api/AuthMockApi.scala index 8afdce6d86..b97a4d8f12 100644 --- a/core/src/main/scala/tech/beshu/ror/api/AuthMockApi.scala +++ b/core/src/main/scala/tech/beshu/ror/api/AuthMockApi.scala @@ -20,7 +20,8 @@ import cats.data.EitherT import cats.implicits._ import cats.{Eq, Show} import eu.timepit.refined.types.string.NonEmptyString -import io.circe.{Decoder, DecodingFailure} +import io.circe._ +import io.circe.syntax._ import monix.eval.Task import org.apache.logging.log4j.scala.Logging import tech.beshu.ror.RequestId @@ -29,9 +30,7 @@ import tech.beshu.ror.accesscontrol.blocks.definitions.{ExternalAuthenticationSe import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider.{ExternalAuthenticationServiceMock, ExternalAuthorizationServiceMock, LdapServiceMock} import tech.beshu.ror.accesscontrol.blocks.mocks.{AuthServicesMocks, MocksProvider} import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId -import tech.beshu.ror.accesscontrol.domain.{Group, User} -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService._ +import tech.beshu.ror.accesscontrol.domain.{Group, GroupName, User} import tech.beshu.ror.boot.RorInstance.{IndexConfigUpdateError, TestConfig} import tech.beshu.ror.boot.{RorInstance, RorSchedulers} import tech.beshu.ror.configuration.RorConfig @@ -40,8 +39,10 @@ import tech.beshu.ror.utils.CirceOps.CirceErrorOps class AuthMockApi(rorInstance: RorInstance) extends Logging { + import AuthMockApi.AuthMockResponse._ + import AuthMockApi.AuthMockService._ import AuthMockApi.Utils._ - import AuthMockApi.Utils.decoders._ + import AuthMockApi.Utils.codecs._ import AuthMockApi._ def call(request: AuthMockRequest) @@ -182,7 +183,7 @@ object AuthMockApi { } sealed trait AuthMockResponse - object AuthMockResponse { + private[AuthMockApi] object AuthMockResponse { sealed trait ProvideAuthMock extends AuthMockResponse object ProvideAuthMock { final case class CurrentAuthMocks(services: List[AuthMockService]) extends ProvideAuthMock @@ -205,10 +206,11 @@ object AuthMockApi { } } - sealed trait AuthMockService - object AuthMockService { + private[AuthMockApi] sealed trait AuthMockService + private[AuthMockApi] object AuthMockService { final case class MockUser(name: NonEmptyString) - final case class MockUserWithGroups(name: NonEmptyString, groups: List[NonEmptyString]) + final case class MockUserWithGroups(name: NonEmptyString, groups: List[MockGroup]) + final case class MockGroup(id: NonEmptyString, name: Option[NonEmptyString]) sealed trait MockMode[+T] object MockMode { @@ -233,29 +235,25 @@ object AuthMockApi { } } - implicit class StatusFromAuthMockResponse(val response: AuthMockResponse) extends AnyVal { - def status: String = response match { - case _: ProvideAuthMock.CurrentAuthMocks => "TEST_SETTINGS_PRESENT" - case _: ProvideAuthMock.NotConfigured => "TEST_SETTINGS_NOT_CONFIGURED" - case _: ProvideAuthMock.Invalidated => "TEST_SETTINGS_INVALIDATED" - case _: UpdateAuthMock.Success => "OK" - case _: UpdateAuthMock.NotConfigured => "TEST_SETTINGS_NOT_CONFIGURED" - case _: UpdateAuthMock.Invalidated => "TEST_SETTINGS_INVALIDATED" - case _: UpdateAuthMock.UnknownAuthServicesDetected => "UNKNOWN_AUTH_SERVICES_DETECTED" - case _: UpdateAuthMock.Failed => "FAILED" - case _: Failure.BadRequest => "FAILED" + implicit class AuthMockResponseOps(val authMockResponse: AuthMockResponse) extends AnyVal { + + type JSON = ujson.Value + + def statusCode: StatusCode = authMockResponse match { + case _: AuthMockResponse.ProvideAuthMock => StatusCode.Ok + case _: AuthMockResponse.UpdateAuthMock => StatusCode.Ok + case _: AuthMockResponse.Failure.BadRequest => StatusCode.BadRequest } - } - implicit class AuthMockServiceOps(val service: AuthMockService) extends AnyVal { - def serviceType: String = service match { - case _: LdapAuthorizationService => "LDAP" - case _: ExternalAuthenticationService => "EXT_AUTHN" - case _: ExternalAuthorizationService => "EXT_AUTHZ" + def body: JSON = { + import Utils.codecs.authMockResponseEncoder + ujson.read(authMockResponse.asJson.noSpaces) } } private object Utils { + import AuthMockService._ + final case class UpdateMocksRequest(services: List[AuthMockService]) implicit class MockUserOps(val mock: MockUserWithGroups) extends AnyVal { @@ -263,9 +261,9 @@ object AuthMockApi { def domainGroups: Set[Group] = mock.groups.map(toDomainGroup).toSet - private def toDomainGroup(groupId: NonEmptyString) = { - val id = GroupId(groupId) - Group.from(id) + private def toDomainGroup(mockGroup: MockGroup) = { + val id = GroupId(mockGroup.id) + Group(id, mockGroup.name.map(GroupName.apply).getOrElse(GroupName.from(id))) } } @@ -275,7 +273,7 @@ object AuthMockApi { maybeMock .map { _.users - .map(user => MockUserWithGroups(user.id.value, user.groups.map(_.id.value).toList)) + .map(user => MockUserWithGroups(user.id.value, user.groups.map(toMockGroup).toList)) .toList } .map(AuthMockService.LdapAuthorizationService.Mock.apply) @@ -291,7 +289,7 @@ object AuthMockApi { maybeMock .map { _.users - .map(user => MockUserWithGroups(user.id.value, user.groups.map(_.id.value).toList)) + .map(user => MockUserWithGroups(user.id.value, user.groups.map(toMockGroup).toList)) .toList } .map(AuthMockService.ExternalAuthorizationService.Mock.apply) @@ -343,6 +341,8 @@ object AuthMockApi { } } + private def toMockGroup(group: Group): MockGroup = MockGroup(id = group.id.value, name = Some(group.name.value)) + private def toLdapMock(user: MockUserWithGroups) = { MocksProvider.LdapServiceMock.LdapUserMock(id = user.domainUserId, groups = user.domainGroups) } @@ -355,61 +355,141 @@ object AuthMockApi { MocksProvider.ExternalAuthenticationServiceMock.ExternalAuthenticationUserMock(id = User.Id(user.name)) } - object decoders { - implicit val nonEmptyStringDecoder: Decoder[NonEmptyString] = Decoder.decodeString.emap(NonEmptyString.from) - implicit val mockUserDecoder: Decoder[MockUser] = Decoder.forProduct1("name")(MockUser.apply) - implicit val mockServiceUserDecoder: Decoder[MockUserWithGroups] = - Decoder.forProduct2("name", "groups")(MockUserWithGroups.apply) - - private def mockModeDecoder[T: Decoder]: Decoder[MockMode[T]] = Decoder.instance { c => - c - .as[String] - .flatMap { - case "NOT_CONFIGURED" => Right(MockMode.NotConfigured) - case "" => Left(DecodingFailure(s"Mock type cannot be empty", ops = c.history)) - case other => Left(DecodingFailure(s"Unknown type of mock: $other", ops = c.history)) - } - .orElse(Decoder[T].apply(c).map(MockMode.Enabled.apply)) + object codecs { + + import AuthMockResponse._ + + implicit val nonEmptyStringCodec: Codec[NonEmptyString] = Codec.from( + Decoder.decodeString.emap(NonEmptyString.from), + Encoder.encodeString.contramap(_.value) + ) + implicit val mockUserCodec: Codec[MockUser] = Codec.forProduct1("name")(MockUser.apply)(_.name) + implicit val mockGroupCodec: Codec[MockGroup] = + Codec.forProduct2("id", "name")(MockGroup.apply)(group => (group.id, group.name)) + implicit val mockServiceUserCodec: Codec[MockUserWithGroups] = + Codec.forProduct2("name", "groups")(MockUserWithGroups.apply)(user => (user.name, user.groups)) + + private def mockModeCodecFor[T: Encoder : Decoder]: Codec[MockMode[T]] = { + val decoder: Decoder[MockMode[T]] = Decoder.instance { c => + c + .as[String] + .flatMap { + case "NOT_CONFIGURED" => Right(MockMode.NotConfigured) + case "" => Left(DecodingFailure(s"Mock type cannot be empty", ops = c.history)) + case other => Left(DecodingFailure(s"Unknown type of mock: $other", ops = c.history)) + } + .orElse(Decoder[T].apply(c).map(MockMode.Enabled.apply)) + } + val encoder: Encoder[MockMode[T]] = Encoder.encodeJson.contramap { + case MockMode.NotConfigured => "NOT_CONFIGURED".asJson + case MockMode.Enabled(configuredMock) => Encoder[T].apply(configuredMock) + } + Codec.from(decoder, encoder) } - implicit val ldapAuthorizationServiceDecoder: Decoder[LdapAuthorizationService] = { - implicit val mockDecoder: Decoder[LdapAuthorizationService.Mock] = - Decoder.forProduct1("users")(LdapAuthorizationService.Mock.apply) - implicit val modeDecoder: Decoder[MockMode[LdapAuthorizationService.Mock]] = - mockModeDecoder[LdapAuthorizationService.Mock] + implicit val ldapAuthorizationServiceCodec: Codec[LdapAuthorizationService] = { + implicit val mockCodec: Codec[LdapAuthorizationService.Mock] = + Codec.forProduct1("users")(LdapAuthorizationService.Mock.apply)(_.users) + implicit val mockModeCodec: Codec[MockMode[LdapAuthorizationService.Mock]] = + mockModeCodecFor[LdapAuthorizationService.Mock] - Decoder.forProduct2("name", "mock")(LdapAuthorizationService.apply) + Codec.forProduct2("name", "mock")( + LdapAuthorizationService.apply + )(mock => (mock.name, mock.mock)) } - implicit val externalAuthenticationMockType: Decoder[ExternalAuthenticationService] = { - implicit val mockDecoder: Decoder[ExternalAuthenticationService.Mock] = - Decoder.forProduct1("users")(ExternalAuthenticationService.Mock.apply) - implicit val modeDecoder: Decoder[MockMode[ExternalAuthenticationService.Mock]] = - mockModeDecoder[ExternalAuthenticationService.Mock] + implicit val externalAuthenticationServiceCodec: Codec[ExternalAuthenticationService] = { + implicit val mockCodec: Codec[ExternalAuthenticationService.Mock] = + Codec.forProduct1("users")(ExternalAuthenticationService.Mock.apply)(_.users) + implicit val mockModeCodec: Codec[MockMode[ExternalAuthenticationService.Mock]] = + mockModeCodecFor[ExternalAuthenticationService.Mock] - Decoder.forProduct2("name", "mock")(ExternalAuthenticationService.apply) + Codec.forProduct2("name", "mock")( + ExternalAuthenticationService.apply + )(mock => (mock.name, mock.mock)) } - implicit val externalAuthorizationMockType: Decoder[ExternalAuthorizationService] = { - implicit val mockDecoder: Decoder[ExternalAuthorizationService.Mock] = - Decoder.forProduct1("users")(ExternalAuthorizationService.Mock.apply) - implicit val modeDecoder: Decoder[MockMode[ExternalAuthorizationService.Mock]] = - mockModeDecoder[ExternalAuthorizationService.Mock] + implicit val externalAuthorizationServiceCodec: Codec[ExternalAuthorizationService] = { + implicit val mockCodec: Codec[ExternalAuthorizationService.Mock] = + Codec.forProduct1("users")(ExternalAuthorizationService.Mock.apply)(_.users) + implicit val mockModeCodec: Codec[MockMode[ExternalAuthorizationService.Mock]] = + mockModeCodecFor[ExternalAuthorizationService.Mock] - Decoder.forProduct2("name", "mock")(ExternalAuthorizationService.apply) + Codec.forProduct2("name", "mock")( + ExternalAuthorizationService.apply + )(mock => (mock.name, mock.mock)) } - implicit val authMockServiceDecoder: Decoder[AuthMockService] = Decoder.instance { c => - for { - serviceType <- c.downField("type").as[String] - service <- serviceType match { - case "LDAP" => Decoder[LdapAuthorizationService].apply(c) - case "EXT_AUTHN" => Decoder[ExternalAuthenticationService].apply(c) - case "EXT_AUTHZ" => Decoder[ExternalAuthorizationService].apply(c) - case other => Left(DecodingFailure(s"Unknown auth mock service type: $other", Nil)) + implicit val authMockServiceCodec: Codec[AuthMockService] = { + val decoder: Decoder[AuthMockService] = Decoder.instance { c => + for { + serviceType <- c.downField("type").as[String] + service <- serviceType match { + case "LDAP" => Decoder[LdapAuthorizationService].apply(c) + case "EXT_AUTHN" => Decoder[ExternalAuthenticationService].apply(c) + case "EXT_AUTHZ" => Decoder[ExternalAuthorizationService].apply(c) + case other => Left(DecodingFailure(s"Unknown auth mock service type: $other", Nil)) + } + } yield service + } + val encoder: Encoder[AuthMockService] = Encoder.instance { + case service: LdapAuthorizationService => + Json.obj("type" -> Json.fromString("LDAP")) + .deepMerge(Encoder[LdapAuthorizationService].apply(service)) + case service: ExternalAuthenticationService => + Json.obj("type" -> Json.fromString("EXT_AUTHN")) + .deepMerge(Encoder[ExternalAuthenticationService].apply(service)) + case service: ExternalAuthorizationService => + Json.obj("type" -> Json.fromString("EXT_AUTHZ")) + .deepMerge(Encoder[ExternalAuthorizationService].apply(service)) + } + Codec.from(decoder, encoder) + } + + implicit val authMockResponseEncoder: Encoder[AuthMockResponse] = { + implicit val provideAuthMockResponseEncoder: Encoder[ProvideAuthMock] = { + val currentAuthMocksResponseEncoder: Encoder[AuthMockResponse.ProvideAuthMock.CurrentAuthMocks] = + Encoder.forProduct2("status", "services")(response => + ("TEST_SETTINGS_PRESENT", response.services) + ) + Encoder.instance { + case response: ProvideAuthMock.CurrentAuthMocks => + currentAuthMocksResponseEncoder.apply(response) + case ProvideAuthMock.NotConfigured(message) => + Map("status" -> "TEST_SETTINGS_NOT_CONFIGURED", "message" -> message).asJson + case ProvideAuthMock.Invalidated(message) => + Map("status" -> "TEST_SETTINGS_INVALIDATED", "message" -> message).asJson + } + } + + implicit val updateAuthMockResponseEncoder: Encoder[UpdateAuthMock] = { + def toJson(status: String, message: String): Json = + Map("status" -> status, "message" -> message).asJson + + Encoder.instance { + case UpdateAuthMock.Success(message) => + toJson("OK", message) + case UpdateAuthMock.NotConfigured(message) => + toJson("TEST_SETTINGS_NOT_CONFIGURED", message) + case UpdateAuthMock.Invalidated(message) => + toJson("TEST_SETTINGS_INVALIDATED", message) + case UpdateAuthMock.UnknownAuthServicesDetected(message) => + toJson("UNKNOWN_AUTH_SERVICES_DETECTED", message) + case UpdateAuthMock.Failed(message) => + toJson("FAILED", message) } - } yield service + } + + implicit val failureEncoder: Encoder[Failure] = Encoder.instance { + case Failure.BadRequest(message) => Map("status" -> "FAILED", "message" -> message).asJson + } + + Encoder.instance { + case response: ProvideAuthMock => Encoder[ProvideAuthMock].apply(response) + case response: UpdateAuthMock => Encoder[UpdateAuthMock].apply(response) + case response: Failure => Encoder[Failure].apply(response) + } } implicit val updateRequestDecoder: Decoder[UpdateMocksRequest] = Decoder.forProduct1("services")(UpdateMocksRequest.apply) diff --git a/core/src/main/scala/tech/beshu/ror/api/StatusCode.scala b/core/src/main/scala/tech/beshu/ror/api/StatusCode.scala new file mode 100644 index 0000000000..c92fe90540 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/api/StatusCode.scala @@ -0,0 +1,23 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.api + +sealed abstract class StatusCode(val code: Int) +object StatusCode { + case object Ok extends StatusCode(200) + case object BadRequest extends StatusCode(400) +} diff --git a/core/src/main/scala/tech/beshu/ror/utils/json/JsonPath.scala b/core/src/main/scala/tech/beshu/ror/utils/json/JsonPath.scala new file mode 100644 index 0000000000..1f7a3e4149 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/utils/json/JsonPath.scala @@ -0,0 +1,52 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.utils.json + +import tech.beshu.ror.com.jayway.jsonpath.{JsonPath => JaywayJsonPath} +import tech.beshu.ror.utils.json.JsonPath.UnableToReadInputAtJsonPath + +import scala.util.{Failure, Try} + + +final case class JsonPath private(rawPath: String) { + private val path: JaywayJsonPath = JsonPath.compile(rawPath) + + def read[A](json: String): Try[A] = tryRead(path.read[A](json)) + + def read[A](obj: Object): Try[A] = tryRead(path.read[A](obj)) + + private def tryRead[A](value: => A): Try[A] = + Try(value) + .recoverWith { + case cause: Throwable => Failure(UnableToReadInputAtJsonPath(cause)) + } +} + +object JsonPath { + def apply(value: String): Try[JsonPath] = { + Try(compile(value)) + .recoverWith { + case cause: Throwable => Failure(UnableToCompileJsonPath(cause)) + } + .map(_ => new JsonPath(value)) + } + + private def compile(path: String): JaywayJsonPath = JaywayJsonPath.compile(path) + + private final case class UnableToCompileJsonPath(cause: Throwable) extends Exception("Unable to compile JSON path", cause) + private final case class UnableToReadInputAtJsonPath(cause: Throwable) extends Exception("Unable to read input at JSON path", cause) +} diff --git a/core/src/test/resources/current_user_metadata_access_control_tests/wiremock_service3_user7.json b/core/src/test/resources/current_user_metadata_access_control_tests/wiremock_service3_user7.json new file mode 100644 index 0000000000..c2076a2d0f --- /dev/null +++ b/core/src/test/resources/current_user_metadata_access_control_tests/wiremock_service3_user7.json @@ -0,0 +1,16 @@ +{ + "priority": 1, + "request": { + "method": "GET", + "urlPath": "/groups", + "queryParameters": { + "user": { + "equalTo": "user7" + } + } + }, + "response": { + "status": 200, + "body": "{ \"sth\": \"unknown\", \"groups\": [ { \"id\": \"service3_group1\", \"name\": \"Group 1\" }, { \"id\": \"service3_group2\", \"name\": \"Group 2\" } ] }" + } +} \ No newline at end of file diff --git a/core/src/test/scala/tech/beshu/ror/integration/CurrentGroupHandlingAccessControlTests.scala b/core/src/test/scala/tech/beshu/ror/integration/CurrentGroupHandlingAccessControlTests.scala index c3177c0bbf..614a3b3209 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/CurrentGroupHandlingAccessControlTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/CurrentGroupHandlingAccessControlTests.scala @@ -27,7 +27,7 @@ import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId import tech.beshu.ror.accesscontrol.domain.LoggedUser.DirectlyLoggedUser import tech.beshu.ror.accesscontrol.domain.User import tech.beshu.ror.mocks.MockRequestContext -import tech.beshu.ror.utils.TestsUtils.{basicAuthHeader, bearerHeader, currentGroupHeader, group} +import tech.beshu.ror.utils.TestsUtils._ import tech.beshu.ror.utils.misc.JwtUtils._ import tech.beshu.ror.utils.uniquelist.UniqueList diff --git a/core/src/test/scala/tech/beshu/ror/integration/CurrentUserMetadataAccessControlTests.scala b/core/src/test/scala/tech/beshu/ror/integration/CurrentUserMetadataAccessControlTests.scala index d269a4cef3..671263252c 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/CurrentUserMetadataAccessControlTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/CurrentUserMetadataAccessControlTests.scala @@ -49,7 +49,8 @@ class CurrentUserMetadataAccessControlTests private val wiremock = new WireMockScalaAdapter(WireMockContainer.create( "/current_user_metadata_access_control_tests/wiremock_service1_user5.json", - "/current_user_metadata_access_control_tests/wiremock_service2_user6.json" + "/current_user_metadata_access_control_tests/wiremock_service2_user6.json", + "/current_user_metadata_access_control_tests/wiremock_service3_user7.json", )) private val ldap1 = LdapContainer.create("LDAP1", "current_user_metadata_access_control_tests/ldap_ldap1_user5.ldif" @@ -151,6 +152,12 @@ class CurrentUserMetadataAccessControlTests | user_groups_provider: "Service2" | groups: ["service2_group2"] | + | - name: "SERVICE3 user7" + | proxy_auth: "user7" + | groups_provider_authorization: + | user_groups_provider: "Service3" + | groups: ["service3_group1"] + | | users: | | - username: user1 @@ -162,7 +169,11 @@ class CurrentUserMetadataAccessControlTests | auth_key: "user2:pass" | | - username: user4 - | groups: ["group5", "group6"] + | groups: + | - id: group5 + | name: "Group 5" + | - id : group6 + | name: "Group 6" | auth_key: "user4:pass" | | user_groups_providers: @@ -179,6 +190,13 @@ class CurrentUserMetadataAccessControlTests | auth_token_passed_as: QUERY_PARAM | response_groups_json_path: "$$..groups[?(@.name)].name" | + | - name: Service3 + | groups_endpoint: "http://${wiremock.getWireMockHost}:${wiremock.getWireMockPort}/groups" + | auth_token_name: "user" + | auth_token_passed_as: QUERY_PARAM + | response_group_ids_json_path: "$$..groups[?(@.id)].id" + | response_group_names_json_path: "$$..groups[?(@.name)].name" + | | ldaps: | - name: Ldap1 | host: "${ldap1.ldapHost}" @@ -239,7 +257,7 @@ class CurrentUserMetadataAccessControlTests inside(loginResponse.result) { case Allow(userMetadata, _) => userMetadata.loggedUser should be (Some(DirectlyLoggedUser(User.Id("user4")))) userMetadata.currentGroupId should be (Some(GroupId("group6"))) - userMetadata.availableGroups.toSet should be (Set(group("group5"), group("group6"))) + userMetadata.availableGroups.toSet should be (Set(group("group5", "Group 5"), group("group6", "Group 6"))) userMetadata.kibanaIndex should be (Some(kibanaIndexName("user4_group6_kibana_index"))) userMetadata.hiddenKibanaApps should be (Set.empty) userMetadata.allowedKibanaApiPaths should be (Set.empty) @@ -254,7 +272,7 @@ class CurrentUserMetadataAccessControlTests inside(switchTenancyResponse.result) { case Allow(userMetadata, _) => userMetadata.loggedUser should be (Some(DirectlyLoggedUser(User.Id("user4")))) userMetadata.currentGroupId should be (Some(GroupId("group5"))) - userMetadata.availableGroups.toSet should be (Set(group("group5"), group("group6"))) + userMetadata.availableGroups.toSet should be (Set(group("group5", "Group 5"), group("group6", "Group 6"))) userMetadata.kibanaIndex should be (Some(kibanaIndexName("user4_group5_kibana_index"))) userMetadata.hiddenKibanaApps should be (Set.empty) userMetadata.allowedKibanaApiPaths should be (Set.empty) @@ -312,6 +330,16 @@ class CurrentUserMetadataAccessControlTests userMetadata.loggedUser should be (Some(DirectlyLoggedUser(User.Id("user5")))) userMetadata.availableGroups.toSet should be (Set(group("service1_group1"), group("service1_group2"))) } + + val request3 = MockRequestContext.metadata.copy( + headers = Set(header("X-Forwarded-User", "user7"), currentGroupHeader("service3_group1")) + ) + val result3 = acl.handleMetadataRequest(request3).runSyncUnsafe() + + inside(result3.result) { case Allow(userMetadata, _) => + userMetadata.loggedUser should be(Some(DirectlyLoggedUser(User.Id("user7")))) + userMetadata.availableGroups.toSet should be(Set(group("service3_group1", "Group 1"))) + } } "the service is LDAP" in { val request1 = MockRequestContext.metadata.copy(headers = Set(basicAuthHeader("user6:user2"))) diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/ldap/implementations/UnboundidLdapAuthorizationServiceInDefaultGroupSearchModeTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/ldap/implementations/UnboundidLdapAuthorizationServiceInDefaultGroupSearchModeTests.scala index 96ad2e2e2c..afa9925e95 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/ldap/implementations/UnboundidLdapAuthorizationServiceInDefaultGroupSearchModeTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/ldap/implementations/UnboundidLdapAuthorizationServiceInDefaultGroupSearchModeTests.scala @@ -28,12 +28,12 @@ import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.Dn import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.LdapService.Name import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UnboundidLdapConnectionPoolProvider.LdapConnectionConfig import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UnboundidLdapConnectionPoolProvider.LdapConnectionConfig.{BindRequestUser, ConnectionMethod, LdapHost} -import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserGroupsSearchFilterConfig.UserGroupsSearchMode._ +import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserGroupsSearchFilterConfig.UserGroupsSearchMode.{DefaultGroupSearch, GroupIdAttribute, GroupSearchFilter, NestedGroupsConfig, UniqueMemberAttribute} import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserSearchFilterConfig.UserIdAttribute import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations._ import tech.beshu.ror.accesscontrol.domain.{Group, PlainTextSecret, User} import tech.beshu.ror.utils.SingletonLdapContainers -import tech.beshu.ror.utils.TestsUtils.group +import tech.beshu.ror.utils.TestsUtils._ import tech.beshu.ror.utils.uniquelist.UniqueList import java.time.Clock diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/ldap/implementations/UnboundidLdapAuthorizationServiceInGroupsFromUserAttributeModeTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/ldap/implementations/UnboundidLdapAuthorizationServiceInGroupsFromUserAttributeModeTests.scala index d06ae08d0e..db640623be 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/ldap/implementations/UnboundidLdapAuthorizationServiceInGroupsFromUserAttributeModeTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/ldap/implementations/UnboundidLdapAuthorizationServiceInGroupsFromUserAttributeModeTests.scala @@ -28,12 +28,12 @@ import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.Dn import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.LdapService.Name import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UnboundidLdapConnectionPoolProvider.LdapConnectionConfig import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UnboundidLdapConnectionPoolProvider.LdapConnectionConfig.{BindRequestUser, ConnectionMethod, LdapHost} -import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserGroupsSearchFilterConfig.UserGroupsSearchMode._ import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserSearchFilterConfig.UserIdAttribute +import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserGroupsSearchFilterConfig.UserGroupsSearchMode.{GroupIdAttribute, GroupSearchFilter, GroupsFromUserAttribute, GroupsFromUserEntry, NestedGroupsConfig, UniqueMemberAttribute} import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations._ import tech.beshu.ror.accesscontrol.domain.{Group, PlainTextSecret, User} import tech.beshu.ror.utils.SingletonLdapContainers -import tech.beshu.ror.utils.TestsUtils.group +import tech.beshu.ror.utils.TestsUtils._ import tech.beshu.ror.utils.uniquelist.UniqueList import java.time.Clock diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/auth/BaseGroupsRuleTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/auth/BaseGroupsRuleTests.scala index 785d0ee541..7252b6e7d7 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/auth/BaseGroupsRuleTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/auth/BaseGroupsRuleTests.scala @@ -566,7 +566,7 @@ trait BaseGroupsRuleTests extends AnyWordSpecLike with Inside with BlockContextA ) } - def groups(g1: NonEmptyString, gs: NonEmptyString*): UniqueNonEmptyList[Group] = { + def groups(g1: String, gs: String*): UniqueNonEmptyList[Group] = { UniqueNonEmptyList.of(group(g1), gs.map(group): _*) } diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/auth/JwtAuthRuleTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/auth/JwtAuthRuleTests.scala index 298674f053..69751ab8b1 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/auth/JwtAuthRuleTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/auth/JwtAuthRuleTests.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.unit.acl.blocks.rules.auth +import cats.data.NonEmptyList import eu.timepit.refined.api.Refined import eu.timepit.refined.auto._ import eu.timepit.refined.types.string.NonEmptyString @@ -30,7 +31,7 @@ import org.scalatest.wordspec.AnyWordSpec import tech.beshu.ror.accesscontrol.blocks.BlockContext import tech.beshu.ror.accesscontrol.blocks.BlockContext.GeneralIndexRequestBlockContext import tech.beshu.ror.accesscontrol.blocks.definitions.ExternalAuthenticationService.Name -import tech.beshu.ror.accesscontrol.blocks.definitions.JwtDef.SignatureCheckMethod +import tech.beshu.ror.accesscontrol.blocks.definitions.JwtDef.{GroupsConfig, SignatureCheckMethod} import tech.beshu.ror.accesscontrol.blocks.definitions.{CacheableExternalAuthenticationServiceDecorator, ExternalAuthenticationService, JwtDef} import tech.beshu.ror.accesscontrol.blocks.metadata.UserMetadata import tech.beshu.ror.accesscontrol.blocks.rules.Rule.RuleResult.{Fulfilled, Rejected} @@ -40,7 +41,6 @@ import tech.beshu.ror.accesscontrol.domain import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId import tech.beshu.ror.accesscontrol.domain.LoggedUser.DirectlyLoggedUser import tech.beshu.ror.accesscontrol.domain.{Jwt => _, _} -import tech.beshu.ror.com.jayway.jsonpath.JsonPath import tech.beshu.ror.mocks.MockRequestContext import tech.beshu.ror.utils.DurationOps._ import tech.beshu.ror.utils.TestsUtils._ @@ -50,6 +50,7 @@ import tech.beshu.ror.utils.uniquelist.{UniqueList, UniqueNonEmptyList} import java.security.Key import scala.concurrent.duration._ +import scala.jdk.CollectionConverters._ import scala.language.postfixOps class JwtAuthRuleTests @@ -66,7 +67,7 @@ class JwtAuthRuleTests AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(secret.getEncoded), userClaim = None, - groupsClaim = None + groupsConfig = None ), tokenHeader = bearerHeader(jwt) ) { @@ -84,7 +85,7 @@ class JwtAuthRuleTests AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Rsa(pub), userClaim = None, - groupsClaim = None + groupsConfig = None ), tokenHeader = bearerHeader(jwt) ) { @@ -101,7 +102,7 @@ class JwtAuthRuleTests AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.NoCheck(authService(jwt.stringify(), authenticated = true)), userClaim = None, - groupsClaim = None + groupsConfig = None ), tokenHeader = bearerHeader(jwt) ) { @@ -119,7 +120,7 @@ class JwtAuthRuleTests AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.NoCheck(authService), userClaim = None, - groupsClaim = None + groupsConfig = None ) def checkValidToken = assertMatchRule( configuredJwtDef = jwtDef, @@ -149,8 +150,8 @@ class JwtAuthRuleTests JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = None + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = None, ), tokenHeader = bearerHeader(jwt) ) { @@ -160,7 +161,7 @@ class JwtAuthRuleTests )(blockContext) } } - "groups claim name is defined and groups are passed in JWT token claim (no preferred group)" in { + "group IDs claim name is defined and groups are passed in JWT token claim (no preferred group)" in { val key: Key = Keys.secretKeyFor(SignatureAlgorithm.valueOf("HS256")) val jwt = Jwt(key, claims = List( "userId" := "user1", @@ -171,8 +172,8 @@ class JwtAuthRuleTests JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("groups"))) + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups")), None)) ), tokenHeader = bearerHeader(jwt) ) { @@ -182,7 +183,7 @@ class JwtAuthRuleTests )(blockContext) } } - "groups claim name is defined and groups are passed in JWT token claim (with preferred group)" in { + "group IDs claim name is defined and groups are passed in JWT token claim (with preferred group)" in { val key: Key = Keys.secretKeyFor(SignatureAlgorithm.valueOf("HS256")) val jwt = Jwt(key, claims = List( "userId" := "user1", @@ -193,8 +194,8 @@ class JwtAuthRuleTests JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("groups"))) + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups")), None)) ), tokenHeader = bearerHeader(jwt), preferredGroupId = Some(GroupId("group1")) @@ -206,7 +207,7 @@ class JwtAuthRuleTests )(blockContext) } } - "groups claim name is defined as http address and groups are passed in JWT token claim" in { + "group IDs claim name is defined as http address and groups are passed in JWT token claim" in { val key: Key = Keys.secretKeyFor(SignatureAlgorithm.valueOf("HS256")) val jwt = Jwt(key, claims = List( "userId" := "user1", @@ -217,8 +218,8 @@ class JwtAuthRuleTests JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("https://{domain}/claims/roles"))) + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("https://{domain}/claims/roles")), None)) ), tokenHeader = bearerHeader(jwt) ) { @@ -228,7 +229,7 @@ class JwtAuthRuleTests )(blockContext) } } - "groups claim name is defined and no groups field is passed in JWT token claim" in { + "group IDs claim name is defined and no groups field is passed in JWT token claim" in { val key: Key = Keys.secretKeyFor(SignatureAlgorithm.valueOf("HS256")) val jwt = Jwt(key, claims = List( "userId" := "user1" @@ -238,19 +239,19 @@ class JwtAuthRuleTests JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("groups"))) + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups")), None)) ), configuredGroups = Groups.NotDefined, tokenHeader = bearerHeader(jwt) ) { blockContext => assertBlockContext( loggedUser = Some(DirectlyLoggedUser(User.Id("user1"))), - jwt = Some(domain.Jwt.Payload(jwt.defaultClaims())) + jwt = Some(domain.Jwt.Payload(jwt.defaultClaims())), )(blockContext) } } - "groups claim path is defined and groups are passed in JWT token claim" in { + "group IDs claim path is defined and groups are passed in JWT token claim" in { val key: Key = Keys.secretKeyFor(SignatureAlgorithm.valueOf("HS256")) val jwt = Jwt(key, claims = List( "userId" := "user1", @@ -261,8 +262,8 @@ class JwtAuthRuleTests JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("tech.beshu.groups"))) + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("tech.beshu.groups")), None)) ), tokenHeader = bearerHeader(jwt) ) { @@ -272,19 +273,112 @@ class JwtAuthRuleTests )(blockContext) } } + "group names claim is defined and group names are passed in JWT token claim" in { + val key: Key = Keys.secretKeyFor(SignatureAlgorithm.valueOf("HS256")) + val jwt = Jwt(key, claims = List( + "userId" := "user1", + Claim(NonEmptyList.one(ClaimKey("groups")), List( + Map("id" -> "group1", "name" -> "Group 1").asJava, + Map("id" -> "group2", "name" -> "Group 2").asJava + ).asJava) + )) + assertMatchRule( + configuredJwtDef = JwtDef( + JwtDef.Name("test"), + AuthorizationTokenDef(Header.Name.authorization, "Bearer "), + SignatureCheckMethod.Hmac(key.getEncoded), + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig( + idsClaim = domain.Jwt.ClaimName(jsonPathFrom("groups[?(@.id)].id")), + namesClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("groups[?(@.name)].name"))) + )) + ), + tokenHeader = bearerHeader(jwt) + ) { + blockContext => + assertBlockContext( + loggedUser = Some(DirectlyLoggedUser(User.Id("user1"))), + jwt = Some(domain.Jwt.Payload(jwt.defaultClaims())), + )(blockContext) + } + } + "group names claim is defined and group names passed in JWT token claim are malformed" when { + "group names count differs from the group ID count" in { + val key: Key = Keys.secretKeyFor(SignatureAlgorithm.valueOf("HS256")) + val jwt = Jwt(key, claims = List( + "userId" := "user1", + Claim(NonEmptyList.one(ClaimKey("groups")), List( + Map("id" -> "group1", "name" -> List("Group 1", "Group A").asJava).asJava, + Map("id" -> "group2", "name" -> List("Group 2", "Group B").asJava).asJava + ).asJava) + )) + assertMatchRule( + configuredJwtDef = JwtDef( + JwtDef.Name("test"), + AuthorizationTokenDef(Header.Name.authorization, "Bearer "), + SignatureCheckMethod.Hmac(key.getEncoded), + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig( + idsClaim = domain.Jwt.ClaimName(jsonPathFrom("groups[?(@.id)].id")), + namesClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("groups[?(@.name)].name"))) + )) + ), + tokenHeader = bearerHeader(jwt) + ) { + blockContext => + assertBlockContext( + loggedUser = Some(DirectlyLoggedUser(User.Id("user1"))), + jwt = Some(domain.Jwt.Payload(jwt.defaultClaims())), + )(blockContext) + } + } + "one group does not have a name" in { + val key: Key = Keys.secretKeyFor(SignatureAlgorithm.valueOf("HS256")) + val jwt = Jwt(key, claims = List( + "userId" := "user1", + Claim(NonEmptyList.one(ClaimKey("groups")), List( + Map("id" -> "group1", "name" -> "Group 1").asJava, + Map("id" -> "group2").asJava, + Map("id" -> "group3", "name" -> "Group 3").asJava + ).asJava) + )) + assertMatchRule( + configuredJwtDef = JwtDef( + JwtDef.Name("test"), + AuthorizationTokenDef(Header.Name.authorization, "Bearer "), + SignatureCheckMethod.Hmac(key.getEncoded), + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups[?(@.id)].id")), Some(domain.Jwt.ClaimName(jsonPathFrom("groups[?(@.name)].name"))))) + ), + tokenHeader = bearerHeader(jwt) + ) { + blockContext => + assertBlockContext( + loggedUser = Some(DirectlyLoggedUser(User.Id("user1"))), + jwt = Some(domain.Jwt.Payload(jwt.defaultClaims())), + )(blockContext) + } + } + } "rule groups with 'or' logic are defined and intersection between those groups and JWT ones is not empty (1)" in { val key: Key = Keys.secretKeyFor(SignatureAlgorithm.valueOf("HS256")) val jwt = Jwt(key, claims = List( "userId" := "user1", - "groups" := List("group1", "group2") + Claim(NonEmptyList.one(ClaimKey("groups")), List( + Map("id" -> "group1", "name" -> "Group 1").asJava, + Map("id" -> "group2", "name" -> "Group 2").asJava + ).asJava) )) assertMatchRule( configuredJwtDef = JwtDef( JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("groups"))) + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig( + idsClaim = domain.Jwt.ClaimName(jsonPathFrom("groups[?(@.id)].id")), + namesClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("groups[?(@.name)].name"))) + )) ), configuredGroups = Groups.Defined(GroupsLogic.Or(PermittedGroupIds( UniqueNonEmptyList.of(GroupId("group3"), GroupId("group2")) @@ -295,7 +389,7 @@ class JwtAuthRuleTests loggedUser = Some(DirectlyLoggedUser(User.Id("user1"))), jwt = Some(domain.Jwt.Payload(jwt.defaultClaims())), currentGroup = Some(GroupId("group2")), - availableGroups = UniqueList.of(group("group2")) + availableGroups = UniqueList.of(group("group2", "Group 2")) )(blockContext) } } @@ -303,15 +397,21 @@ class JwtAuthRuleTests val key: Key = Keys.secretKeyFor(SignatureAlgorithm.valueOf("HS256")) val jwt = Jwt(key, claims = List( "userId" := "user1", - "groups" := List("group1", "group2") + Claim(NonEmptyList.one(ClaimKey("groups")), List( + Map("id" -> "group1", "name" -> "Group 1").asJava, + Map("id" -> "group2", "name" -> "Group 2").asJava + ).asJava) )) assertMatchRule( configuredJwtDef = JwtDef( JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("groups"))) + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig( + idsClaim = domain.Jwt.ClaimName(jsonPathFrom("groups[?(@.id)].id")), + namesClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("groups[?(@.name)].name"))) + )) ), configuredGroups = Groups.Defined(GroupsLogic.Or(PermittedGroupIds( UniqueNonEmptyList.of(GroupId("group3"), GroupIdLike.from("*2")) @@ -322,7 +422,7 @@ class JwtAuthRuleTests loggedUser = Some(DirectlyLoggedUser(User.Id("user1"))), jwt = Some(domain.Jwt.Payload(jwt.defaultClaims())), currentGroup = Some(GroupId("group2")), - availableGroups = UniqueList.of(group("group2")) + availableGroups = UniqueList.of(group("group2", "Group 2")) )(blockContext) } } @@ -330,15 +430,21 @@ class JwtAuthRuleTests val key: Key = Keys.secretKeyFor(SignatureAlgorithm.valueOf("HS256")) val jwt = Jwt(key, claims = List( "userId" := "user1", - "groups" := List("group1", "group2") + Claim(NonEmptyList.one(ClaimKey("groups")), List( + Map("id" -> "group1", "name" -> "Group 1").asJava, + Map("id" -> "group2", "name" -> "Group 2").asJava + ).asJava) )) assertMatchRule( configuredJwtDef = JwtDef( JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("groups"))) + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig( + idsClaim = domain.Jwt.ClaimName(jsonPathFrom("groups[?(@.id)].id")), + namesClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("groups[?(@.name)].name"))) + )) ), configuredGroups = Groups.Defined(GroupsLogic.And(PermittedGroupIds( UniqueNonEmptyList.of(GroupId("group1"), GroupId("group2")) @@ -349,7 +455,7 @@ class JwtAuthRuleTests loggedUser = Some(DirectlyLoggedUser(User.Id("user1"))), jwt = Some(domain.Jwt.Payload(jwt.defaultClaims())), currentGroup = Some(GroupId("group1")), - availableGroups = UniqueList.of(group("group1"), group("group2")) + availableGroups = UniqueList.of(group("group1", "Group 1"), group("group2", "Group 2")) )(blockContext) } } @@ -357,15 +463,21 @@ class JwtAuthRuleTests val key: Key = Keys.secretKeyFor(SignatureAlgorithm.valueOf("HS256")) val jwt = Jwt(key, claims = List( "userId" := "user1", - "groups" := List("group1", "group2") + Claim(NonEmptyList.one(ClaimKey("groups")), List( + Map("id" -> "group1", "name" -> "Group 1").asJava, + Map("id" -> "group2", "name" -> "Group 2").asJava + ).asJava) )) assertMatchRule( configuredJwtDef = JwtDef( JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("groups"))) + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig( + idsClaim = domain.Jwt.ClaimName(jsonPathFrom("groups[?(@.id)].id")), + namesClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("groups[?(@.name)].name"))) + )) ), configuredGroups = Groups.Defined(GroupsLogic.And(PermittedGroupIds( UniqueNonEmptyList.of(GroupIdLike.from("*1"), GroupIdLike.from("*2")) @@ -376,7 +488,7 @@ class JwtAuthRuleTests loggedUser = Some(DirectlyLoggedUser(User.Id("user1"))), jwt = Some(domain.Jwt.Payload(jwt.defaultClaims())), currentGroup = Some(GroupId("group1")), - availableGroups = UniqueList.of(group("group1"), group("group2")) + availableGroups = UniqueList.of(group("group1", "Group 1"), group("group2", "Group 2")) )(blockContext) } } @@ -389,7 +501,7 @@ class JwtAuthRuleTests AuthorizationTokenDef(Header.Name("x-jwt-custom-header"), "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), userClaim = None, - groupsClaim = None + groupsConfig = None ), tokenHeader = bearerHeader("x-jwt-custom-header", jwt) ) { @@ -407,7 +519,7 @@ class JwtAuthRuleTests AuthorizationTokenDef(Header.Name("x-jwt-custom-header"), "MyPrefix "), SignatureCheckMethod.Hmac(key.getEncoded), userClaim = None, - groupsClaim = None + groupsConfig = None ), tokenHeader = new Header( Header.Name("x-jwt-custom-header"), @@ -431,7 +543,7 @@ class JwtAuthRuleTests AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key1.getEncoded), userClaim = None, - groupsClaim = None + groupsConfig = None ), tokenHeader = bearerHeader(jwt2) ) @@ -446,7 +558,7 @@ class JwtAuthRuleTests AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Rsa(pub), userClaim = None, - groupsClaim = None + groupsConfig = None ), tokenHeader = bearerHeader(jwt) ) @@ -459,7 +571,7 @@ class JwtAuthRuleTests AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.NoCheck(authService(jwt.stringify(), authenticated = false)), userClaim = None, - groupsClaim = None + groupsConfig = None ), tokenHeader = bearerHeader(jwt) ) @@ -472,13 +584,13 @@ class JwtAuthRuleTests JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = None + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = None ), tokenHeader = bearerHeader(jwt) ) } - "groups claim name is defined but groups aren't passed in JWT token claim" in { + "group IDs claim name is defined but groups aren't passed in JWT token claim" in { val key: Key = Keys.secretKeyFor(SignatureAlgorithm.valueOf("HS256")) val jwt = Jwt(key, claims = List.empty) assertNotMatchRule( @@ -486,13 +598,13 @@ class JwtAuthRuleTests JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("groups"))) + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups")), None)) ), tokenHeader = bearerHeader(jwt) ) } - "groups claim path is wrong" in { + "group IDs claim path is wrong" in { val key: Key = Keys.secretKeyFor(SignatureAlgorithm.valueOf("HS256")) val jwt = Jwt(key, claims = List( "userId" := "user1", @@ -503,8 +615,8 @@ class JwtAuthRuleTests JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("tech.beshu.groups.subgroups"))) + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("tech.beshu.groups.subgroups")), None)) ), configuredGroups = Groups.Defined(GroupsLogic.Or(PermittedGroupIds( UniqueNonEmptyList.of(GroupId("group1")) @@ -523,8 +635,8 @@ class JwtAuthRuleTests JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("groups"))) + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups")), None)) ), configuredGroups = Groups.Defined(GroupsLogic.Or(PermittedGroupIds( UniqueNonEmptyList.of(GroupId("group3"), GroupId("group4")) @@ -543,8 +655,8 @@ class JwtAuthRuleTests JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("groups"))) + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups")), None)) ), configuredGroups = Groups.Defined(GroupsLogic.And(PermittedGroupIds( UniqueNonEmptyList.of(GroupId("group2"), GroupId("group3")) @@ -563,8 +675,8 @@ class JwtAuthRuleTests JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("groups"))) + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups")), None)) ), tokenHeader = bearerHeader(jwt), preferredGroupId = Some(GroupId("group3")) @@ -581,8 +693,8 @@ class JwtAuthRuleTests JwtDef.Name("test"), AuthorizationTokenDef(Header.Name.authorization, "Bearer "), SignatureCheckMethod.Hmac(key.getEncoded), - userClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("userId"))), - groupsClaim = Some(domain.Jwt.ClaimName(JsonPath.compile("groups"))) + userClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("userId"))), + groupsConfig = Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups")), None)) ), configuredGroups = Groups.Defined(GroupsLogic.Or(PermittedGroupIds( UniqueNonEmptyList.of(GroupId("group2")) diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/variables/RuntimeResolvableVariablesTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/variables/RuntimeResolvableVariablesTests.scala index 95f79c50ca..3b62e666ba 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/variables/RuntimeResolvableVariablesTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/variables/RuntimeResolvableVariablesTests.scala @@ -293,7 +293,7 @@ class RuntimeResolvableVariablesTests extends AnyWordSpec with MockFactory { } ) )) - variable shouldBe Left(CannotExtractValue("Cannot find value string or collection of strings in path '$['tech']['beshu']' of JWT Token")) + variable shouldBe Left(CannotExtractValue("Cannot find value string or collection of strings in path 'tech.beshu' of JWT Token")) } } "have not been able to be created" when { diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/domain/GroupsLogicExecutorTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/domain/GroupsLogicExecutorTests.scala index ba7e7a3ea1..ca9006145d 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/domain/GroupsLogicExecutorTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/domain/GroupsLogicExecutorTests.scala @@ -21,7 +21,7 @@ import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import tech.beshu.ror.accesscontrol.domain.{GroupIdLike, GroupsLogic, PermittedGroupIds} import tech.beshu.ror.utils.uniquelist.UniqueNonEmptyList -import tech.beshu.ror.utils.TestsUtils.group +import tech.beshu.ror.utils.TestsUtils._ class GroupsLogicExecutorTests extends AnyWordSpec with Matchers { diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/CoreFactoryTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/CoreFactoryTests.scala index ec406fcaa4..ec9b624332 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/CoreFactoryTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/CoreFactoryTests.scala @@ -227,7 +227,7 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | |""".stripMargin) val acl = createCore(config, new MockHttpClientsFactoryWithFixedHttpClient(mock[HttpClient])) diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/ImpersonationWarningsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/ImpersonationWarningsTests.scala index 0084ed215c..acc45825de 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/ImpersonationWarningsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/ImpersonationWarningsTests.scala @@ -152,7 +152,7 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | |""".stripMargin @@ -264,7 +264,7 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | |""".stripMargin diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/LocalUsersTest.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/LocalUsersTest.scala index d99f147e4e..da7870cfcb 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/LocalUsersTest.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/LocalUsersTest.scala @@ -122,7 +122,7 @@ class LocalUsersTest extends AnyWordSpec with Inside { | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "token" | auth_token_passed_as: QUERY_PARAM # HEADER OR QUERY_PARAM - | response_groups_json_path: "$$..groups[?(@.name)].name" # see: https://github.com/json-path/JsonPath + | response_group_ids_json_path: "$$..groups[?(@.id)].id" # see: https://github.com/json-path/JsonPath | cache_ttl_in_sec: 60 | http_connection_settings: | connection_timeout_in_sec: 5 # default 2 diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/LdapServicesSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/LdapServicesSettingsTests.scala index 52b0cafdc4..65e7b8c488 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/LdapServicesSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/LdapServicesSettingsTests.scala @@ -19,11 +19,16 @@ package tech.beshu.ror.unit.acl.factory.decoders.definitions import com.dimafeng.testcontainers.{ForAllTestContainer, MultipleContainers} import eu.timepit.refined.api.Refined import eu.timepit.refined.auto._ +import monix.execution.Scheduler.Implicits.global +import org.joor.Reflect.on import org.scalatest.BeforeAndAfterAll import org.scalatest.matchers.should.Matchers._ import tech.beshu.ror.accesscontrol.blocks.definitions.CircuitBreakerConfig -import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.{UserGroupsSearchFilterConfig, _} import tech.beshu.ror.accesscontrol.blocks.definitions.ldap._ +import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserGroupsSearchFilterConfig.UserGroupsSearchMode +import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserGroupsSearchFilterConfig.UserGroupsSearchMode._ +import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserSearchFilterConfig.UserIdAttribute.{Cn, CustomAttribute} +import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations._ import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} import tech.beshu.ror.accesscontrol.factory.decoders.definitions.LdapServicesDecoder @@ -31,15 +36,10 @@ import tech.beshu.ror.utils.SingletonLdapContainers import tech.beshu.ror.utils.TaskComonad.wait30SecTaskComonad import tech.beshu.ror.utils.containers.LdapWithDnsContainer -import scala.concurrent.duration._ -import scala.language.postfixOps -import org.joor.Reflect.on -import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserGroupsSearchFilterConfig.UserGroupsSearchMode -import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserGroupsSearchFilterConfig.UserGroupsSearchMode._ -import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserSearchFilterConfig.UserIdAttribute.{Cn, CustomAttribute} -import monix.execution.Scheduler.Implicits.global import java.time.Clock import scala.annotation.tailrec +import scala.concurrent.duration._ +import scala.language.postfixOps import scala.reflect.ClassTag class LdapServicesSettingsTests private(ldapConnectionPoolProvider: UnboundidLdapConnectionPoolProvider) @@ -460,7 +460,8 @@ class LdapServicesSettingsTests private(ldapConnectionPoolProvider: UnboundidLda definitions.items should have size 1 val ldapService = definitions.items.head ldapService shouldBe a[CircuitBreakerLdapServiceDecorator] - ldapService.asInstanceOf[CircuitBreakerLdapServiceDecorator].circuitBreakerConfig shouldBe CircuitBreakerConfig(Refined.unsafeApply(10), Refined.unsafeApply(10 seconds)) + ldapService.asInstanceOf[CircuitBreakerLdapServiceDecorator].circuitBreakerConfig shouldBe + CircuitBreakerConfig(Refined.unsafeApply(10), Refined.unsafeApply(10 seconds)) ldapService.id should be(LdapService.Name("ldap1")) val groupsSearchFilterConfig = getGroupsSearchFilterConfigFrom( diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ExternalAuthorizationRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ExternalAuthorizationRuleSettingsTests.scala index 43a7ace2e9..a180a2575b 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ExternalAuthorizationRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ExternalAuthorizationRuleSettingsTests.scala @@ -20,19 +20,26 @@ import eu.timepit.refined.auto._ import org.scalamock.scalatest.MockFactory import org.scalatest.Inside import org.scalatest.matchers.should.Matchers._ +import tech.beshu.ror.accesscontrol.blocks.definitions.HttpExternalAuthorizationService.Config +import tech.beshu.ror.accesscontrol.blocks.definitions.HttpExternalAuthorizationService.Config.GroupsConfig.{GroupIdsConfig, GroupNamesConfig} +import tech.beshu.ror.accesscontrol.blocks.definitions.HttpExternalAuthorizationService.Config._ import tech.beshu.ror.accesscontrol.blocks.definitions._ import tech.beshu.ror.accesscontrol.blocks.rules.auth.ExternalAuthorizationRule import tech.beshu.ror.accesscontrol.blocks.rules.auth.ExternalAuthorizationRule.Settings import tech.beshu.ror.accesscontrol.domain.GroupIdLike.{GroupId, GroupIdPattern} -import tech.beshu.ror.accesscontrol.domain.{GroupIdLike, GroupsLogic, PermittedGroupIds, User} +import tech.beshu.ror.accesscontrol.domain.{GroupIdLike, GroupsLogic, Header, PermittedGroupIds, User} import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory.HttpClient import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, RulesLevelCreationError} +import tech.beshu.ror.utils.TestsUtils._ import tech.beshu.ror.mocks.MockHttpClientsFactoryWithFixedHttpClient import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.uniquelist.UniqueNonEmptyList +import scala.concurrent.duration._ +import scala.language.postfixOps + class ExternalAuthorizationRuleSettingsTests extends BaseRuleSettingsDecoderTest[ExternalAuthorizationRule] with MockFactory with Inside { @@ -59,14 +66,27 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, assertion = rule => { inside(rule.settings) { case Settings(service, permittedGroupsLogic, users) => service.id should be(ExternalAuthorizationService.Name("GroupsService1")) + service.serviceTimeout.value should be(5 seconds) service shouldBe a[HttpExternalAuthorizationService] + service.asInstanceOf[HttpExternalAuthorizationService].config should be(Config( + uri = uriFrom("http://localhost:8080/groups"), + method = SupportedHttpMethod.Get, + tokenName = AuthTokenName("user"), + groupsConfig = GroupsConfig( + idsConfig = GroupIdsConfig(jsonPathFrom("$..groups[?(@.id)].id")), + namesConfig = None + ), + authTokenSendMethod = AuthTokenSendMethod.UsingQueryParam, + defaultHeaders = Set.empty, + defaultQueryParams = Set.empty + )) permittedGroupsLogic should be( GroupsLogic.Or(PermittedGroupIds(UniqueNonEmptyList.of(GroupIdLike.from("g*")))) ) @@ -97,19 +117,32 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | | - name: GroupsService2 | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user2" | auth_token_passed_as: HEADER - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, assertion = rule => { inside(rule.settings) { case Settings(service, permittedGroupsLogic, users) => service.id should be(ExternalAuthorizationService.Name("GroupsService2")) + service.serviceTimeout.value should be(5 seconds) service shouldBe a[HttpExternalAuthorizationService] + service.asInstanceOf[HttpExternalAuthorizationService].config should be(Config( + uri = uriFrom("http://localhost:8080/groups"), + method = SupportedHttpMethod.Get, + tokenName = AuthTokenName("user2"), + groupsConfig = GroupsConfig( + idsConfig = GroupIdsConfig(jsonPathFrom("$..groups[?(@.id)].id")), + namesConfig = None + ), + authTokenSendMethod = AuthTokenSendMethod.UsingHeader, + defaultHeaders = Set.empty, + defaultQueryParams = Set.empty + )) permittedGroupsLogic should be( GroupsLogic.Or(PermittedGroupIds(UniqueNonEmptyList.of(GroupId("group3")))) ) @@ -140,14 +173,30 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, assertion = rule => { inside(rule.settings) { case Settings(service, permittedGroupsLogic, users) => service.id should be(ExternalAuthorizationService.Name("GroupsService1")) + service.serviceTimeout.value should be(5 seconds) service shouldBe a[CacheableExternalAuthorizationServiceDecorator] + val cachableService = service.asInstanceOf[CacheableExternalAuthorizationServiceDecorator] + cachableService.underlying shouldBe a[HttpExternalAuthorizationService] + val httpService = cachableService.underlying.asInstanceOf[HttpExternalAuthorizationService] + httpService.config should be(Config( + uri = uriFrom("http://localhost:8080/groups"), + method = SupportedHttpMethod.Get, + tokenName = AuthTokenName("user"), + groupsConfig = GroupsConfig( + idsConfig = GroupIdsConfig(jsonPathFrom("$..groups[?(@.id)].id")), + namesConfig = None + ), + authTokenSendMethod = AuthTokenSendMethod.UsingQueryParam, + defaultHeaders = Set.empty, + defaultQueryParams = Set.empty + )) permittedGroupsLogic should be( GroupsLogic.Or(PermittedGroupIds(UniqueNonEmptyList.of(GroupId("group3")))) ) @@ -176,13 +225,26 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, assertion = rule => { inside(rule.settings) { case Settings(service, permittedGroupsLogic, users) => service.id should be(ExternalAuthorizationService.Name("GroupsService1")) + service.serviceTimeout.value should be(5 seconds) service shouldBe a[HttpExternalAuthorizationService] + service.asInstanceOf[HttpExternalAuthorizationService].config should be(Config( + uri = uriFrom("http://localhost:8080/groups"), + method = SupportedHttpMethod.Get, + tokenName = AuthTokenName("user"), + groupsConfig = GroupsConfig( + idsConfig = GroupIdsConfig(jsonPathFrom("$..groups[?(@.id)].id")), + namesConfig = None + ), + authTokenSendMethod = AuthTokenSendMethod.UsingQueryParam, + defaultHeaders = Set.empty, + defaultQueryParams = Set.empty + )) permittedGroupsLogic should be( GroupsLogic.Or(PermittedGroupIds(UniqueNonEmptyList.of(GroupId("group3")))) ) @@ -191,6 +253,109 @@ class ExternalAuthorizationRuleSettingsTests } ) } + "old format of group IDs json path property is used" in { + assertDecodingSuccess( + yaml = + """ + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | auth_key_sha1: "d27aaf7fa3c1603948bb29b7339f2559dc02019a" + | groups_provider_authorization: + | user_groups_provider: "GroupsService1" + | groups: ["g*"] + | users: user1 + | + | user_groups_providers: + | + | - name: GroupsService1 + | groups_endpoint: "http://localhost:8080/groups" + | auth_token_name: "user" + | auth_token_passed_as: QUERY_PARAM + | response_groups_json_path: "$..groups[?(@.id)].id" + | + |""".stripMargin, + httpClientsFactory = mockedHttpClientsFactory, + assertion = rule => { + inside(rule.settings) { case Settings(service, permittedGroupsLogic, users) => + service.id should be(ExternalAuthorizationService.Name("GroupsService1")) + service.serviceTimeout.value should be(5 seconds) + service shouldBe a[HttpExternalAuthorizationService] + service.asInstanceOf[HttpExternalAuthorizationService].config should be(Config( + uri = uriFrom("http://localhost:8080/groups"), + method = SupportedHttpMethod.Get, + tokenName = AuthTokenName("user"), + groupsConfig = GroupsConfig( + idsConfig = GroupIdsConfig(jsonPathFrom("$..groups[?(@.id)].id")), + namesConfig = None + ), + authTokenSendMethod = AuthTokenSendMethod.UsingQueryParam, + defaultHeaders = Set.empty, + defaultQueryParams = Set.empty + )) + permittedGroupsLogic should be( + GroupsLogic.Or(PermittedGroupIds(UniqueNonEmptyList.of(GroupIdLike.from("g*")))) + ) + permittedGroupsLogic.permittedGroupIds.permittedGroupIds.groupIds.head shouldBe a[GroupIdPattern] + users should be(UniqueNonEmptyList.of(User.Id("user1"))) + } + } + ) + } + "group names json path is used" in { + assertDecodingSuccess( + yaml = + """ + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | auth_key_sha1: "d27aaf7fa3c1603948bb29b7339f2559dc02019a" + | groups_provider_authorization: + | user_groups_provider: "GroupsService1" + | groups: ["g*"] + | users: user1 + | + | user_groups_providers: + | + | - name: GroupsService1 + | groups_endpoint: "http://localhost:8080/groups" + | auth_token_name: "user" + | auth_token_passed_as: QUERY_PARAM + | response_group_ids_json_path: "$..groups[?(@.id)].id" + | response_group_names_json_path: "$..groups[?(@.name)].name" + | + |""".stripMargin, + httpClientsFactory = mockedHttpClientsFactory, + assertion = rule => { + inside(rule.settings) { case Settings(service, permittedGroupsLogic, users) => + service.id should be(ExternalAuthorizationService.Name("GroupsService1")) + service.serviceTimeout.value should be(5 seconds) + service shouldBe a[HttpExternalAuthorizationService] + service.asInstanceOf[HttpExternalAuthorizationService].config should be(Config( + uri = uriFrom("http://localhost:8080/groups"), + method = SupportedHttpMethod.Get, + tokenName = AuthTokenName("user"), + groupsConfig = GroupsConfig( + idsConfig = GroupIdsConfig(jsonPathFrom("$..groups[?(@.id)].id")), + namesConfig = Some(GroupNamesConfig(jsonPathFrom("$..groups[?(@.name)].name"))) + ), + authTokenSendMethod = AuthTokenSendMethod.UsingQueryParam, + defaultHeaders = Set.empty, + defaultQueryParams = Set.empty + )) + permittedGroupsLogic should be( + GroupsLogic.Or(PermittedGroupIds(UniqueNonEmptyList.of(GroupIdLike.from("g*")))) + ) + permittedGroupsLogic.permittedGroupIds.permittedGroupIds.groupIds.head shouldBe a[GroupIdPattern] + users should be(UniqueNonEmptyList.of(User.Id("user1"))) + } + } + ) + } "authorization service definition is declared using all available fields" in { assertDecodingSuccess( yaml = @@ -211,10 +376,11 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: HEADER - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" + | response_group_names_json_path: "$..groups[?(@.name)].name" | http_method: POST - | default_query_parameters: query1:value1,query2:value2 - | default_headers: header1:hValue1, header2:hValue2 + | default_query_parameters: query1:value1;query2:value2 + | default_headers: header1:hValue1; header2:hValue2 | cache_ttl_in_sec: 100 | validate: false |""".stripMargin, @@ -222,7 +388,24 @@ class ExternalAuthorizationRuleSettingsTests assertion = rule => { inside(rule.settings) { case Settings(service, permittedGroupsLogic, users) => service.id should be(ExternalAuthorizationService.Name("GroupsService1")) + service.serviceTimeout.value should be(5 seconds) service shouldBe a[CacheableExternalAuthorizationServiceDecorator] + val cacheableService = service.asInstanceOf[CacheableExternalAuthorizationServiceDecorator] + cacheableService.ttl.value should be(100 seconds) + cacheableService.underlying shouldBe a[HttpExternalAuthorizationService] + val underlyingService = cacheableService.underlying.asInstanceOf[HttpExternalAuthorizationService] + underlyingService.config should be(Config( + uri = uriFrom("http://localhost:8080/groups"), + method = SupportedHttpMethod.Post, + tokenName = AuthTokenName("user"), + groupsConfig = GroupsConfig( + idsConfig = GroupIdsConfig(jsonPathFrom("$..groups[?(@.id)].id")), + namesConfig = Some(GroupNamesConfig(jsonPathFrom("$..groups[?(@.name)].name"))) + ), + authTokenSendMethod = AuthTokenSendMethod.UsingHeader, + defaultHeaders = Set(Header(("header1", "hValue1")), Header(("header2", "hValue2"))), + defaultQueryParams = Set(QueryParam("query1", "value1"), QueryParam("query2", "value2")) + )) permittedGroupsLogic should be(GroupsLogic.Or( PermittedGroupIds(UniqueNonEmptyList.of(GroupIdLike.from("g*"), GroupIdLike.from("r1"))) )) @@ -253,10 +436,11 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: HEADER - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" + | response_group_names_json_path: "$..groups[?(@.name)].name" | http_method: POST - | default_query_parameters: query1:value1,query2:value2 - | default_headers: header1:hValue1, header2:hValue2 + | default_query_parameters: query1:value1;query2:value2 + | default_headers: header1:hValue1; header2:hValue2 | cache_ttl_in_sec: 100 | http_connection_settings: | connection_timeout_in_sec: 1 @@ -268,7 +452,24 @@ class ExternalAuthorizationRuleSettingsTests assertion = rule => { inside(rule.settings) { case Settings(service, permittedGroupsLogic, users) => service.id should be(ExternalAuthorizationService.Name("GroupsService1")) + service.serviceTimeout.value should be(10 seconds) service shouldBe a[CacheableExternalAuthorizationServiceDecorator] + val cacheableService = service.asInstanceOf[CacheableExternalAuthorizationServiceDecorator] + cacheableService.ttl.value should be(100 seconds) + cacheableService.underlying shouldBe a[HttpExternalAuthorizationService] + val underlyingService = cacheableService.underlying.asInstanceOf[HttpExternalAuthorizationService] + underlyingService.config should be(Config( + uri = uriFrom("http://localhost:8080/groups"), + method = SupportedHttpMethod.Post, + tokenName = AuthTokenName("user"), + groupsConfig = GroupsConfig( + idsConfig = GroupIdsConfig(jsonPathFrom("$..groups[?(@.id)].id")), + namesConfig = Some(GroupNamesConfig(jsonPathFrom("$..groups[?(@.name)].name"))) + ), + authTokenSendMethod = AuthTokenSendMethod.UsingHeader, + defaultHeaders = Set(Header(("header1", "hValue1")), Header(("header2", "hValue2"))), + defaultQueryParams = Set(QueryParam("query1", "value1"), QueryParam("query2", "value2")) + )) permittedGroupsLogic should be( GroupsLogic.Or(PermittedGroupIds(UniqueNonEmptyList.of(GroupId("group3")))) ) @@ -298,7 +499,7 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, @@ -333,7 +534,7 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, @@ -365,7 +566,7 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, @@ -417,7 +618,7 @@ class ExternalAuthorizationRuleSettingsTests | - groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, @@ -427,7 +628,7 @@ class ExternalAuthorizationRuleSettingsTests """- groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: "QUERY_PARAM" - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" |""".stripMargin ))) } @@ -453,13 +654,13 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | | - name: GroupsService1 | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user2" | auth_token_passed_as: HEADER - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, assertion = errors => { @@ -489,7 +690,7 @@ class ExternalAuthorizationRuleSettingsTests | - name: GroupsService1 | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, @@ -499,7 +700,7 @@ class ExternalAuthorizationRuleSettingsTests """- name: "GroupsService1" | auth_token_name: "user" | auth_token_passed_as: "QUERY_PARAM" - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" |""".stripMargin ))) } @@ -525,7 +726,7 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://malformed@{user}:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, assertion = errors => { @@ -553,7 +754,7 @@ class ExternalAuthorizationRuleSettingsTests | - name: GroupsService1 | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, assertion = errors => { @@ -562,7 +763,7 @@ class ExternalAuthorizationRuleSettingsTests """- name: "GroupsService1" | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" |""".stripMargin ))) } @@ -588,12 +789,12 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user2" | auth_token_passed_as: BODY - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, assertion = errors => { errors should have size 1 - errors.head should be(DefinitionsLevelCreationError(Message("Unknown value 'BODY' of 'auth_token_passed_as' attribute"))) + errors.head should be(DefinitionsLevelCreationError(Message("Unknown value 'BODY' of 'auth_token_passed_as' attribute. Supported: 'HEADER', 'QUERY_PARAM'"))) } ) } @@ -621,17 +822,13 @@ class ExternalAuthorizationRuleSettingsTests httpClientsFactory = mockedHttpClientsFactory, assertion = errors => { errors should have size 1 - errors.head should be(DefinitionsLevelCreationError(MalformedValue( - """- name: "GroupsService1" - | groups_endpoint: "http://localhost:8080/groups" - | auth_token_name: "user" - | auth_token_passed_as: "QUERY_PARAM" - |""".stripMargin + errors.head should be(DefinitionsLevelCreationError(Message( + "External authorization service 'GroupsService1' configuration is missing the 'response_group_ids_json_path' attribute" ))) } ) } - "authorization service groups JSON path is malformed" in { + "authorization service group IDs JSON path is malformed" in { assertDecodingFailure( yaml = """ @@ -651,7 +848,65 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?.name)].name_malformed" + | response_group_ids_json_path: "$..groups[?.id)].id_malformed" + |""".stripMargin, + httpClientsFactory = mockedHttpClientsFactory, + assertion = errors => { + errors should have size 1 + errors.head should be(DefinitionsLevelCreationError(Message("Cannot compile '$..groups[?.id)].id_malformed' to JSON path"))) + } + ) + } + "authorization service group IDs JSON path is not defined" in { + assertDecodingFailure( + yaml = + """ + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | groups_provider_authorization: + | user_groups_provider: GroupsService1 + | groups: ["group3"] + | users: ["user1", "user2"] + | + | user_groups_providers: + | + | - name: GroupsService1 + | groups_endpoint: "http://localhost:8080/groups" + | auth_token_name: "user" + | auth_token_passed_as: QUERY_PARAM + |""".stripMargin, + httpClientsFactory = mockedHttpClientsFactory, + assertion = errors => { + errors should have size 1 + errors.head should be(DefinitionsLevelCreationError(Message("External authorization service 'GroupsService1' configuration is missing the 'response_group_ids_json_path' attribute"))) + } + ) + } + "authorization service group names JSON path is malformed" in { + assertDecodingFailure( + yaml = + """ + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | groups_provider_authorization: + | user_groups_provider: GroupsService1 + | groups: ["group3"] + | users: ["user1", "user2"] + | + | user_groups_providers: + | + | - name: GroupsService1 + | groups_endpoint: "http://localhost:8080/groups" + | auth_token_name: "user" + | auth_token_passed_as: QUERY_PARAM + | response_group_ids_json_path: "$..groups[?(@.id)].id" + | response_group_names_json_path: "$..groups[?.name)].name_malformed" |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, assertion = errors => { @@ -660,7 +915,7 @@ class ExternalAuthorizationRuleSettingsTests } ) } - "authorization service auth token name is not defined" in { + "authorization service group IDs JSON path is defined in the old and new syntax at once" in { assertDecodingFailure( yaml = """ @@ -678,8 +933,40 @@ class ExternalAuthorizationRuleSettingsTests | | - name: GroupsService1 | groups_endpoint: "http://localhost:8080/groups" + | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" + |""".stripMargin, + httpClientsFactory = mockedHttpClientsFactory, + assertion = errors => { + errors should have size 1 + errors.head should be(DefinitionsLevelCreationError(Message( + "External authorization service 'GroupsService1' configuration cannot have the 'response_groups_json_path' and 'response_group_ids_json_path' attributes defined at the same time" + ))) + } + ) + } + "authorization service auth token name is not defined" in { + assertDecodingFailure( + yaml = + """ + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | groups_provider_authorization: + | user_groups_provider: GroupsService1 + | groups: ["group3"] + | users: ["user1", "user2"] + | + | user_groups_providers: + | + | - name: GroupsService1 + | groups_endpoint: "http://localhost:8080/groups" + | auth_token_passed_as: QUERY_PARAM + | response_group_ids_json_path: "$..groups[?(@.id)].id" |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, assertion = errors => { @@ -688,7 +975,7 @@ class ExternalAuthorizationRuleSettingsTests """- name: "GroupsService1" | groups_endpoint: "http://localhost:8080/groups" | auth_token_passed_as: "QUERY_PARAM" - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" |""".stripMargin ))) } @@ -714,7 +1001,7 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, assertion = errors => { @@ -746,7 +1033,7 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | cache_ttl_in_sec: hundred |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, @@ -776,7 +1063,7 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | cache_ttl_in_sec: -120 |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, @@ -806,13 +1093,13 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | http_method: DELETE |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, assertion = errors => { errors should have size 1 - errors.head should be(DefinitionsLevelCreationError(Message("Unknown value 'DELETE' of 'http_method' attribute"))) + errors.head should be(DefinitionsLevelCreationError(Message("Unknown value 'DELETE' of 'http_method' attribute. Supported: 'GET', 'POST'"))) } ) } @@ -836,7 +1123,7 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | default_query_parameters: "query:;query1:12345" |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, @@ -866,7 +1153,7 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | default_headers: "header1:;header2:12345" |""".stripMargin, httpClientsFactory = mockedHttpClientsFactory, @@ -896,7 +1183,7 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | validate: true | http_connection_settings: | validate: false @@ -928,7 +1215,7 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | http_connection_settings: | connection_timeout_in_sec: -10 |""".stripMargin, @@ -959,7 +1246,7 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | http_connection_settings: | connection_request_timeout_in_sec: -10 |""".stripMargin, @@ -990,7 +1277,7 @@ class ExternalAuthorizationRuleSettingsTests | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$..groups[?(@.name)].name" + | response_group_ids_json_path: "$..groups[?(@.id)].id" | http_connection_settings: | connection_pool_size: -10 |""".stripMargin, diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/GroupsRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/GroupsRuleSettingsTests.scala index 28e5a12f33..22e539670f 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/GroupsRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/GroupsRuleSettingsTests.scala @@ -148,6 +148,42 @@ sealed abstract class GroupsRuleSettingsTests[R <: BaseGroupsRule : ClassTag](ru } ) } + "structured groups format is used" in { + assertDecodingSuccess( + yaml = + s""" + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | ${ruleName.name.value}: group1 + | + | users: + | - username: cartman + | groups: + | - id: "group1" + | name: "Group 1" + | - id: "group3" + | name: "Group 3" + | auth_key: "cartman:pass" + | + |""".stripMargin, + assertion = rule => { + val groups = ResolvablePermittedGroupIds(UniqueNonEmptyList.of(AlreadyResolved(GroupId("group1").nel))) + rule.settings.permittedGroupIds should be(groups) + rule.settings.usersDefinitions.length should be(1) + inside(rule.settings.usersDefinitions.head) { case UserDef(_, patterns, WithoutGroupsMapping(authRule, localGroups)) => + patterns should be(UserIdPatterns(UniqueNonEmptyList.of(User.UserIdPattern(User.Id("cartman"))))) + localGroups should be(UniqueNonEmptyList.of(group("group1", "Group 1"), group("group3", "Group 3"))) + authRule shouldBe an[AuthKeyRule] + authRule.asInstanceOf[AuthKeyRule].settings should be { + BasicAuthenticationRule.Settings(Credentials(User.Id("cartman"), PlainTextSecret("pass"))) + } + } + } + ) + } } "several groups are defined" when { "no variables are used in group ids" in { @@ -255,7 +291,11 @@ sealed abstract class GroupsRuleSettingsTests[R <: BaseGroupsRule : ClassTag](ru | groups_or: ["group3"] | | - username: morgan - | groups: ["group2", "group3"] + | groups: + | - id: group2 + | name: Group 2 + | - id: group3 + | name: Group 3 | auth_key_sha1: "d27aaf7fa3c1603948bb29b7339f2559dc02019a" | | ldaps: @@ -271,7 +311,7 @@ sealed abstract class GroupsRuleSettingsTests[R <: BaseGroupsRule : ClassTag](ru | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$$..groups[?(@.name)].name" + | response_group_ids_json_path: "$$..groups[?(@.id)].id" | |""".stripMargin, assertion = rule => { @@ -297,7 +337,7 @@ sealed abstract class GroupsRuleSettingsTests[R <: BaseGroupsRule : ClassTag](ru } inside(sortedUserDefinitions.tail.head) { case UserDef(_, patterns, WithoutGroupsMapping(rule1, localGroups)) => patterns should be(UserIdPatterns(UniqueNonEmptyList.of(User.UserIdPattern(User.Id("morgan"))))) - localGroups should be(UniqueNonEmptyList.of(group("group2"), group("group3"))) + localGroups should be(UniqueNonEmptyList.of(group("group2", "Group 2"), group("group3", "Group 3"))) rule1 shouldBe an[AuthKeySha1Rule] rule1.asInstanceOf[AuthKeySha1Rule].settings should be { BasicAuthenticationRule.Settings(HashedUserAndPassword("d27aaf7fa3c1603948bb29b7339f2559dc02019a")) @@ -361,7 +401,7 @@ sealed abstract class GroupsRuleSettingsTests[R <: BaseGroupsRule : ClassTag](ru } ) } - "non-simple groups mapping is used" in { + "advanced groups mapping with groups in a simple format is used" in { assertDecodingSuccess( yaml = s""" @@ -395,7 +435,7 @@ sealed abstract class GroupsRuleSettingsTests[R <: BaseGroupsRule : ClassTag](ru | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$$..groups[?(@.name)].name" + | response_group_ids_json_path: "$$..groups[?(@.id)].id" | |""".stripMargin, assertion = rule => { @@ -413,6 +453,76 @@ sealed abstract class GroupsRuleSettingsTests[R <: BaseGroupsRule : ClassTag](ru Mapping(group("group2"), UniqueNonEmptyList.of(GroupId("ldap_group4"))) ))) + rule1 shouldBe an[AuthKeyRule] + rule1.asInstanceOf[AuthKeyRule].settings should be { + BasicAuthenticationRule.Settings(Credentials(User.Id("cartman"), PlainTextSecret("pass"))) + } + rule2 shouldBe an[ExternalAuthorizationRule] + rule2.asInstanceOf[ExternalAuthorizationRule].settings.permittedGroupsLogic should be( + GroupsLogic.Or(PermittedGroupIds(UniqueNonEmptyList.of(GroupId("ldap_group3"), GroupId("ldap_group4")))) + ) + } + } + ) + } + "advanced groups mapping with structured groups is used" in { + assertDecodingSuccess( + yaml = + s""" + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | ${ruleName.name.value}: [group1, group3] + | + | users: + | - username: cartman + | groups: + | - local_group: + | id: group1 + | name: Group 1 + | external_group_ids: ["ldap_group3"] + | - local_group: + | id: group2 + | name: Group 2 + | external_group_ids: ["ldap_group4"] + | auth_key: "cartman:pass" + | groups_provider_authorization: + | user_groups_provider: GroupsService1 + | groups: ["ldap_group3", "ldap_group4"] + | + | ldaps: + | - name: ldap1 + | host: ${SingletonLdapContainers.ldap1.ldapHost} + | port: ${SingletonLdapContainers.ldap1.ldapPort} + | ssl_enabled: false + | search_user_base_DN: "ou=People,dc=example,dc=com" + | search_groups_base_DN: "ou=People,dc=example,dc=com" + | + | user_groups_providers: + | - name: GroupsService1 + | groups_endpoint: "http://localhost:8080/groups" + | auth_token_name: "user" + | auth_token_passed_as: QUERY_PARAM + | response_group_ids_json_path: "$$..groups[?(@.id)].id" + | + |""".stripMargin, + assertion = rule => { + val groups = ResolvablePermittedGroupIds(UniqueNonEmptyList.of( + AlreadyResolved(GroupId("group1").nel), + AlreadyResolved(GroupId("group3").nel) + )) + rule.settings.permittedGroupIds should be(groups) + rule.settings.usersDefinitions.length should be(1) + val sortedUserDefinitions = rule.settings.usersDefinitions + inside(sortedUserDefinitions.head) { case UserDef(_, patterns, WithGroupsMapping(Auth.SeparateRules(rule1, rule2), groupMappings)) => + patterns should be(UserIdPatterns(UniqueNonEmptyList.of(User.UserIdPattern(User.Id("cartman"))))) + groupMappings should be(GroupMappings.Advanced(UniqueNonEmptyList.of( + Mapping(group("group1", "Group 1"), UniqueNonEmptyList.of(GroupId("ldap_group3"))), + Mapping(group("group2", "Group 2"), UniqueNonEmptyList.of(GroupId("ldap_group4"))) + ))) + rule1 shouldBe an[AuthKeyRule] rule1.asInstanceOf[AuthKeyRule].settings should be { BasicAuthenticationRule.Settings(Credentials(User.Id("cartman"), PlainTextSecret("pass"))) @@ -538,7 +648,7 @@ sealed abstract class GroupsRuleSettingsTests[R <: BaseGroupsRule : ClassTag](ru |""".stripMargin, assertion = errors => { errors should have size 1 - errors.head should be(DefinitionsLevelCreationError(Message("Non empty list of user ID patterns are required"))) + errors.head should be(DefinitionsLevelCreationError(Message("Error for field 'username': Non empty list of user ID patterns are required"))) } ) } @@ -561,7 +671,7 @@ sealed abstract class GroupsRuleSettingsTests[R <: BaseGroupsRule : ClassTag](ru |""".stripMargin, assertion = errors => { errors should have size 1 - errors.head should be(DefinitionsLevelCreationError(Message("Non empty list of user ID patterns are required"))) + errors.head should be(DefinitionsLevelCreationError(Message("Error for field 'username': Non empty list of user ID patterns are required"))) } ) } @@ -606,7 +716,7 @@ sealed abstract class GroupsRuleSettingsTests[R <: BaseGroupsRule : ClassTag](ru |""".stripMargin, assertion = errors => { errors should have size 1 - errors.head should be(DefinitionsLevelCreationError(Message("Non empty list of group IDs is required"))) + errors.head should be(DefinitionsLevelCreationError(Message("Non empty list of groups is required"))) } ) } @@ -629,11 +739,11 @@ sealed abstract class GroupsRuleSettingsTests[R <: BaseGroupsRule : ClassTag](ru |""".stripMargin, assertion = errors => { errors should have size 1 - errors.head should be(DefinitionsLevelCreationError(Message("Non empty list of group IDs is required"))) + errors.head should be(DefinitionsLevelCreationError(Message("Non empty list of groups is required"))) } ) } - "only one authentication rule can be defined for user in users section" in { + "mapped group IDs array in user definition is empty" in { assertDecodingFailure( yaml = s""" @@ -642,17 +752,259 @@ sealed abstract class GroupsRuleSettingsTests[R <: BaseGroupsRule : ClassTag](ru | access_control_rules: | | - name: test_block1 - | ${ruleName.name.value}: ["group1"] + | ${ruleName.name.value}: [group1, group3] | | users: | - username: cartman - | groups: ["group1", "group3"] + | groups: + | - local_group: + | id: group1 + | name: Group 1 + | external_group_ids: ["ldap_group3"] + | - local_group: + | id: group2 + | name: Group 2 + | external_group_ids: [] | auth_key: "cartman:pass" - | auth_key_sha1: "d27aaf7fa3c1603948bb29b7339f2559dc02019a" + | groups_provider_authorization: + | user_groups_provider: GroupsService1 + | groups: ["ldap_group3", "ldap_group4"] | - |""".stripMargin, + | ldaps: + | - name: ldap1 + | host: ${SingletonLdapContainers.ldap1.ldapHost} + | port: ${SingletonLdapContainers.ldap1.ldapPort} + | ssl_enabled: false + | search_user_base_DN: "ou=People,dc=example,dc=com" + | search_groups_base_DN: "ou=People,dc=example,dc=com" + | + | user_groups_providers: + | - name: GroupsService1 + | groups_endpoint: "http://localhost:8080/groups" + | auth_token_name: "user" + | auth_token_passed_as: QUERY_PARAM + | response_group_ids_json_path: "$$..groups[?(@.id)].id" + | + |""".stripMargin + , + assertion = errors => { + errors should have size 1 + errors.head should be(DefinitionsLevelCreationError(Message( + "Error for field 'external_group_ids': Non empty list of group IDs or/and patterns is required" + ))) + } + ) + } + "mapped group IDs array in user definition is empty (old syntax)" in { + assertDecodingFailure( + yaml = + s""" + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | ${ruleName.name.value}: [group1, group3] + | + | users: + | - username: cartman + | groups: + | - "group1": [] + | auth_key: "cartman:pass" + | groups_provider_authorization: + | user_groups_provider: GroupsService1 + | groups: ["ldap_group3", "ldap_group4"] + | + | ldaps: + | - name: ldap1 + | host: ${SingletonLdapContainers.ldap1.ldapHost} + | port: ${SingletonLdapContainers.ldap1.ldapPort} + | ssl_enabled: false + | search_user_base_DN: "ou=People,dc=example,dc=com" + | search_groups_base_DN: "ou=People,dc=example,dc=com" + | + | user_groups_providers: + | - name: GroupsService1 + | groups_endpoint: "http://localhost:8080/groups" + | auth_token_name: "user" + | auth_token_passed_as: QUERY_PARAM + | response_group_ids_json_path: "$$..groups[?(@.id)].id" + | + |""".stripMargin + , + assertion = errors => { + errors should have size 1 + errors.head should be(DefinitionsLevelCreationError(Message( + "Error for field 'group1': Non empty list of group IDs or/and patterns is required" + ))) + } + ) + } + "group mappings in user definition contain an empty mapped group ID" in { + assertDecodingFailure( + yaml = + s""" + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | ${ruleName.name.value}: [group1, group3] + | + | users: + | - username: cartman + | groups: + | - local_group: + | id: group1 + | name: Group 1 + | external_group_ids: [""] + | auth_key: "cartman:pass" + | groups_provider_authorization: + | user_groups_provider: GroupsService1 + | groups: ["ldap_group3", "ldap_group4"] + | + | ldaps: + | - name: ldap1 + | host: ${SingletonLdapContainers.ldap1.ldapHost} + | port: ${SingletonLdapContainers.ldap1.ldapPort} + | ssl_enabled: false + | search_user_base_DN: "ou=People,dc=example,dc=com" + | search_groups_base_DN: "ou=People,dc=example,dc=com" + | + | user_groups_providers: + | - name: GroupsService1 + | groups_endpoint: "http://localhost:8080/groups" + | auth_token_name: "user" + | auth_token_passed_as: QUERY_PARAM + | response_group_ids_json_path: "$$..groups[?(@.id)].id" + | + |""".stripMargin + , + assertion = errors => { + errors should have size 1 + errors.head should be(DefinitionsLevelCreationError(Message( + "Error for field 'external_group_ids': Non empty list of group IDs or/and patterns is required" + ))) + } + ) + } + "group ID in user definition is empty" in { + assertDecodingFailure( + yaml = + s""" + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | ${ruleName.name.value}: [group1, group3] + | + | users: + | - username: cartman + | groups: + | - local_group: + | id: "" + | name: Group 1 + | external_group_ids: ["ldap_group3"] + | auth_key: "cartman:pass" + | groups_provider_authorization: + | user_groups_provider: GroupsService1 + | groups: ["ldap_group3", "ldap_group4"] + | + | ldaps: + | - name: ldap1 + | host: ${SingletonLdapContainers.ldap1.ldapHost} + | port: ${SingletonLdapContainers.ldap1.ldapPort} + | ssl_enabled: false + | search_user_base_DN: "ou=People,dc=example,dc=com" + | search_groups_base_DN: "ou=People,dc=example,dc=com" + | + | user_groups_providers: + | - name: GroupsService1 + | groups_endpoint: "http://localhost:8080/groups" + | auth_token_name: "user" + | auth_token_passed_as: QUERY_PARAM + | response_group_ids_json_path: "$$..groups[?(@.id)].id" + | + |""".stripMargin + , + assertion = errors => { + errors should have size 1 + errors.head should be(DefinitionsLevelCreationError(Message( + "Error for field 'id': Group ID cannot be an empty string" + ))) + } + ) + } + "group name in user definition is empty" in { + assertDecodingFailure( + yaml = + s""" + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | ${ruleName.name.value}: [group1, group3] + | + | users: + | - username: cartman + | groups: + | - local_group: + | id: group1 + | name: "" + | external_group_ids: ["ldap_group3"] + | auth_key: "cartman:pass" + | groups_provider_authorization: + | user_groups_provider: GroupsService1 + | groups: ["ldap_group3", "ldap_group4"] + | + | ldaps: + | - name: ldap1 + | host: ${SingletonLdapContainers.ldap1.ldapHost} + | port: ${SingletonLdapContainers.ldap1.ldapPort} + | ssl_enabled: false + | search_user_base_DN: "ou=People,dc=example,dc=com" + | search_groups_base_DN: "ou=People,dc=example,dc=com" + | + | user_groups_providers: + | - name: GroupsService1 + | groups_endpoint: "http://localhost:8080/groups" + | auth_token_name: "user" + | auth_token_passed_as: QUERY_PARAM + | response_group_ids_json_path: "$$..groups[?(@.id)].id" + | + |""".stripMargin + , assertion = errors => { errors should have size 1 + errors.head should be(DefinitionsLevelCreationError(Message( + "Error for field 'name': Group name cannot be an empty string" + ))) + } + ) + } + } + "only one authentication rule can be defined for user in users section" in { + assertDecodingFailure( + yaml = + s""" + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | ${ruleName.name.value}: ["group1"] + | + | users: + | - username: cartman + | groups: ["group1", "group3"] + | auth_key: "cartman:pass" + | auth_key_sha1: "d27aaf7fa3c1603948bb29b7339f2559dc02019a" + | + |""".stripMargin, + assertion = errors => { + errors should have size 1 errors.head should be(DefinitionsLevelCreationError(Message( """Users definition section external groups mapping feature allows for single rule with authentication | and authorization or two rules which handle authentication and authorization separately. 'auth_key' @@ -696,7 +1048,7 @@ sealed abstract class GroupsRuleSettingsTests[R <: BaseGroupsRule : ClassTag](ru | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$$..groups[?(@.name)].name" + | response_group_ids_json_path: "$$..groups[?(@.id)].id" | |""".stripMargin, assertion = errors => { @@ -780,7 +1132,7 @@ sealed abstract class GroupsRuleSettingsTests[R <: BaseGroupsRule : ClassTag](ru | groups_endpoint: "http://localhost:8080/groups" | auth_token_name: "user" | auth_token_passed_as: QUERY_PARAM - | response_groups_json_path: "$$..groups[?(@.name)].name" + | response_group_ids_json_path: "$$..groups[?(@.id)].id" | |""".stripMargin, assertion = errors => { @@ -869,5 +1221,4 @@ sealed abstract class GroupsRuleSettingsTests[R <: BaseGroupsRule : ClassTag](ru ) } } - } } diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/JwtAuthRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/JwtAuthRuleSettingsTests.scala index 106262ddaa..3a2bb3b574 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/JwtAuthRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/JwtAuthRuleSettingsTests.scala @@ -19,7 +19,7 @@ package tech.beshu.ror.unit.acl.factory.decoders.rules.auth import eu.timepit.refined.auto._ import org.scalamock.scalatest.MockFactory import org.scalatest.matchers.should.Matchers._ -import tech.beshu.ror.accesscontrol.blocks.definitions.JwtDef.SignatureCheckMethod +import tech.beshu.ror.accesscontrol.blocks.definitions.JwtDef.{GroupsConfig, SignatureCheckMethod} import tech.beshu.ror.accesscontrol.blocks.definitions.{CacheableExternalAuthenticationServiceDecorator, JwtDef} import tech.beshu.ror.accesscontrol.blocks.rules.auth.JwtAuthRule import tech.beshu.ror.accesscontrol.blocks.rules.auth.JwtAuthRule.Groups @@ -29,7 +29,6 @@ import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory.HttpClient import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, GeneralReadonlyrestSettingsError, RulesLevelCreationError} -import tech.beshu.ror.com.jayway.jsonpath.JsonPath import tech.beshu.ror.mocks.MockHttpClientsFactoryWithFixedHttpClient import tech.beshu.ror.providers.EnvVarProvider.EnvVarName import tech.beshu.ror.providers.EnvVarsProvider @@ -69,7 +68,7 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(Header.Name.authorization, "Bearer ")) rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.Hmac] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(None) + rule.settings.jwt.groupsConfig should be(None) rule.settings.permittedGroups should be(Groups.NotDefined) } ) @@ -97,7 +96,7 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(Header.Name.authorization, "Bearer ")) rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.Hmac] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(None) + rule.settings.jwt.groupsConfig should be(None) rule.settings.permittedGroups should be(Groups.NotDefined) } ) @@ -127,7 +126,7 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(Header.Name.authorization, "Bearer ")) rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.Hmac] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(None) + rule.settings.jwt.groupsConfig should be(None) rule.settings.permittedGroups should be(Groups.Defined(GroupsLogic.Or(PermittedGroupIds( UniqueNonEmptyList.of(GroupIdLike.from("group1*"), GroupId("group2")) )))) @@ -160,7 +159,7 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(Header.Name.authorization, "Bearer ")) rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.Hmac] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(None) + rule.settings.jwt.groupsConfig should be(None) rule.settings.permittedGroups should be(Groups.Defined(GroupsLogic.And(PermittedGroupIds( UniqueNonEmptyList.of(GroupIdLike.from("group1*"), GroupId("group2")) )))) @@ -192,7 +191,7 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(headerNameFrom("X-JWT-Custom-Header"), "Bearer ")) rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.Hmac] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(None) + rule.settings.jwt.groupsConfig should be(None) rule.settings.permittedGroups should be(Groups.NotDefined) } ) @@ -221,7 +220,7 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(headerNameFrom("X-JWT-Custom-Header"), "MyPrefix ")) rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.Hmac] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(None) + rule.settings.jwt.groupsConfig should be(None) rule.settings.permittedGroups should be(Groups.NotDefined) } ) @@ -249,7 +248,7 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(Header.Name.authorization, "MyPrefix ")) rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.Hmac] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(None) + rule.settings.jwt.groupsConfig should be(None) rule.settings.permittedGroups should be(Groups.NotDefined) } ) @@ -278,7 +277,7 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(headerNameFrom("X-JWT-Custom-Header"), "")) rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.Hmac] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(None) + rule.settings.jwt.groupsConfig should be(None) rule.settings.permittedGroups should be(Groups.NotDefined) } ) @@ -305,41 +304,76 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.id should be(JwtDef.Name("jwt1")) rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(Header.Name.authorization, "Bearer ")) rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.Hmac] - rule.settings.jwt.userClaim should be(Some(domain.Jwt.ClaimName(JsonPath.compile("user")))) - rule.settings.jwt.groupsClaim should be(None) + rule.settings.jwt.userClaim should be(Some(domain.Jwt.ClaimName(jsonPathFrom("user")))) + rule.settings.jwt.groupsConfig should be(None) rule.settings.permittedGroups should be(Groups.NotDefined) } ) } - "roles claim can be enabled in JWT definition" in { + "group IDs claim can be enabled in JWT definition" in { + val claimKeys = List("roles_claim", "groups_claim", "group_ids_claim") + claimKeys.foreach { claimKey => + assertDecodingSuccess( + yaml = + s""" + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | jwt_auth: jwt1 + | + | jwt: + | + | - name: jwt1 + | $claimKey: groups + | signature_key: "123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456" + | + |""".stripMargin, + assertion = rule => { + rule.settings.jwt.id should be(JwtDef.Name("jwt1")) + rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(Header.Name.authorization, "Bearer ")) + rule.settings.jwt.checkMethod shouldBe a[SignatureCheckMethod.Hmac] + rule.settings.jwt.userClaim should be(None) + rule.settings.jwt.groupsConfig should be(Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups")), None))) + rule.settings.permittedGroups should be(Groups.NotDefined) + } + ) + } + } + "group names claim can be enabled in JWT definition" in { assertDecodingSuccess( yaml = - """ - |readonlyrest: - | - | access_control_rules: - | - | - name: test_block1 - | jwt_auth: jwt1 - | - | jwt: - | - | - name: jwt1 - | roles_claim: groups - | signature_key: "123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456" - | - |""".stripMargin, + s""" + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | jwt_auth: jwt1 + | + | jwt: + | + | - name: jwt1 + | group_ids_claim: groups + | group_names_claim: group_names + | signature_key: "123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456" + | + |""".stripMargin, assertion = rule => { rule.settings.jwt.id should be(JwtDef.Name("jwt1")) rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(Header.Name.authorization, "Bearer ")) - rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.Hmac] + rule.settings.jwt.checkMethod shouldBe a[SignatureCheckMethod.Hmac] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(Some(domain.Jwt.ClaimName(JsonPath.compile("groups")))) + rule.settings.jwt.groupsConfig should be(Some(GroupsConfig( + idsClaim = domain.Jwt.ClaimName(jsonPathFrom("groups")), + namesClaim = Some(domain.Jwt.ClaimName(jsonPathFrom("group_names"))) + ))) rule.settings.permittedGroups should be(Groups.NotDefined) } ) } - "roles claim can be enabled in JWT definition and is a http address" in { + "groups claim can be enabled in JWT definition and is a http address" in { assertDecodingSuccess( yaml = """ @@ -353,7 +387,7 @@ class JwtAuthRuleSettingsTests | jwt: | | - name: jwt1 - | roles_claim: "https://{domain}/claims/roles" + | group_ids_claim: "https://{domain}/claims/roles" | signature_key: "123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456" | |""".stripMargin, @@ -362,7 +396,7 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(Header.Name.authorization, "Bearer ")) rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.Hmac] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(Some(domain.Jwt.ClaimName(JsonPath.compile("https://{domain}/claims/roles")))) + rule.settings.jwt.groupsConfig should be(Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("https://{domain}/claims/roles")), None))) rule.settings.permittedGroups should be(Groups.NotDefined) } ) @@ -382,7 +416,7 @@ class JwtAuthRuleSettingsTests | jwt: | | - name: jwt1 - | roles_claim: groups + | group_ids_claim: groups | signature_algo: "RSA" | signature_key: "${Base64.getEncoder.encodeToString(pkey.getEncoded)}" | @@ -392,7 +426,7 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(Header.Name.authorization, "Bearer ")) rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.Rsa] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(Some(domain.Jwt.ClaimName(JsonPath.compile("groups")))) + rule.settings.jwt.groupsConfig should be(Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups")), None))) rule.settings.permittedGroups should be(Groups.NotDefined) } ) @@ -411,7 +445,7 @@ class JwtAuthRuleSettingsTests | jwt: | | - name: jwt1 - | roles_claim: groups + | group_ids_claim: groups | signature_algo: "RSA" | signature_key: "env:SECRET_RSA" | @@ -421,7 +455,7 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(Header.Name.authorization, "Bearer ")) rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.Rsa] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(Some(domain.Jwt.ClaimName(JsonPath.compile("groups")))) + rule.settings.jwt.groupsConfig should be(Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups")), None))) rule.settings.permittedGroups should be(Groups.NotDefined) } ) @@ -442,7 +476,7 @@ class JwtAuthRuleSettingsTests | jwt: | | - name: jwt1 - | roles_claim: groups + | group_ids_claim: groups | signature_algo: "RSA" | signature_key: "@{env:SECRET_RSA}" | @@ -452,7 +486,7 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(Header.Name.authorization, "Bearer ")) rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.Rsa] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(Some(domain.Jwt.ClaimName(JsonPath.compile("groups")))) + rule.settings.jwt.groupsConfig should be(Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups")), None))) rule.settings.permittedGroups should be(Groups.NotDefined) } ) @@ -472,7 +506,7 @@ class JwtAuthRuleSettingsTests | jwt: | | - name: jwt1 - | roles_claim: groups + | group_ids_claim: groups | signature_algo: "EC" | signature_key: "text: ${Base64.getEncoder.encodeToString(pkey.getEncoded)}" | @@ -482,7 +516,7 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.authorizationTokenDef should be(AuthorizationTokenDef(Header.Name.authorization, "Bearer ")) rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.Ec] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(Some(domain.Jwt.ClaimName(JsonPath.compile("groups")))) + rule.settings.jwt.groupsConfig should be(Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups")), None))) rule.settings.permittedGroups should be(Groups.NotDefined) } ) @@ -501,7 +535,7 @@ class JwtAuthRuleSettingsTests | jwt: | | - name: jwt1 - | roles_claim: groups + | group_ids_claim: groups | signature_algo: "NONE" | external_validator: | url: "http://192.168.0.1:8080/jwt" @@ -517,7 +551,7 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.NoCheck] rule.settings.jwt.checkMethod.asInstanceOf[SignatureCheckMethod.NoCheck].service shouldBe a[CacheableExternalAuthenticationServiceDecorator] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(Some(domain.Jwt.ClaimName(JsonPath.compile("groups")))) + rule.settings.jwt.groupsConfig should be(Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups")),None))) rule.settings.permittedGroups should be(Groups.NotDefined) } ) @@ -536,7 +570,7 @@ class JwtAuthRuleSettingsTests | jwt: | | - name: jwt1 - | roles_claim: groups + | group_ids_claim: groups | signature_algo: "NONE" | external_validator: | url: "http://192.168.0.1:8080/jwt" @@ -555,7 +589,7 @@ class JwtAuthRuleSettingsTests rule.settings.jwt.checkMethod shouldBe a [SignatureCheckMethod.NoCheck] rule.settings.jwt.checkMethod.asInstanceOf[SignatureCheckMethod.NoCheck].service shouldBe a[CacheableExternalAuthenticationServiceDecorator] rule.settings.jwt.userClaim should be(None) - rule.settings.jwt.groupsClaim should be(Some(domain.Jwt.ClaimName(JsonPath.compile("groups")))) + rule.settings.jwt.groupsConfig should be(Some(GroupsConfig(domain.Jwt.ClaimName(jsonPathFrom("groups")),None))) rule.settings.permittedGroups should be(Groups.NotDefined) } ) @@ -1075,6 +1109,60 @@ class JwtAuthRuleSettingsTests } ) } + "group IDs claim json path is malformed" in { + assertDecodingFailure( + yaml = + s""" + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | jwt_auth: jwt1 + | + | jwt: + | + | - name: jwt1 + | group_ids_claim: "$$..groups[?.id)].id_malformed" + | signature_key: "123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456" + | + |""".stripMargin, + assertion = errors => { + errors should have size 1 + errors.head should be(DefinitionsLevelCreationError(Message( + """Cannot compile '$..groups[?.id)].id_malformed' to JSON path""".stripMargin + ))) + } + ) + + } + "group names claim json path is malformed" in { + assertDecodingFailure( + yaml = + s""" + |readonlyrest: + | + | access_control_rules: + | + | - name: test_block1 + | jwt_auth: jwt1 + | + | jwt: + | + | - name: jwt1 + | group_ids_claim: groups + | group_names_claim: "$$..groups[?.id)].name_malformed" + | signature_key: "123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456" + | + |""".stripMargin, + assertion = errors => { + errors should have size 1 + errors.head should be(DefinitionsLevelCreationError(Message( + """Cannot compile '$..groups[?.id)].name_malformed' to JSON path""".stripMargin + ))) + } + ) + } } } diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthRuleSettingsTests.scala index b462b14079..fc993ea215 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthRuleSettingsTests.scala @@ -162,7 +162,7 @@ class LdapAuthRuleSettingsTests |""".stripMargin, assertion = errors => { errors should have size 1 - errors.head should be(RulesLevelCreationError(Message("Non empty list of group IDs or/and patters is required"))) + errors.head should be(RulesLevelCreationError(Message("Non empty list of group IDs or/and patterns is required"))) } ) } diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthorizationRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthorizationRuleSettingsTests.scala index 32e7f2a766..a2f5b54446 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthorizationRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthorizationRuleSettingsTests.scala @@ -243,7 +243,7 @@ class LdapAuthorizationRuleSettingsTests |""".stripMargin, assertion = errors => { errors should have size 1 - errors.head should be(RulesLevelCreationError(Message("Non empty list of group IDs or/and patters is required"))) + errors.head should be(RulesLevelCreationError(Message("Non empty list of group IDs or/and patterns is required"))) } ) } diff --git a/core/src/test/scala/tech/beshu/ror/utils/TestsUtils.scala b/core/src/test/scala/tech/beshu/ror/utils/TestsUtils.scala index 1b8ae4b963..384143ddb4 100644 --- a/core/src/test/scala/tech/beshu/ror/utils/TestsUtils.scala +++ b/core/src/test/scala/tech/beshu/ror/utils/TestsUtils.scala @@ -18,6 +18,7 @@ package tech.beshu.ror.utils import better.files.File import cats.data.NonEmptyList +import com.softwaremill.sttp.Uri import eu.timepit.refined.types.string.NonEmptyString import io.circe.ParsingFailure import io.jsonwebtoken.JwtBuilder @@ -48,6 +49,7 @@ import tech.beshu.ror.accesscontrol.domain.User.UserIdPattern import tech.beshu.ror.accesscontrol.domain._ import tech.beshu.ror.configuration.RawRorConfig import tech.beshu.ror.utils.js.{JsCompiler, MozillaJsCompiler} +import tech.beshu.ror.utils.json.JsonPath import tech.beshu.ror.utils.misc.JwtUtils import tech.beshu.ror.utils.uniquelist.{UniqueList, UniqueNonEmptyList} @@ -104,7 +106,9 @@ object TestsUtils { } } - def group(str: NonEmptyString): Group = Group.from(GroupId(str)) + def group(str: String): Group = Group.from(GroupId(NonEmptyString.unsafeFrom(str))) + + def group(id: String, name: String): Group = Group(GroupId(NonEmptyString.unsafeFrom(id)), GroupName(NonEmptyString.unsafeFrom(name))) def clusterIndexName(str: NonEmptyString): ClusterIndexName = ClusterIndexName.unsafeFromString(str.value) @@ -141,13 +145,15 @@ object TestsUtils { def indexPattern(str: NonEmptyString): IndexPattern = IndexPattern(clusterIndexName(str)) + def userId(str: NonEmptyString): User.Id = User.Id(str) + implicit def scalaFiniteDuration2JavaDuration(duration: FiniteDuration): Duration = Duration.ofMillis(duration.toMillis) def impersonatorDefFrom(userIdPattern: NonEmptyString, impersonatorCredentials: Credentials, impersonatedUsersIdPatterns: NonEmptyList[NonEmptyString]): ImpersonatorDef = { ImpersonatorDef( - UserIdPatterns(UniqueNonEmptyList.of(UserIdPattern(User.Id(userIdPattern)))), + UserIdPatterns(UniqueNonEmptyList.of(UserIdPattern(userId(userIdPattern)))), new AuthKeyRule( BasicAuthenticationRule.Settings(impersonatorCredentials), CaseSensitivity.Enabled, @@ -323,7 +329,7 @@ object TestsUtils { } def noGroupMappingFrom(value: String): GroupMappings = - GroupMappings.Simple(UniqueNonEmptyList.of(group(NonEmptyString.unsafeFrom(value)))) + GroupMappings.Simple(UniqueNonEmptyList.of(group(value))) def groupMapping(mapping: Mapping, mappings: Mapping*): GroupMappings = GroupMappings.Advanced(UniqueNonEmptyList.of(mapping, mappings: _*)) @@ -338,6 +344,10 @@ object TestsUtils { case Left(_) => throw new IllegalArgumentException(s"Cannot convert $value to Token") } + def jsonPathFrom(value: String): JsonPath = JsonPath(value).get + + def uriFrom(value: String): Uri = Uri.parse(value).get + implicit class NonEmptyListOps[T](val value: T) extends AnyVal { def nel: NonEmptyList[T] = NonEmptyList.one(value) } diff --git a/docs/api/ror-internal-api-changelog.md b/docs/api/ror-internal-api-changelog.md new file mode 100644 index 0000000000..d023d71375 --- /dev/null +++ b/docs/api/ror-internal-api-changelog.md @@ -0,0 +1,25 @@ +# ROR INTERNAL API CHANGELOG + +## 3.0.0 + +Changes: + +* endpoint GET `/_readonlyrest/metadata/current_user`: + + Changes in response body: + * `x-ror-current-group` type changed from string to an object + * `x-ror-available-groups` type changed from an array of strings to an array of objects + +* endpoint GET `/_readonlyrest/admin/config/test/authmock`: + + Changes in response body: + * `groups` type changed from an array of strings to an array of objects for `LDAP` and `EXT_AUTHZ` auth services + +* endpoint POST `/_readonlyrest/admin/config/test/authmock`: + + Changes in request body: + * `groups` type changed from an array of strings to an array of objects for `LDAP` and `EXT_AUTHZ` auth services + +## 2.1.0 + +Changelog for versions <= 2.1.0 is not provided. \ No newline at end of file diff --git a/docs/api/ror-internal-api-swagger.yaml b/docs/api/ror-internal-api-swagger.yaml index af0bcf27e5..0f3406b537 100644 --- a/docs/api/ror-internal-api-swagger.yaml +++ b/docs/api/ror-internal-api-swagger.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: ROR Internal API description: The API is designed for ROR KBN plugin. It should be treated as internal. It means that backward compatibility won't be a first-class thing for us. - version: 2.1.0 + version: 3.0.0 paths: /_readonlyrest/metadata/current_user: @@ -16,7 +16,7 @@ paths: - $ref: '#/components/parameters/XRorCurrentGroupHeader' responses: 200: - description: Contains currently configured Test Settings or business error + description: Contains current user metadata or business error content: application/json: schema: @@ -565,8 +565,9 @@ components: items: $ref: '#/components/schemas/Group' x-ror-current-group: - type: string description: the user's group that will be selected in tenancy selector + allOf: + - $ref: '#/components/schemas/Group' x-ror-kibana_access: type: string description: defines the minimum set of actions necessary for browsers to use Kibana. @@ -600,9 +601,21 @@ components: type: object description: json with metadata ([key, value] pairs) from the matched block - Group: + GroupId: type: string - description: It's a group that the user belongs to. It is used in tenancy selector in ROR Kibana + description: It's a group ID that the user belongs to + + Group: + type: object + required: + - id + - name + properties: + id: + $ref: '#/components/schemas/GroupId' + name: + type: string + description: It's a human-readable group name used in tenancy selector in ROR Kibana KibanaAllowedApiPath: type: object @@ -1136,13 +1149,13 @@ components: oneOf: - $ref: '#/components/schemas/LdapAuthorizationService' - $ref: '#/components/schemas/ExternalAuthenticationService' - - $ref: '#/components/schemas/ExternalAuthortizationService' + - $ref: '#/components/schemas/ExternalAuthorizationService' discriminator: propertyName: type mapping: LDAP: '#/components/schemas/LdapAuthorizationService' EXT_AUTHN: '#/components/schemas/ExternalAuthenticationService' - EXT_AUTHZ: '#/components/schemas/ExternalAuthortizationService' + EXT_AUTHZ: '#/components/schemas/ExternalAuthorizationService' LdapAuthorizationService: type: object @@ -1204,7 +1217,7 @@ components: items: $ref: '#/components/schemas/MockServiceUsers' - ExternalAuthortizationService: + ExternalAuthorizationService: type: object required: - type @@ -1220,10 +1233,10 @@ components: example: "LDAP 1" mock: oneOf: - - $ref: '#/components/schemas/ExternalAuthortizationServiceMock' + - $ref: '#/components/schemas/ExternalAuthorizationServiceMock' - $ref: '#/components/schemas/MockServiceNotConfigured' - ExternalAuthortizationServiceMock: + ExternalAuthorizationServiceMock: type: object required: - users @@ -1264,11 +1277,22 @@ components: example: John Doe MockServiceUserGroup: - type: string + type: object description: User's group returned by mocked Service + required: + - id + properties: + id: + type: string + description: It's a group ID that the user belongs to + name: + type: string + description: It's a human-readable group name used in the tenancy selector in ROR Kibana. If not defined, the group ID is used as the group name example: - - Developer - - Manager + - id: DeveloperGroup + name: Developer + - id: ManagerGroup + name: Manager MockServiceNotConfigured: type: string @@ -1295,9 +1319,9 @@ components: name: X-ROR-Current-Group in: header required: false - description: The selected user's group in the tenancy selector + description: The selected user's group ID in the tenancy selector schema: - $ref: '#/components/schemas/Group' + $ref: '#/components/schemas/GroupId' examples: @@ -1306,18 +1330,24 @@ components: value: x-ror-correlation-id: 7D2695DF-C2CC-419A-9D0D-8A309D429786 x-ror-username: admin - x-ror-current-group: administration + x-ror-current-group: + id: administration_group + name: administration x-ror-available-groups: - - administration - - management - - users + - id: administration_group + name: administration + - id: management_group + name: management + - id: users_group + name: users x-ror-kibana_index: .kibana_7.17_admin x-ror-kibana_access: admin x-ror-kibana-allowed-api-paths: - http_method: ANY path_regex: "^/api/index_management/indices$" - http_method: ANY - path_regex: "^\/api\/spaces\/.*$" + path_regex: >- + ^\/api\/spaces\/.*$ - http_method: GET path_regex: "^/api/saved_objects/.*$" x-ror-kibana-metadata: @@ -1555,21 +1585,26 @@ components: users: - name: JohnDoe groups: - - Developer - - DevOps + - id: DeveloperGroup + name: Developer + - id: DevOpsGroup + name: DevOps - name: RobertSmith groups: - - Manager + - id: ManagerGroup + name: Manager - type: LDAP name: LDAP 2 mock: users: - name: JohnDoe groups: - - DevOps + - id: DevOpsGroup + name: DevOps - name: JudyBrown groups: - - Customer + - id: CustomerGroup + name: Customer - type: EXT_AUTHN name: ACME1 External Authentication Service mock: @@ -1583,10 +1618,12 @@ components: users: - name: JaimeRhynes groups: - - Customer + - id: CustomerGroup + name: Customer - name: Martian groups: - - Visitor + - id: VisitorGroup + name: Visitor AllTypesOfAuthServicesConfiguredRequestExample: summary: Three types of currently mockable Auth Services are configured @@ -1598,21 +1635,26 @@ components: users: - name: JohnDoe groups: - - Developer - - DevOps + - id: DeveloperGroup + name: Developer + - id: DevOpsGroup + name: DevOps - name: RobertSmith groups: - - Manager + - id: ManagerGroup + name: Manager - type: LDAP name: LDAP 2 mock: users: - name: JohnDoe groups: - - DevOps + - id: DevOpsGroup + name: DevOps - name: JudyBrown groups: - - Customer + - id: CustomerGroup + name: Customer - type: EXT_AUTHN name: ACME1 External Authentication Service mock: @@ -1626,10 +1668,12 @@ components: users: - name: JaimeRhynes groups: - - Customer + - id: CustomerGroup + name: Customer - name: Martian groups: - - Visitor + - id: VisitorGroup + name: Visitor NoAuthServiceConfiguredResponseExample: summary: Three types of currently mockable Auth Services are not configured yet @@ -1677,21 +1721,26 @@ components: users: - name: JohnDoe groups: - - Developer - - DevOps + - id: DeveloperGroup + name: Developer + - id: DevOpsGroup + name: DevOps - name: RobertSmith groups: - - Manager + - id: ManagerGroup + name: Manager - type: LDAP name: LDAP 2 mock: users: - name: JohnDoe groups: - - DevOps + - id: DevOpsGroup + name: DevOps - name: JudyBrown groups: - - Customer + - id: CustomerGroup + name: Customer - type: EXT_AUTHN name: ACME1 External Authentication Service mock: NOT_CONFIGURED @@ -1709,21 +1758,26 @@ components: users: - name: JohnDoe groups: - - Developer - - DevOps + - id: DeveloperGroup + name: Developer + - id: DevOpsGroup + name: DevOps - name: RobertSmith groups: - - Manager + - id: ManagerGroup + name: Manager - type: LDAP name: LDAP 2 mock: users: - name: JohnDoe groups: - - DevOps + - id: DevOpsGroup + name: DevOps - name: JudyBrown groups: - - Customer + - id: CustomerGroup + name: Customer - type: EXT_AUTHN name: ACME1 External Authentication Service mock: NOT_CONFIGURED diff --git a/es68x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es68x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index c943bf981c..b895a320ab 100644 --- a/es68x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es68x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -21,11 +21,8 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { @@ -35,121 +32,11 @@ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) } override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index c943bf981c..b895a320ab 100644 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -21,11 +21,8 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { @@ -35,121 +32,11 @@ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) } override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index bfdb2bad10..ca524c5a22 100644 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -21,131 +21,18 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index bfdb2bad10..ca524c5a22 100644 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -21,131 +21,18 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index bfdb2bad10..ca524c5a22 100644 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -21,131 +21,18 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index 190d3cd0eb..2f6d826002 100644 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -22,131 +22,18 @@ import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index 190d3cd0eb..2f6d826002 100644 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -22,131 +22,18 @@ import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index c943bf981c..b895a320ab 100644 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -21,11 +21,8 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { @@ -35,121 +32,11 @@ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) } override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index bfdb2bad10..ca524c5a22 100644 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -21,131 +21,18 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index bfdb2bad10..ca524c5a22 100644 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -21,131 +21,18 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index bfdb2bad10..ca524c5a22 100644 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -21,131 +21,18 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index bfdb2bad10..ca524c5a22 100644 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -21,131 +21,18 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index bfdb2bad10..ca524c5a22 100644 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -21,131 +21,18 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index 190d3cd0eb..2f6d826002 100644 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -22,131 +22,18 @@ import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index 190d3cd0eb..2f6d826002 100644 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -22,131 +22,18 @@ import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index 64c4e6e0b0..ea5e5c12af 100644 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -18,135 +18,22 @@ package tech.beshu.ror.es.actions.rrauthmock import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput -import tech.beshu.ror.es.utils.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.StatusToXContentObject +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status: RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status: RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index 64c4e6e0b0..ea5e5c12af 100644 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -18,135 +18,22 @@ package tech.beshu.ror.es.actions.rrauthmock import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput -import tech.beshu.ror.es.utils.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.StatusToXContentObject +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status: RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status: RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index 64c4e6e0b0..ea5e5c12af 100644 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -18,135 +18,22 @@ package tech.beshu.ror.es.actions.rrauthmock import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput -import tech.beshu.ror.es.utils.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.StatusToXContentObject +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status: RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status: RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index 190d3cd0eb..2f6d826002 100644 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -22,131 +22,18 @@ import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index 190d3cd0eb..2f6d826002 100644 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -22,131 +22,18 @@ import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index 190d3cd0eb..2f6d826002 100644 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -22,131 +22,18 @@ import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index 190d3cd0eb..2f6d826002 100644 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -22,131 +22,18 @@ import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index 190d3cd0eb..2f6d826002 100644 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -22,131 +22,18 @@ import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index 190d3cd0eb..2f6d826002 100644 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -22,131 +22,18 @@ import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index 190d3cd0eb..2f6d826002 100644 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -22,131 +22,18 @@ import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala index 190d3cd0eb..2f6d826002 100644 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala +++ b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrauthmock/RRAuthMockResponse.scala @@ -22,131 +22,18 @@ import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} import tech.beshu.ror.api.AuthMockApi -import tech.beshu.ror.api.AuthMockApi.AuthMockResponse.{Failure, ProvideAuthMock, UpdateAuthMock} -import tech.beshu.ror.api.AuthMockApi.AuthMockService.{ExternalAuthenticationService, ExternalAuthorizationService, LdapAuthorizationService, MockMode} -import tech.beshu.ror.api.AuthMockApi.{AuthMockResponse, AuthMockService} +import tech.beshu.ror.es.utils.XContentBuilderOps._ -import scala.jdk.CollectionConverters._ class RRAuthMockResponse(response: AuthMockApi.AuthMockResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case mock: AuthMockResponse.ProvideAuthMock => mock match { - case ProvideAuthMock.CurrentAuthMocks(services) => currentServicesJson(builder, services) - case ProvideAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - } - case mock: AuthMockResponse.UpdateAuthMock => mock match { - case UpdateAuthMock.Success(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.NotConfigured(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Invalidated(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.UnknownAuthServicesDetected(message) => addResponseJson(builder, response.status, message) - case UpdateAuthMock.Failed(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder + builder.json(response.body) } override def writeTo(out: StreamOutput): Unit = () - override def status(): RestStatus = response match { - case _: AuthMockResponse.ProvideAuthMock => RestStatus.OK - case _: AuthMockResponse.UpdateAuthMock => RestStatus.OK - case failure: AuthMockResponse.Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentServicesJson(builder: XContentBuilder, services: List[AuthMockService]): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("services") - services.foreach { service => - buildForService(builder, service) - } - builder.endArray() - builder.endObject - } - - private def buildForService(builder: XContentBuilder, service: AuthMockService): Unit = { - service match { - case AuthMockService.LdapAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - ldapMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthenticationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthenticationMock(builder, mock) - builder.endObject() - case AuthMockService.ExternalAuthorizationService(name, mock) => - builder.startObject() - builder.field("type", service.serviceType) - builder.field("name", name.value) - externalAuthorizationMock(builder, mock) - builder.endObject() - } - } - - private def externalAuthorizationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } + override def status(): RestStatus = RestStatus.fromCode(response.statusCode.code) - private def externalAuthenticationMock(builder: XContentBuilder, mock: MockMode[ExternalAuthenticationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } - - private def ldapMock(builder: XContentBuilder, mock: MockMode[LdapAuthorizationService.Mock]): Unit = mock match { - case MockMode.Enabled(configuredMock) => - builder.startObject("mock") - builder.startArray("users") - configuredMock.users.foreach { user => - builder.startObject() - builder.field("name", user.name.value) - builder.field("groups", user.groups.map(_.value).asJava) - builder.endObject() - } - builder.endArray() - builder.endObject() - case MockMode.NotConfigured => - builder.field("mock", "NOT_CONFIGURED") - } } diff --git a/gradle.properties b/gradle.properties index 49cad5f125..11196e01be 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ publishedPluginVersion=1.56.0 -pluginVersion=1.57.0-pre2 +pluginVersion=1.57.0-pre3 pluginName=readonlyrest org.gradle.jvmargs=-Xmx6144m diff --git a/integration-tests/src/test/resources/admin_api_mocks/readonlyrest.yml b/integration-tests/src/test/resources/admin_api_mocks/readonlyrest.yml index 82725b4d0e..15e74231a7 100644 --- a/integration-tests/src/test/resources/admin_api_mocks/readonlyrest.yml +++ b/integration-tests/src/test/resources/admin_api_mocks/readonlyrest.yml @@ -113,13 +113,13 @@ readonlyrest: groups_endpoint: "http://{EXT1_HOST}:{EXT1_PORT}/auth" auth_token_name: "user" auth_token_passed_as: QUERY_PARAM - response_groups_json_path: "$..groups[?(@.name)].name" + response_group_ids_json_path: "$..groups[?(@.id)].id" - name: "grp2" groups_endpoint: "http://{EXT1_HOST}:{EXT1_PORT}/auth" auth_token_name: "user" auth_token_passed_as: QUERY_PARAM - response_groups_json_path: "$..groups[?(@.name)].name" + response_group_ids_json_path: "$..groups[?(@.id)].id" impersonation: diff --git a/integration-tests/src/test/resources/admin_api_mocks/wiremock_group_provider1_gpa_user_1.json b/integration-tests/src/test/resources/admin_api_mocks/wiremock_group_provider1_gpa_user_1.json index d92d9c74ff..874ea19dd1 100644 --- a/integration-tests/src/test/resources/admin_api_mocks/wiremock_group_provider1_gpa_user_1.json +++ b/integration-tests/src/test/resources/admin_api_mocks/wiremock_group_provider1_gpa_user_1.json @@ -10,6 +10,6 @@ }, "response": { "status": 200, - "body": "{ \"sth\": \"unknown\", \"groups\": [ { \"name\": \"group4\" }, { \"name\": \"group5\" } ] }" + "body": "{ \"sth\": \"unknown\", \"groups\": [ { \"id\": \"group4\" }, { \"id\": \"group5\" } ] }" } } \ No newline at end of file diff --git a/integration-tests/src/test/resources/admin_api_mocks/wiremock_group_provider2_gpa_user_2.json b/integration-tests/src/test/resources/admin_api_mocks/wiremock_group_provider2_gpa_user_2.json index 58a5c9cd39..2261db0e48 100644 --- a/integration-tests/src/test/resources/admin_api_mocks/wiremock_group_provider2_gpa_user_2.json +++ b/integration-tests/src/test/resources/admin_api_mocks/wiremock_group_provider2_gpa_user_2.json @@ -10,6 +10,6 @@ }, "response": { "status": 200, - "body": "{ \"sth\": \"unknown\", \"groups\": [ { \"name\": \"group4a\" }, { \"name\": \"group5b\" } ] }" + "body": "{ \"sth\": \"unknown\", \"groups\": [ { \"id\": \"group4a\" }, { \"id\": \"group5b\" } ] }" } } \ No newline at end of file diff --git a/integration-tests/src/test/resources/current_user_metadata/readonlyrest.yml b/integration-tests/src/test/resources/current_user_metadata/readonlyrest.yml index 8848a40cf0..b53679b0c3 100644 --- a/integration-tests/src/test/resources/current_user_metadata/readonlyrest.yml +++ b/integration-tests/src/test/resources/current_user_metadata/readonlyrest.yml @@ -70,5 +70,9 @@ readonlyrest: auth_key: "user2:pass" - username: user4 - groups: ["group5", "group6"] + groups: + - id: group5 + name: "Group 5" + - id: group6 + name: "Group 6" auth_key: "user4:pass" \ No newline at end of file diff --git a/integration-tests/src/test/resources/duplicated_response_headers_issue/brian_groups.json b/integration-tests/src/test/resources/duplicated_response_headers_issue/brian_groups.json index f06d404fdd..febf2c79e6 100644 --- a/integration-tests/src/test/resources/duplicated_response_headers_issue/brian_groups.json +++ b/integration-tests/src/test/resources/duplicated_response_headers_issue/brian_groups.json @@ -6,7 +6,7 @@ "response": { "status": 200, "jsonBody": { - "groups":[{ "name": "011" }] + "groups":[{ "id": "011", "name": "Developers" }] } } } \ No newline at end of file diff --git a/integration-tests/src/test/resources/duplicated_response_headers_issue/freddie_groups.json b/integration-tests/src/test/resources/duplicated_response_headers_issue/freddie_groups.json index 0242211754..2e8af4540a 100644 --- a/integration-tests/src/test/resources/duplicated_response_headers_issue/freddie_groups.json +++ b/integration-tests/src/test/resources/duplicated_response_headers_issue/freddie_groups.json @@ -6,7 +6,7 @@ "response": { "status": 200, "jsonBody": { - "groups":[{ "name": "013" }] + "groups":[{ "id": "013", "name": "Managers" }] } } } \ No newline at end of file diff --git a/integration-tests/src/test/resources/duplicated_response_headers_issue/readonlyrest.yml b/integration-tests/src/test/resources/duplicated_response_headers_issue/readonlyrest.yml index 25e21aaec3..36f9fed055 100644 --- a/integration-tests/src/test/resources/duplicated_response_headers_issue/readonlyrest.yml +++ b/integration-tests/src/test/resources/duplicated_response_headers_issue/readonlyrest.yml @@ -79,7 +79,8 @@ readonlyrest: groups_endpoint: "http://{EXT1_HOST}:{EXT1_PORT}/groups" auth_token_name: "token" auth_token_passed_as: QUERY_PARAM # HEADER OR QUERY_PARAM - response_groups_json_path: "$..groups[?(@.name)].name" # see: + response_group_ids_json_path: "$..groups[?(@.id)].id" # see: + response_group_names_json_path: "$..groups[?(@.name)].name" cache_ttl_in_sec: 1 http_connection_settings: connection_timeout_in_sec: 5 # default 2 diff --git a/integration-tests/src/test/resources/impersonation/readonlyrest.yml b/integration-tests/src/test/resources/impersonation/readonlyrest.yml index 0192ed33ae..b24693c9d9 100644 --- a/integration-tests/src/test/resources/impersonation/readonlyrest.yml +++ b/integration-tests/src/test/resources/impersonation/readonlyrest.yml @@ -113,13 +113,13 @@ readonlyrest: groups_endpoint: "http://{EXT1_HOST}:{EXT1_PORT}/auth" auth_token_name: "user" auth_token_passed_as: QUERY_PARAM - response_groups_json_path: "$..groups[?(@.name)].name" + response_group_ids_json_path: "$..groups[?(@.id)].id" - name: "grp2" groups_endpoint: "http://{EXT1_HOST}:{EXT1_PORT}/auth" auth_token_name: "user" auth_token_passed_as: QUERY_PARAM - response_groups_json_path: "$..groups[?(@.name)].name" + response_group_ids_json_path: "$..groups[?(@.id)].id" impersonation: diff --git a/integration-tests/src/test/resources/impersonation/wiremock_group_provider1_gpa_user_1.json b/integration-tests/src/test/resources/impersonation/wiremock_group_provider1_gpa_user_1.json index d92d9c74ff..874ea19dd1 100644 --- a/integration-tests/src/test/resources/impersonation/wiremock_group_provider1_gpa_user_1.json +++ b/integration-tests/src/test/resources/impersonation/wiremock_group_provider1_gpa_user_1.json @@ -10,6 +10,6 @@ }, "response": { "status": 200, - "body": "{ \"sth\": \"unknown\", \"groups\": [ { \"name\": \"group4\" }, { \"name\": \"group5\" } ] }" + "body": "{ \"sth\": \"unknown\", \"groups\": [ { \"id\": \"group4\" }, { \"id\": \"group5\" } ] }" } } \ No newline at end of file diff --git a/integration-tests/src/test/resources/impersonation/wiremock_group_provider2_gpa_user_2.json b/integration-tests/src/test/resources/impersonation/wiremock_group_provider2_gpa_user_2.json index 58a5c9cd39..2261db0e48 100644 --- a/integration-tests/src/test/resources/impersonation/wiremock_group_provider2_gpa_user_2.json +++ b/integration-tests/src/test/resources/impersonation/wiremock_group_provider2_gpa_user_2.json @@ -10,6 +10,6 @@ }, "response": { "status": 200, - "body": "{ \"sth\": \"unknown\", \"groups\": [ { \"name\": \"group4a\" }, { \"name\": \"group5b\" } ] }" + "body": "{ \"sth\": \"unknown\", \"groups\": [ { \"id\": \"group4a\" }, { \"id\": \"group5b\" } ] }" } } \ No newline at end of file diff --git a/integration-tests/src/test/resources/jwt_auth/readonlyrest.yml b/integration-tests/src/test/resources/jwt_auth/readonlyrest.yml index ce5b4fb665..ac069b9b08 100644 --- a/integration-tests/src/test/resources/jwt_auth/readonlyrest.yml +++ b/integration-tests/src/test/resources/jwt_auth/readonlyrest.yml @@ -17,7 +17,7 @@ readonlyrest: type: allow jwt_auth: name: "jwt3" - roles: ["role_viewer", "role_xyz"] + groups: ["role_viewer", "role_xyz"] - name: Valid JWT token is present in custom header and header prefix type: allow @@ -37,7 +37,8 @@ readonlyrest: - name: jwt3 signature_key: "1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890" user_claim: user - roles_claim: roles + group_ids_claim: roles[?(@.id)].id + group_names_claim: roles[?(@.name)].name - name: jwt4 header_name: x-custom-header2 diff --git a/integration-tests/src/test/resources/local_groups/readonlyrest.yml b/integration-tests/src/test/resources/local_groups/readonlyrest.yml index c6b6a32dde..aeddb0e9e5 100644 --- a/integration-tests/src/test/resources/local_groups/readonlyrest.yml +++ b/integration-tests/src/test/resources/local_groups/readonlyrest.yml @@ -31,6 +31,11 @@ readonlyrest: users: - username: user auth_key: user:passwd - groups: ["a_testgroup", "group_extra", "foogroup"] - + groups: + - id: "a_testgroup" + name: "Test group" + - id: "group_extra" + name: "Extra group" + - id: "foogroup" + name: "Foo group" diff --git a/integration-tests/src/test/resources/rev_proxy_groups_provider/readonlyrest.yml b/integration-tests/src/test/resources/rev_proxy_groups_provider/readonlyrest.yml index 9061789cde..44022f210d 100644 --- a/integration-tests/src/test/resources/rev_proxy_groups_provider/readonlyrest.yml +++ b/integration-tests/src/test/resources/rev_proxy_groups_provider/readonlyrest.yml @@ -48,10 +48,10 @@ readonlyrest: groups_endpoint: "http://{GROUPS1_HOST}:{GROUPS1_PORT}/groups" auth_token_name: "user" auth_token_passed_as: QUERY_PARAM - response_groups_json_path: "$..groups[?(@.name)].name" + response_group_ids_json_path: "$..groups[?(@.id)].id" - name: GroupsService2 groups_endpoint: "http://{GROUPS2_HOST}:{GROUPS2_PORT}/groups" auth_token_name: "auth_token" auth_token_passed_as: HEADER - response_groups_json_path: "$..groups[?(@.name)].name" + response_group_ids_json_path: "$..groups[?(@.id)].id" diff --git a/integration-tests/src/test/resources/rev_proxy_groups_provider/wiremock_service1_cartman.json b/integration-tests/src/test/resources/rev_proxy_groups_provider/wiremock_service1_cartman.json index 64fdbe81b4..2b33616201 100644 --- a/integration-tests/src/test/resources/rev_proxy_groups_provider/wiremock_service1_cartman.json +++ b/integration-tests/src/test/resources/rev_proxy_groups_provider/wiremock_service1_cartman.json @@ -11,6 +11,6 @@ }, "response": { "status": 200, - "body": "{ \"sth\": \"unknown\", \"groups\": [ { \"name\": \"group1\" }, { \"name\": \"group2\" } ] }" + "body": "{ \"sth\": \"unknown\", \"groups\": [ { \"id\": \"group1\" }, { \"id\": \"group2\" } ] }" } } \ No newline at end of file diff --git a/integration-tests/src/test/resources/rev_proxy_groups_provider/wiremock_service1_morgan.json b/integration-tests/src/test/resources/rev_proxy_groups_provider/wiremock_service1_morgan.json index b7e2066981..73e1c32a81 100644 --- a/integration-tests/src/test/resources/rev_proxy_groups_provider/wiremock_service1_morgan.json +++ b/integration-tests/src/test/resources/rev_proxy_groups_provider/wiremock_service1_morgan.json @@ -11,6 +11,6 @@ }, "response": { "status": 200, - "body": "{ \"sth\": \"unknown\", \"groups\": [ { \"name\": \"group3\" } ] }" + "body": "{ \"sth\": \"unknown\", \"groups\": [ { \"id\": \"group3\" } ] }" } } \ No newline at end of file diff --git a/integration-tests/src/test/resources/rev_proxy_groups_provider/wiremock_service2_token.json b/integration-tests/src/test/resources/rev_proxy_groups_provider/wiremock_service2_token.json index f3c3371c19..b8b0e97dc4 100644 --- a/integration-tests/src/test/resources/rev_proxy_groups_provider/wiremock_service2_token.json +++ b/integration-tests/src/test/resources/rev_proxy_groups_provider/wiremock_service2_token.json @@ -11,6 +11,6 @@ }, "response": { "status": 200, - "body": "{ \"sth\": \"unknown\", \"groups\": [ { \"name\": \"group3\" }, { \"name\": \"group4\" } ] }" + "body": "{ \"sth\": \"unknown\", \"groups\": [ { \"id\": \"group3\" }, { \"id\": \"group4\" } ] }" } } \ No newline at end of file diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/AdminApiAuthMockSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/AdminApiAuthMockSuite.scala index 1a4354a943..17e4bcc791 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/AdminApiAuthMockSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/AdminApiAuthMockSuite.scala @@ -241,14 +241,23 @@ class AdminApiAuthMockSuite | { | "name": "JohnDoe", | "groups": [ - | "Developer", - | "DevOps" + | { + | "id": "DeveloperGroup", + | "name": "Developer" + | }, + | { + | "id": "DevOpsGroup", + | "name": "DevOps" + | } | ] | }, | { | "name": "RobertSmith", | "groups": [ - | "Manager" + | { + | "id": "ManagerGroup", + | "name": "Manager" + | } | ] | } | ] @@ -262,13 +271,19 @@ class AdminApiAuthMockSuite | { | "name": "JohnDoe", | "groups": [ - | "DevOps" + | { + | "id": "DevOpsGroup", + | "name": "DevOps" + | } | ] | }, | { | "name": "JudyBrown", | "groups": [ - | "Customer" + | { + | "id": "CustomerGroup", + | "name": "Customer" + | } | ] | } | ] @@ -310,16 +325,16 @@ class AdminApiAuthMockSuite | { | "id": "JohnDoe", | "groups": [ - | "Developer", - | "DevOps" + | "DeveloperGroup", + | "DevOpsGroup" | ], | "userGroups": [ | { - | "id": "Developer", + | "id": "DeveloperGroup", | "name": "Developer" | }, | { - | "id": "DevOps", + | "id": "DevOpsGroup", | "name": "DevOps" | } | ] @@ -327,11 +342,11 @@ class AdminApiAuthMockSuite | { | "id": "RobertSmith", | "groups": [ - | "Manager" + | "ManagerGroup" | ], | "userGroups": [ | { - | "id": "Manager", + | "id": "ManagerGroup", | "name": "Manager" | } | ] @@ -343,11 +358,11 @@ class AdminApiAuthMockSuite | { | "id": "JohnDoe", | "groups": [ - | "DevOps" + | "DevOpsGroup" | ], | "userGroups": [ | { - | "id": "DevOps", + | "id": "DevOpsGroup", | "name": "DevOps" | } | ] @@ -355,11 +370,11 @@ class AdminApiAuthMockSuite | { | "id": "JudyBrown", | "groups": [ - | "Customer" + | "CustomerGroup" | ], | "userGroups": [ | { - | "id": "Customer", + | "id": "CustomerGroup", | "name": "Customer" | } | ] @@ -396,14 +411,23 @@ class AdminApiAuthMockSuite | { | "name": "JohnDoe", | "groups": [ - | "Developer", - | "DevOps" + | { + | "id": "DeveloperGroup", + | "name": "Developer" + | }, + | { + | "id": "DevOpsGroup", + | "name": "DevOps" + | } | ] | }, | { | "name": "RobertSmith", | "groups": [ - | "Manager" + | { + | "id": "ManagerGroup", + | "name": "Manager" + | } | ] | } | ] @@ -417,13 +441,19 @@ class AdminApiAuthMockSuite | { | "name": "JohnDoe", | "groups": [ - | "DevOps" + | { + | "id": "DevOpsGroup", + | "name": "DevOps" + | } | ] | }, | { | "name": "JudyBrown", | "groups": [ - | "Customer" + | { + | "id": "CustomerGroup", + | "name": "Customer" + | } | ] | } | ] @@ -462,7 +492,10 @@ class AdminApiAuthMockSuite | { | "name": "JaimeRhynes", | "groups": [ - | "Customer" + | { + | "id": "CustomerGroup", + | "name": "Customer" + | } | ] | } | ] @@ -476,7 +509,10 @@ class AdminApiAuthMockSuite | { | "name": "Martian", | "groups": [ - | "Visitor" + | { + | "id": "VisitorGroup", + | "name": "Visitor" + | } | ] | } | ] @@ -499,16 +535,16 @@ class AdminApiAuthMockSuite | { | "id": "JohnDoe", | "groups": [ - | "Developer", - | "DevOps" + | "DeveloperGroup", + | "DevOpsGroup" | ], | "userGroups": [ | { - | "id": "Developer", + | "id": "DeveloperGroup", | "name": "Developer" | }, | { - | "id": "DevOps", + | "id": "DevOpsGroup", | "name": "DevOps" | } | ] @@ -516,11 +552,11 @@ class AdminApiAuthMockSuite | { | "id": "RobertSmith", | "groups": [ - | "Manager" + | "ManagerGroup" | ], | "userGroups": [ | { - | "id": "Manager", + | "id": "ManagerGroup", | "name": "Manager" | } | ] @@ -532,11 +568,11 @@ class AdminApiAuthMockSuite | { | "id": "JohnDoe", | "groups": [ - | "DevOps" + | "DevOpsGroup" | ], | "userGroups": [ | { - | "id": "DevOps", + | "id": "DevOpsGroup", | "name": "DevOps" | } | ] @@ -544,11 +580,11 @@ class AdminApiAuthMockSuite | { | "id": "JudyBrown", | "groups": [ - | "Customer" + | "CustomerGroup" | ], | "userGroups": [ | { - | "id": "Customer", + | "id": "CustomerGroup", | "name": "Customer" | } | ] @@ -581,11 +617,11 @@ class AdminApiAuthMockSuite | { | "id": "JaimeRhynes", | "groups": [ - | "Customer" + | "CustomerGroup" | ], | "userGroups": [ | { - | "id": "Customer", + | "id": "CustomerGroup", | "name": "Customer" | } | ] @@ -597,11 +633,11 @@ class AdminApiAuthMockSuite | { | "id": "Martian", | "groups": [ - | "Visitor" + | "VisitorGroup" | ], | "userGroups": [ | { - | "id": "Visitor", + | "id": "VisitorGroup", | "name": "Visitor" | } | ] @@ -738,14 +774,23 @@ class AdminApiAuthMockSuite | { | "name": "JohnDoe", | "groups": [ - | "DeveloperGroup", - | "DevOpsGroup" + | { + | "id": "DeveloperGroup", + | "name": "DeveloperGroup" + | }, + | { + | "id": "DevOpsGroup", + | "name": "DevOpsGroup" + | } | ] | }, | { | "name": "RobertSmith", | "groups": [ - | "ManagerGroup" + | { + | "id": "ManagerGroup", + | "name": "ManagerGroup" + | } | ] | } | ] @@ -759,13 +804,19 @@ class AdminApiAuthMockSuite | { | "name": "JohnDoe", | "groups": [ - | "DevOpsGroup" + | { + | "id": "DevOpsGroup", + | "name": "DevOpsGroup" + | } | ] | }, | { | "name": "JudyBrown", | "groups": [ - | "CustomerGroup" + | { + | "id": "CustomerGroup", + | "name": "CustomerGroup" + | } | ] | } | ] @@ -804,7 +855,10 @@ class AdminApiAuthMockSuite | { | "name": "JaimeRhynes", | "groups": [ - | "CustomerGroup" + | { + | "id": "CustomerGroup", + | "name": "CustomerGroup" + | } | ] | } | ] @@ -818,7 +872,10 @@ class AdminApiAuthMockSuite | { | "name": "Martian", | "groups": [ - "VisitorGroup" + | { + | "id": "VisitorGroup", + | "name": "VisitorGroup" + | } | ] | } | ] @@ -855,14 +912,23 @@ class AdminApiAuthMockSuite | { | "name": "JohnDoe", | "groups": [ - | "Developer", - | "DevOps" + | { + | "id": "DeveloperGroup", + | "name": "Developer" + | }, + | { + | "id": "DevOpsGroup", + | "name": "DevOps" + | } | ] | }, | { | "name": "RobertSmith", | "groups": [ - | "Manager" + | { + | "id": "ManagerGroup", + | "name": "Manager" + | } | ] | } | ] @@ -876,13 +942,18 @@ class AdminApiAuthMockSuite | { | "name": "JohnDoe", | "groups": [ - | "DevOps" + | { + | "id": "DevOpsGroup", + | "name": "DevOps" + | } | ] | }, | { | "name": "JudyBrown", | "groups": [ - | "Customer" + | { + | "id": "CustomerGroup" + | } | ] | } | ] @@ -921,7 +992,10 @@ class AdminApiAuthMockSuite | { | "name": "JaimeRhynes", | "groups": [ - | "Customer" + | { + | "id": "CustomerGroup", + | "name": "Customer" + | } | ] | } | ] @@ -935,7 +1009,10 @@ class AdminApiAuthMockSuite | { | "name": "Martian", | "groups": [ - | "Visitor" + | { + | "id": "VisitorGroup", + | "name": "Visitor" + | } | ] | } | ] @@ -964,14 +1041,23 @@ class AdminApiAuthMockSuite | { | "name": "JohnDoe", | "groups": [ - | "Developer", - | "DevOps" + | { + | "id": "DeveloperGroup", + | "name": "Developer" + | }, + | { + | "id": "DevOpsGroup", + | "name": "DevOps" + | } | ] | }, | { | "name": "RobertSmith", | "groups": [ - | "Manager" + | { + | "id": "ManagerGroup", + | "name": "Manager" + | } | ] | } | ] diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CurrentUserMetadataSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CurrentUserMetadataSuite.scala index 0e4f77e158..103e522194 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CurrentUserMetadataSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CurrentUserMetadataSuite.scala @@ -46,8 +46,16 @@ class CurrentUserMetadataSuite result.responseJson should be(ujson.read( s"""{ | "x-ror-username": "user1", - | "x-ror-current-group": "group1", - | "x-ror-available-groups": [ "group1" ], + | "x-ror-current-group": { + | "id": "group1", + | "name": "group1" + | }, + | "x-ror-available-groups": [ + | { + | "id": "group1", + | "name": "group1" + | } + | ], | "x-ror-correlation-id": "$correlationId" |}""".stripMargin)) } @@ -64,8 +72,20 @@ class CurrentUserMetadataSuite result.responseJson should be(ujson.read( s"""{ | "x-ror-username": "user4", - | "x-ror-current-group": "group6", - | "x-ror-available-groups": [ "group5", "group6" ], + | "x-ror-current-group": { + | "id": "group6", + | "name": "Group 6" + | }, + | "x-ror-available-groups": [ + | { + | "id": "group5", + | "name": "Group 5" + | }, + | { + | "id": "group6", + | "name": "Group 6" + | } + | ], | "x-ror-correlation-id": "$correlationId", | "x-ror-kibana_index": "user4_group6_kibana_index", | "x-ror-kibana_template_index": "user4_group6_kibana_template_index", @@ -82,8 +102,16 @@ class CurrentUserMetadataSuite result.responseJson should be(ujson.read( s"""{ | "x-ror-username": "user2", - | "x-ror-current-group": "group2", - | "x-ror-available-groups": [ "group2" ], + | "x-ror-current-group": { + | "id": "group2", + | "name": "group2" + | }, + | "x-ror-available-groups": [ + | { + | "id": "group2", + | "name": "group2" + | } + | ], | "x-ror-correlation-id": "$correlationId", | "x-ror-kibana_index": "user2_kibana_index", | "x-ror-kibana_access": "ro", diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ImpersonationSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ImpersonationSuite.scala index 6b80e50887..baf802927e 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ImpersonationSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ImpersonationSuite.scala @@ -128,7 +128,16 @@ class ImpersonationSuite | "users" : [ | { | "name": "ldap_user_2", - | "groups": ["group1", "group3"] + | "groups": [ + | { + | "id": "group1", + | "name": "group1" + | }, + | { + | "id": "group3", + | "name": "group3" + | } + | ] | } | ] | } @@ -161,7 +170,16 @@ class ImpersonationSuite | "users" : [ | { | "name": "ldap_user_1", - | "groups": ["group1", "group2"] + | "groups": [ + | { + | "id": "group1", + | "name": "group1" + | }, + | { + | "id": "group2", + | "name": "group2" + | } + | ] | } | ] | } @@ -272,8 +290,32 @@ class ImpersonationSuite | "name": "grp2", | "mock": { | "users" : [ - | { "name": "gpa_user_1", "groups": ["group4", "group5"]}, - | { "name": "gpa_user_1a", "groups": ["group4a", "group5a"] } + | { + | "name": "gpa_user_1", + | "groups": [ + | { + | "id": "group4", + | "name": "group4" + | }, + | { + | "id": "group5", + | "name": "group5" + | } + | ] + | }, + | { + | "name": "gpa_user_1a", + | "groups": [ + | { + | "id": "group4a", + | "name": "group4a" + | }, + | { + | "id": "group5a", + | "name": "group5a" + | } + | ] + | } | ] | } | } @@ -303,8 +345,32 @@ class ImpersonationSuite | "name": "grp1", | "mock": { | "users" : [ - | { "name": "gpa_user_1", "groups": ["group4", "group5"]}, - | { "name": "gpa_user_1a", "groups": ["group4a", "group5a"] } + | { + | "name": "gpa_user_1", + | "groups": [ + | { + | "id": "group4", + | "name": "group4" + | }, + | { + | "id": "group5", + | "name": "group5" + | } + | ] + | }, + | { + | "name": "gpa_user_1a", + | "groups": [ + | { + | "id": "group4a", + | "name": "group4a" + | }, + | { + | "id": "group5a", + | "name": "group5a" + | } + | ] + | } | ] | } | } @@ -337,7 +403,16 @@ class ImpersonationSuite | "users" : [ | { | "name": "ldap_user_2", - | "groups": ["group1", "group3"] + | "groups": [ + | { + | "id": "group1", + | "name": "group1" + | }, + | { + | "id": "group3", + | "name": "group3" + | } + | ] | } | ] | } @@ -377,7 +452,16 @@ class ImpersonationSuite | "users" : [ | { | "name": "ldap_user_1", - | "groups": ["group1", "group2"] + | "groups": [ + | { + | "id": "group1", + | "name": "group1" + | }, + | { + | "id": "group2", + | "name": "group2" + | } + | ] | } | ] | } @@ -440,7 +524,16 @@ class ImpersonationSuite | "users" : [ | { | "name": "ldap_user_1", - | "groups": ["group1", "group2"] + | "groups": [ + | { + | "id": "group1", + | "name": "group1" + | }, + | { + | "id": "group2", + | "name": "group2" + | } + | ] | } | ] | } @@ -480,7 +573,16 @@ class ImpersonationSuite | "users" : [ | { | "name": "ldap_user_1", - | "groups": ["group1", "group2"] + | "groups": [ + | { + | "id": "group1", + | "name": "group1" + | }, + | { + | "id": "group2", + | "name": "group2" + | } + | ] | } | ] | } @@ -617,7 +719,16 @@ class ImpersonationSuite | "users" : [ | { | "name": "ldap_user_1", - | "groups": ["group1", "group2"] + | "groups": [ + | { + | "id": "group1", + | "name": "group1" + | }, + | { + | "id": "group2", + | "name": "group2" + | } + | ] | } | ] | } @@ -629,7 +740,16 @@ class ImpersonationSuite | "users" : [ | { | "name": "ldap_user_2", - | "groups": ["group1", "group2"] + | "groups": [ + | { + | "id": "group1", + | "name": "group1" + | }, + | { + | "id": "group2", + | "name": "group2" + | } + | ] | } | ] | } @@ -657,8 +777,32 @@ class ImpersonationSuite | "name": "grp1", | "mock": { | "users" : [ - | { "name": "gpa_user_1", "groups": ["group4", "group5"]}, - | { "name": "gpa_user_1a", "groups": ["group4a", "group5a"] } + | { + | "name": "gpa_user_1", + | "groups": [ + | { + | "id": "group4", + | "name": "group4" + | }, + | { + | "id": "group5", + | "name": "group5" + | } + | ] + | }, + | { + | "name": "gpa_user_1a", + | "groups": [ + | { + | "id": "group4a", + | "name": "group4a" + | }, + | { + | "id": "group5a", + | "name": "group5a" + | } + | ] + | } | ] | } | }, @@ -667,8 +811,32 @@ class ImpersonationSuite | "name": "grp2", | "mock": { | "users" : [ - | { "name": "gpa_user_2", "groups": ["group4", "group5"]}, - | { "name": "gpa_user_2a", "groups": ["group4a", "group5a"] } + | { + | "name": "gpa_user_2", + | "groups": [ + | { + | "id": "group4", + | "name": "group4" + | }, + | { + | "id": "group5", + | "name": "group5" + | } + | ] + | }, + | { + | "name": "gpa_user_2a", + | "groups": [ + | { + | "id": "group4a", + | "name": "group4a" + | }, + | { + | "id": "group5a", + | "name": "group5a" + | } + | ] + | } | ] | } | } diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/JwtAuthSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/JwtAuthSuite.scala index 8c03ad0f1c..66826f98c9 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/JwtAuthSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/JwtAuthSuite.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.integration.suites +import cats.data.NonEmptyList import io.jsonwebtoken.SignatureAlgorithm import org.scalatest.wordspec.AnyWordSpec import tech.beshu.ror.integration.suites.base.support.BaseSingleNodeEsClusterTest @@ -23,6 +24,7 @@ import tech.beshu.ror.integration.utils.{ESVersionSupportForAnyWordSpecLike, Sin import tech.beshu.ror.utils.elasticsearch.CatManager import tech.beshu.ror.utils.misc.CustomScalaTestMatchers import tech.beshu.ror.utils.misc.JwtUtils._ +import scala.jdk.CollectionConverters._ //TODO change test names. Current names are copies from old java integration tests class JwtAuthSuite @@ -149,7 +151,9 @@ class JwtAuthSuite "rejectTokenWithWrongRolesClaim" in { val jwt = Jwt(algo, validKeyRole, claims = List( - "roles" := "role_wrong" + Claim(NonEmptyList.one(ClaimKey("roles")), List( + Map("id" -> "role_wrong", "name" -> "Wrong").asJava, + ).asJava) )) val clusterStateManager = new CatManager( noBasicAuthClient, @@ -163,7 +167,10 @@ class JwtAuthSuite "acceptValidTokenWithRolesClaim" in { val jwt = Jwt(algo, validKeyRole, claims = List( "user" := "user1", - "roles" := List("role_viewer", "something_lol") + Claim(NonEmptyList.one(ClaimKey("roles")), List( + Map("id" -> "role_viewer", "name" -> "Viewer").asJava, + Map("id" -> "role_manager", "name" -> "Manager").asJava + ).asJava) )) val clusterStateManager = new CatManager( noBasicAuthClient, diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/LocalGroupsSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/LocalGroupsSuite.scala index 71ca9fd4a5..e4384d7765 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/LocalGroupsSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/LocalGroupsSuite.scala @@ -81,8 +81,20 @@ class LocalGroupsSuite response.responseJson should be(ujson.read( s"""{ | "x-ror-username": "user", - | "x-ror-current-group": "a_testgroup", - | "x-ror-available-groups": [ "a_testgroup", "foogroup" ], + | "x-ror-current-group": { + | "id": "a_testgroup", + | "name": "Test group" + | }, + | "x-ror-available-groups": [ + | { + | "id": "a_testgroup", + | "name": "Test group" + | }, + | { + | "id": "foogroup", + | "name": "Foo group" + | } + | ], | "x-ror-correlation-id": "$correlationId", | "x-ror-kibana_index": ".kibana_user", | "x-ror-kibana-hidden-apps": [ "timelion" ], @@ -100,8 +112,20 @@ class LocalGroupsSuite response.responseJson should be(ujson.read( s"""{ | "x-ror-username": "user", - | "x-ror-current-group": "foogroup", - | "x-ror-available-groups": [ "a_testgroup", "foogroup" ], + | "x-ror-current-group": { + | "id": "foogroup", + | "name": "Foo group" + | }, + | "x-ror-available-groups": [ + | { + | "id": "a_testgroup", + | "name": "Test group" + | }, + | { + | "id": "foogroup", + | "name": "Foo group" + | } + | ], | "x-ror-correlation-id": "$correlationId", | "x-ror-kibana_index": ".kibana_foogroup", | "x-ror-kibana-hidden-apps": [ "foo:app" ], diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/QueryAuditLogSerializerSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/QueryAuditLogSerializerSuite.scala index 80afe9c0a2..eb3a35df1d 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/QueryAuditLogSerializerSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/QueryAuditLogSerializerSuite.scala @@ -72,8 +72,8 @@ class QueryAuditLogSerializerSuite result.responseJson.obj.size should be(4) result.responseJson("x-ror-username").str should be("user1-proxy-id") - result.responseJson("x-ror-current-group").str should be("group1") - result.responseJson("x-ror-available-groups").arr.toList should be(List(Str("group1"))) + result.responseJson("x-ror-current-group").obj("id").str should be("group1") + result.responseJson("x-ror-available-groups").arr.map(_.obj("id")).toList should be(List(Str("group1"))) result.responseJson("x-ror-correlation-id").str should fullyMatch uuidRegex val auditEntries = auditIndexManager.getEntries.jsons