Skip to content

Commit

Permalink
fix #2031
Browse files Browse the repository at this point in the history
  • Loading branch information
mathieuancelin committed Dec 2, 2024
1 parent 9165d99 commit 8a8d2e1
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 45 deletions.
58 changes: 52 additions & 6 deletions otoroshi/app/auth/api.scala
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,62 @@ trait ValidableUser { self =>

def json: JsValue

def email: String

def validate(
validators: Seq[JsonPathValidator],
remoteValidators: Seq[RemoteUserValidatorSettings],
desc: ServiceDescriptor,
isRoute: Boolean,
authModuleConfig: AuthModuleConfig
)(implicit env: Env, ec: ExecutionContext): Future[Either[ErrorReason, self.type]] = {
val jsonuser = json
jsonPathValidate(jsonuser, validators) match {
case Left(err) => Left(err).vfuture
case Right(_) =>
remoteValidation(jsonuser, remoteValidators, desc, isRoute, authModuleConfig)
val allowedUsers = authModuleConfig.allowedUsers
val deniedUsers = authModuleConfig.deniedUsers
if (allowedUsers.nonEmpty && !allowedUsers.exists(str => strMatch(email, str))) {
Left(ErrorReason("User not allowed", Json.obj("error" -> "user blocked by allowed list of auth module").some)).vfuture
} else if (deniedUsers.nonEmpty && deniedUsers.exists(str => strMatch(email, str))) {
Left(ErrorReason("User not allowed", Json.obj("error" -> "user blocked by denied list of auth module").some)).vfuture
} else {
val validators = authModuleConfig.userValidators
jsonPathValidate(jsonuser, validators) match {
case Left(err) => Left(err).vfuture
case Right(_) =>
val remoteValidators = authModuleConfig.remoteValidators
remoteValidation(jsonuser, remoteValidators, desc, isRoute, authModuleConfig)
}
}
}

def strMatch(v: String, expected: String): Boolean = {
if (expected.trim.startsWith("Regex(") && expected.trim.endsWith(")")) {
val regex = expected.substring(6).init
RegexPool.regex(regex).matches(v)
} else if (expected.trim.startsWith("Wildcard(") && expected.trim.endsWith(")")) {
val regex = expected.substring(9).init
RegexPool.apply(regex).matches(v)
} else if (expected.trim.startsWith("RegexNot(") && expected.trim.endsWith(")")) {
val regex = expected.substring(9).init
!RegexPool.regex(regex).matches(v)
} else if (expected.trim.startsWith("WildcardNot(") && expected.trim.endsWith(")")) {
val regex = expected.substring(12).init
!RegexPool.apply(regex).matches(v)
} else if (expected.trim.startsWith("Contains(") && expected.trim.endsWith(")")) {
val contained = expected.substring(9).init
v.contains(contained)
} else if (expected.trim.startsWith("ContainsNot(") && expected.trim.endsWith(")")) {
val contained = expected.substring(12).init
!v.contains(contained)
} else if (expected.trim.startsWith("Not(") && expected.trim.endsWith(")")) {
val contained = expected.substring(4).init
v != contained
} else if (expected.trim.startsWith("ContainedIn(") && expected.trim.endsWith(")")) {
val contained = expected.substring(12).init
contained.split(",").map(_.trim()).contains(v)
} else if (expected.trim.startsWith("NotContainedIn(") && expected.trim.endsWith(")")) {
val contained = expected.substring(15).init
val values = contained.split(",").map(_.trim())
!values.contains(v)
} else {
v == expected
}
}

Expand Down Expand Up @@ -289,6 +333,8 @@ trait AuthModuleConfig extends AsJson with otoroshi.models.EntityLocationSupport
def metadata: Map[String, String]
def sessionCookieValues: SessionCookieValues
def clientSideSessionEnabled: Boolean
def allowedUsers: Seq[String]
def deniedUsers: Seq[String]
def userValidators: Seq[JsonPathValidator]
def remoteValidators: Seq[RemoteUserValidatorSettings]
def save()(implicit ec: ExecutionContext, env: Env): Future[Boolean]
Expand Down
18 changes: 10 additions & 8 deletions otoroshi/app/auth/basic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,9 @@ object BasicAuthModuleConfig extends FromJson[AuthModuleConfig] {
remoteValidators = (json \ "remoteValidators")
.asOpt[Seq[JsValue]]
.map(_.flatMap(v => RemoteUserValidatorSettings.format.reads(v).asOpt))
.getOrElse(Seq.empty)
.getOrElse(Seq.empty),
allowedUsers = json.select("allowedUsers").asOpt[Seq[String]].getOrElse(Seq.empty),
deniedUsers = json.select("deniedUsers").asOpt[Seq[String]].getOrElse(Seq.empty),
)
)
} recover { case e =>
Expand All @@ -185,7 +187,9 @@ case class BasicAuthModuleConfig(
tags: Seq[String],
metadata: Map[String, String],
sessionCookieValues: SessionCookieValues,
location: otoroshi.models.EntityLocation = otoroshi.models.EntityLocation()
location: otoroshi.models.EntityLocation = otoroshi.models.EntityLocation(),
allowedUsers: Seq[String] = Seq.empty,
deniedUsers: Seq[String] = Seq.empty,
) extends AuthModuleConfig {
def `type`: String = "basic"
def humanName: String = "In memory auth. provider"
Expand All @@ -207,6 +211,8 @@ case class BasicAuthModuleConfig(
"users" -> Writes.seq(BasicAuthUser.fmt).writes(this.users),
"sessionCookieValues" -> SessionCookieValues.fmt.writes(this.sessionCookieValues),
"userValidators" -> JsArray(userValidators.map(_.json)),
"allowedUsers" -> this.allowedUsers,
"deniedUsers" -> this.deniedUsers,
"remoteValidators" -> JsArray(remoteValidators.map(_.json))
)
def save()(implicit ec: ExecutionContext, env: Env): Future[Boolean] = env.datastores.authConfigsDataStore.set(this)
Expand Down Expand Up @@ -272,7 +278,7 @@ case class BasicAuthModule(authConfig: BasicAuthModuleConfig) extends AuthModule
tags = Seq.empty,
metadata = Map.empty,
location = authConfig.location
).validate(authConfig.userValidators, authConfig.remoteValidators, descriptor, isRoute = true, authConfig)
).validate(descriptor, isRoute = true, authConfig)
case None => Left(ErrorReason(s"You're not authorized here")).vfuture
}
}
Expand Down Expand Up @@ -302,7 +308,7 @@ case class BasicAuthModule(authConfig: BasicAuthModuleConfig) extends AuthModule
rights = user.rights,
adminEntityValidators = user.adminEntityValidators,
location = authConfig.location
).validate(authConfig.userValidators, authConfig.remoteValidators, descriptor, isRoute = true, authConfig)
).validate(descriptor, isRoute = true, authConfig)
case None => Left(ErrorReason(s"You're not authorized here")).vfuture
}
}
Expand Down Expand Up @@ -410,8 +416,6 @@ case class BasicAuthModule(authConfig: BasicAuthModuleConfig) extends AuthModule
.flatMap {
case Some(user) =>
user.validate(
authConfig.userValidators,
authConfig.remoteValidators,
descriptor,
isRoute = true,
authConfig
Expand Down Expand Up @@ -450,8 +454,6 @@ case class BasicAuthModule(authConfig: BasicAuthModuleConfig) extends AuthModule
metadata = Map.empty,
location = authConfig.location
).validate(
authConfig.userValidators,
authConfig.remoteValidators,
descriptor,
isRoute = true,
authConfig
Expand Down
14 changes: 8 additions & 6 deletions otoroshi/app/auth/ldap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ object LdapAuthModuleConfig extends FromJson[AuthModuleConfig] {
.asOpt[Seq[GroupFilter]](Reads.seq(GroupFilter._fmt))
.getOrElse(Seq.empty[GroupFilter])
},
allowedUsers = json.select("allowedUsers").asOpt[Seq[String]].getOrElse(Seq.empty),
deniedUsers = json.select("deniedUsers").asOpt[Seq[String]].getOrElse(Seq.empty),
searchFilter = (json \ "searchFilter").as[String],
adminUsername = (json \ "adminUsername").asOpt[String].filterNot(_.trim.isEmpty),
adminPassword = (json \ "adminPassword").asOpt[String].filterNot(_.trim.isEmpty),
Expand Down Expand Up @@ -277,7 +279,9 @@ case class LdapAuthModuleConfig(
rightsOverride: Map[String, UserRights] = Map.empty,
dataOverride: Map[String, JsObject] = Map.empty,
adminEntityValidatorsOverride: Map[String, Map[String, Seq[JsonValidator]]] = Map.empty,
groupRights: Map[String, GroupRights] = Map.empty
groupRights: Map[String, GroupRights] = Map.empty,
allowedUsers: Seq[String] = Seq.empty,
deniedUsers: Seq[String] = Seq.empty,
) extends AuthModuleConfig {
def `type`: String = "ldap"
def humanName: String = "Ldap auth. provider"
Expand Down Expand Up @@ -324,6 +328,8 @@ case class LdapAuthModuleConfig(
"extractProfileFilterNot" -> extractProfileFilterNot,
"rightsOverride" -> JsObject(rightsOverride.mapValues(_.json)),
"dataOverride" -> JsObject(dataOverride),
"allowedUsers" -> allowedUsers,
"deniedUsers" -> deniedUsers,
"groupRights" -> JsObject(groupRights.mapValues(GroupRights._fmt.writes)),
"adminEntityValidatorsOverride" -> JsObject(adminEntityValidatorsOverride.mapValues { o =>
JsObject(o.mapValues(v => JsArray(v.map(_.json))))
Expand Down Expand Up @@ -702,7 +708,7 @@ case class LdapAuthModule(authConfig: LdapAuthModuleConfig) extends AuthModule {
tags = Seq.empty,
metadata = Map.empty,
location = authConfig.location
).validate(authConfig.userValidators, authConfig.remoteValidators, descriptor, isRoute = true, authConfig)
).validate(descriptor, isRoute = true, authConfig)
case None => Left(ErrorReason(s"You're not authorized here")).vfuture
}
}
Expand Down Expand Up @@ -766,8 +772,6 @@ case class LdapAuthModule(authConfig: LdapAuthModuleConfig) extends AuthModule {
location = authConfig.location,
adminEntityValidators = user.adminEntityValidators
).validate(
authConfig.userValidators,
authConfig.remoteValidators,
env.backOfficeServiceDescriptor,
isRoute = false,
authConfig
Expand Down Expand Up @@ -872,8 +876,6 @@ case class LdapAuthModule(authConfig: LdapAuthModuleConfig) extends AuthModule {
.flatMap {
case Some(user) =>
user.validate(
authConfig.userValidators,
authConfig.remoteValidators,
descriptor,
isRoute = true,
authConfig
Expand Down
12 changes: 7 additions & 5 deletions otoroshi/app/auth/oauth.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ object GenericOauth2ModuleConfig extends FromJson[AuthModuleConfig] {
.getOrElse(Map.empty),
dataOverride = (json \ "dataOverride").asOpt[Map[String, JsObject]].getOrElse(Map.empty),
otoroshiRightsField = (json \ "otoroshiRightsField").asOpt[String].getOrElse("otoroshi_rights"),
allowedUsers = json.select("allowedUsers").asOpt[Seq[String]].getOrElse(Seq.empty),
deniedUsers = json.select("deniedUsers").asOpt[Seq[String]].getOrElse(Seq.empty),
adminEntityValidatorsOverride = json
.select("adminEntityValidatorsOverride")
.asOpt[JsObject]
Expand Down Expand Up @@ -190,7 +192,9 @@ case class GenericOauth2ModuleConfig(
rightsOverride: Map[String, UserRights] = Map.empty,
dataOverride: Map[String, JsObject] = Map.empty,
otoroshiRightsField: String = "otoroshi_rights",
adminEntityValidatorsOverride: Map[String, Map[String, Seq[JsonValidator]]] = Map.empty
adminEntityValidatorsOverride: Map[String, Map[String, Seq[JsonValidator]]] = Map.empty,
allowedUsers: Seq[String] = Seq.empty,
deniedUsers: Seq[String] = Seq.empty,
) extends OAuth2ModuleConfig {
def theDescription: String = desc
def theMetadata: Map[String, String] = metadata
Expand Down Expand Up @@ -247,6 +251,8 @@ case class GenericOauth2ModuleConfig(
"rightsOverride" -> JsObject(rightsOverride.mapValues(_.json)),
"dataOverride" -> JsObject(dataOverride),
"otoroshiRightsField" -> this.otoroshiRightsField,
"allowedUsers" -> this.allowedUsers,
"deniedUsers" -> this.deniedUsers,
"adminEntityValidatorsOverride" -> JsObject(adminEntityValidatorsOverride.mapValues { o =>
JsObject(o.mapValues(v => JsArray(v.map(_.json))))
})
Expand Down Expand Up @@ -722,8 +728,6 @@ case class GenericOauth2Module(authConfig: OAuth2ModuleConfig) extends AuthModul
metadata = authConfig.metadata,
location = authConfig.location
).validate(
authConfig.userValidators,
authConfig.remoteValidators,
descriptor,
isRoute = true,
authConfig
Expand Down Expand Up @@ -812,8 +816,6 @@ case class GenericOauth2Module(authConfig: OAuth2ModuleConfig) extends AuthModul
},
location = authConfig.location
).validate(
authConfig.userValidators,
authConfig.remoteValidators,
env.backOfficeServiceDescriptor,
isRoute = false,
authConfig
Expand Down
12 changes: 7 additions & 5 deletions otoroshi/app/auth/oauth1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ object Oauth1ModuleConfig extends FromJson[AuthModuleConfig] {
.getOrElse("http://otoroshi.oto.tools:9999/backoffice/auth0/callback"),
metadata = (json \ "metadata").asOpt[Map[String, String]].getOrElse(Map.empty),
tags = (json \ "tags").asOpt[Seq[String]].getOrElse(Seq.empty[String]),
allowedUsers = json.select("allowedUsers").asOpt[Seq[String]].getOrElse(Seq.empty),
deniedUsers = json.select("deniedUsers").asOpt[Seq[String]].getOrElse(Seq.empty),
rightsOverride = (json \ "rightsOverride")
.asOpt[Map[String, JsArray]]
.map(_.mapValues(UserRights.readFromArray))
Expand Down Expand Up @@ -171,7 +173,9 @@ case class Oauth1ModuleConfig(
remoteValidators: Seq[RemoteUserValidatorSettings] = Seq.empty,
rightsOverride: Map[String, UserRights] = Map.empty,
location: otoroshi.models.EntityLocation = otoroshi.models.EntityLocation(),
adminEntityValidatorsOverride: Map[String, Map[String, Seq[JsonValidator]]] = Map.empty
adminEntityValidatorsOverride: Map[String, Map[String, Seq[JsonValidator]]] = Map.empty,
allowedUsers: Seq[String] = Seq.empty,
deniedUsers: Seq[String] = Seq.empty,
) extends AuthModuleConfig {
def `type`: String = "oauth1"
def humanName: String = "OAuth1 provider"
Expand Down Expand Up @@ -208,6 +212,8 @@ case class Oauth1ModuleConfig(
"rightsOverride" -> JsObject(rightsOverride.mapValues(_.json)),
"httpMethod" -> httpMethod.name,
"sessionCookieValues" -> SessionCookieValues.fmt.writes(this.sessionCookieValues),
"allowedUsers" -> allowedUsers,
"deniedUsers" -> deniedUsers,
"adminEntityValidatorsOverride" -> JsObject(adminEntityValidatorsOverride.mapValues { o =>
JsObject(o.mapValues(v => JsArray(v.map(_.json))))
})
Expand Down Expand Up @@ -546,8 +552,6 @@ case class Oauth1AuthModule(authConfig: Oauth1ModuleConfig) extends AuthModule {
),
location = authConfig.location
).validate(
authConfig.userValidators,
authConfig.remoteValidators,
env.backOfficeServiceDescriptor,
isRoute = false,
authConfig
Expand All @@ -565,8 +569,6 @@ case class Oauth1AuthModule(authConfig: Oauth1ModuleConfig) extends AuthModule {
realm = authConfig.cookieSuffix(descriptor.get),
otoroshiData = None
).validate(
authConfig.userValidators,
authConfig.remoteValidators,
descriptor.getOrElse(env.backOfficeServiceDescriptor),
isRoute = true,
authConfig
Expand Down
12 changes: 8 additions & 4 deletions otoroshi/app/auth/saml/SAMLClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ case class SAMLModule(authConfig: SamlAuthModuleConfig) extends AuthModule {
metadata = Map("saml-id" -> assertion.getSubject.getNameID.getValue),
otoroshiData = Some(authConfig.extraMetadata),
location = authConfig.location
).validate(authConfig.userValidators, authConfig.remoteValidators, descriptor, isRoute = true, authConfig)
).validate(descriptor, isRoute = true, authConfig)
}
case None => FastFuture.successful(Left(ErrorReason("error")))
}
Expand Down Expand Up @@ -342,8 +342,6 @@ case class SAMLModule(authConfig: SamlAuthModuleConfig) extends AuthModule {
),
location = authConfig.location
).validate(
authConfig.userValidators,
authConfig.remoteValidators,
env.backOfficeServiceDescriptor,
isRoute = true,
authConfig
Expand Down Expand Up @@ -383,6 +381,8 @@ object SamlAuthModuleConfig extends FromJson[AuthModuleConfig] {
tags = (json \ "tags").asOpt[Seq[String]].getOrElse(Seq.empty[String]),
metadata = (json \ "metadata").asOpt[Map[String, String]].getOrElse(Map.empty),
extraMetadata = (json \ "extraMetadata").asOpt[JsObject].getOrElse(Json.obj()),
allowedUsers = json.select("allowedUsers").asOpt[Seq[String]].getOrElse(Seq.empty),
deniedUsers = json.select("deniedUsers").asOpt[Seq[String]].getOrElse(Seq.empty),
issuer = (json \ "issuer").as[String],
ssoProtocolBinding = (json \ "ssoProtocolBinding")
.asOpt[String]
Expand Down Expand Up @@ -782,7 +782,9 @@ case class SamlAuthModuleConfig(
usedNameIDAsEmail: Boolean = true,
emailAttributeName: Option[String] = Some("Email"),
sessionCookieValues: SessionCookieValues,
adminEntityValidatorsOverride: Map[String, Map[String, Seq[JsonValidator]]] = Map.empty
adminEntityValidatorsOverride: Map[String, Map[String, Seq[JsonValidator]]] = Map.empty,
allowedUsers: Seq[String] = Seq.empty,
deniedUsers: Seq[String] = Seq.empty,
) extends AuthModuleConfig {
def theDescription: String = desc
def theMetadata: Map[String, String] = metadata
Expand All @@ -805,6 +807,8 @@ case class SamlAuthModuleConfig(
"clientSideSessionEnabled" -> this.clientSideSessionEnabled,
"userValidators" -> JsArray(userValidators.map(_.json)),
"remoteValidators" -> JsArray(remoteValidators.map(_.json)),
"allowedUsers" -> this.allowedUsers,
"deniedUsers" -> this.deniedUsers,
"singleSignOnUrl" -> this.singleSignOnUrl,
"singleLogoutUrl" -> this.singleLogoutUrl.map(JsString.apply).getOrElse(JsNull).asValue,
"credentials" -> SAMLCredentials.fmt.writes(this.credentials),
Expand Down
Loading

0 comments on commit 8a8d2e1

Please sign in to comment.