Skip to content

Commit

Permalink
#430 Make attributes from OAuth2 server configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
To-om committed Jan 19, 2018
1 parent c1012c8 commit 0d8a45e
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 91 deletions.
30 changes: 18 additions & 12 deletions thehive-backend/app/services/OAuth2Srv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,20 @@ class OAuth2Srv(
mat)

override val name: String = "oauth2"
private val logger = Logger(classOf[OAuth2Srv]).logger
private val logger = Logger(getClass)

val Oauth2TokenQueryString = "code"

override def authenticate()(implicit request: RequestHeader): Future[AuthContext] = {
oauth2Config.clientId
.fold[Future[AuthContext]](Future.failed(AuthenticationError("OAuth2 not configured properly"))) {
clientId
request.queryString.get(Oauth2TokenQueryString).flatMap(_.headOption) match {
case Some(code)
request.queryString
.get(Oauth2TokenQueryString)
.flatMap(_.headOption)
.fold(createOauth2Redirect(clientId)) { code
getAuthTokenAndAuthenticate(clientId, code)
case None
createOauth2Redirect(clientId)
}
}
}
}

Expand All @@ -95,23 +95,30 @@ class OAuth2Srv(
"client_secret" -> oauth2Config.clientSecret,
"redirect_uri" -> oauth2Config.redirectUri,
"client_id" -> clientId))
.recoverWith {
case error
logger.error(s"Token verification failure", error)
Future.failed(AuthenticationError("Token verification failure"))
}
.flatMap { r
r.status match {
case Status.OK
val accessToken = (r.json \ "access_token").asOpt[String].getOrElse("")
val authHeader = "Authorization" -> s"Bearer $accessToken"
val authHeader = "Authorization" -> s"bearer $accessToken"
ws.url(oauth2Config.userUrl)
.addHttpHeaders(authHeader)
.get().flatMap { userResponse
if (userResponse.status != Status.OK) {
Future.failed(AuthenticationError("unexpected response from server"))
Future.failed(AuthenticationError(s"unexpected response from server: ${userResponse.status} ${userResponse.body}"))
}
else {
val response = userResponse.json.asInstanceOf[JsObject]
getOrCreateUser(response, authHeader)
}
}
case _ Future.failed(AuthenticationError("unexpected response from server"))
case _
logger.error(s"unexpected response from server: ${r.status} ${r.body}")
Future.failed(AuthenticationError("unexpected response from server"))
}
}
}
Expand All @@ -124,18 +131,17 @@ class OAuth2Srv(
userSrv.getFromUser(request, user)
}).recoverWith {
case authErr: AuthorizationError Future.failed(authErr)
case err if oauth2Config.autocreate
case _ if oauth2Config.autocreate
userSrv.inInitAuthContext { implicit authContext
userSrv.create(userFields).flatMap(user {
userSrv.getFromUser(request, user)
})
}
case err Future.failed(err)
}
}
}

private def createOauth2Redirect(clientId: String) = {
private def createOauth2Redirect(clientId: String): Future[AuthContext] = {
val queryStringParams = Map[String, Seq[String]](
"scope" -> Seq(oauth2Config.scope),
"response_type" -> Seq(oauth2Config.responseType),
Expand Down
78 changes: 43 additions & 35 deletions thehive-backend/app/services/mappers/GroupUserMapper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,62 @@ package services.mappers

import javax.inject.Inject

import org.elastic4play.AuthenticationError
import org.elastic4play.controllers.{ Fields, InputValue, StringInputValue }
import scala.concurrent.{ ExecutionContext, Future }

import play.api.Configuration
import play.api.libs.json._
import play.api.libs.ws.WSClient

import scala.concurrent.{ ExecutionContext, Future }

class GroupUserMapper @Inject() (
import org.elastic4play.AuthenticationError
import org.elastic4play.controllers.Fields

class GroupUserMapper(
loginAttrName: String,
nameAttrName: String,
rolesAttrName: Option[String],
groupAttrName: String,
defaultRoles: Seq[String],
groupsUrl: String,
mappings: Map[String, Seq[String]],
ws: WSClient,
configuration: Configuration,
implicit val ec: ExecutionContext) extends UserMapper {

@Inject() def this(

configuration: Configuration,
ws: WSClient,
ec: ExecutionContext) = this(
configuration.get[String]("auth.sso.attributes.login"),
configuration.get[String]("auth.sso.attributes.name"),
configuration.getOptional[String]("auth.sso.attributes.roles"),
configuration.getOptional[String]("auth.sso.attributes.groups").getOrElse(""),
configuration.getOptional[Seq[String]]("auth.sso.defaultRoles").getOrElse(Seq()),
configuration.getOptional[String]("auth.sso.groups.url").getOrElse(""),
configuration.getOptional[Map[String, Seq[String]]]("auth.sso.groups.mappings").getOrElse(Map()),
ws,
ec)

override val name: String = "group"
override val defaultRoles: Seq[String] = configuration.getOptional[Seq[String]]("auth.sso.defaultRoles").getOrElse(Seq())
private val groupsUrl: String = configuration.getOptional[String]("auth.sso.groups.url").getOrElse("")
private val mappings: Map[String, Seq[String]] = configuration.getOptional[Map[String, Seq[String]]]("auth.sso.groups.mappings").getOrElse(Map())

override def getUserFields(jsValue: JsValue, authHeader: Option[(String, String)]): Future[Fields] = {

val jsonUser = jsValue.validate[SimpleJsonUser].get
val apiCall = if (authHeader.isDefined) ws.url(groupsUrl).addHttpHeaders(authHeader.get) else ws.url(groupsUrl)
val apiCall = authHeader.fold(ws.url(groupsUrl))(headers ws.url(groupsUrl).addHttpHeaders(headers))
apiCall.get.flatMap { r
val jsonGroups = r.json.validate[SimpleJsonGroups].get
val roles = mappings.keys.filter(mapping jsonGroups.groups.contains(mapping))
.map(mappings(_)).toSeq.sortWith((left, right) left.length > right.length)
val role = if (roles.nonEmpty) {
StringInputValue(roles.head)
}
else {
if (defaultRoles.isEmpty) {
throw AuthenticationError("No permissions setup for user")
}
StringInputValue(defaultRoles)
val jsonGroups = (r.json \ groupAttrName).as[Seq[String]]
val mappedRoles = jsonGroups.flatMap(mappings.get).maxBy(_.length)
val roles = if (mappedRoles.nonEmpty) mappedRoles else defaultRoles

val fields = for {
login (jsValue \ loginAttrName).validate[String]
name (jsValue \ nameAttrName).validate[String]
} yield Fields(Json.obj(
"login" -> login,
"name" -> name,
"roles" -> roles))
fields match {
case JsSuccess(f, _) Future.successful(f)
case JsError(errors) Future.failed(AuthenticationError(s"User info fails: ${errors.map(_._1).mkString}"))
}
Future.successful {
new Fields(Map[String, InputValue](
"login" -> StringInputValue(jsonUser.username),
"name" -> StringInputValue(jsonUser.name),
"roles" -> role))
}.recoverWith {
case err
Future.failed(err)
}
}.recoverWith {
case err
Future.failed(err)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ class MultiUserMapperSrv @Inject() (
ssoMapperModules: immutable.Set[UserMapper]) extends UserMapper {

override val name: String = "usermapper"
override val defaultRoles: Seq[String] = Seq()
private lazy val mapper: UserMapper = MultiUserMapperSrv.getMapper(configuration, ssoMapperModules)

override def getUserFields(jsValue: JsValue, authHeader: Option[(String, String)]): Future[Fields] = {
Expand Down
7 changes: 0 additions & 7 deletions thehive-backend/app/services/mappers/SimpleJsonUser.scala

This file was deleted.

48 changes: 29 additions & 19 deletions thehive-backend/app/services/mappers/SimpleUserMapper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,42 @@ package services.mappers

import javax.inject.Inject

import org.elastic4play.AuthenticationError
import org.elastic4play.controllers.{ Fields, InputValue, StringInputValue }
import scala.concurrent.{ ExecutionContext, Future }

import play.api.Configuration
import play.api.libs.json.JsValue
import play.api.libs.json.{ JsError, JsSuccess, JsValue, Json }

import scala.concurrent.{ ExecutionContext, Future }
import org.elastic4play.AuthenticationError
import org.elastic4play.controllers.Fields

class SimpleUserMapper(
loginAttrName: String,
nameAttrName: String,
rolesAttrName: Option[String],
defaultRoles: Seq[String],
implicit val ec: ExecutionContext) extends UserMapper {

class SimpleUserMapper @Inject() (
configuration: Configuration,
implicit val ec: ExecutionContext)
extends UserMapper {
@Inject() def this(configuration: Configuration, ec: ExecutionContext) = this(
configuration.get[String]("auth.sso.attributes.login"),
configuration.get[String]("auth.sso.attributes.name"),
configuration.getOptional[String]("auth.sso.attributes.roles"),
configuration.getOptional[Seq[String]]("auth.sso.defaultRoles").getOrElse(Seq()),
ec)

override val name: String = "simple"
override val defaultRoles: Seq[String] = configuration.getOptional[Seq[String]]("auth.sso.defaultRoles").getOrElse(Seq())

override def getUserFields(jsValue: JsValue, authHeader: Option[(String, String)]): Future[Fields] = {
if (defaultRoles.isEmpty) {
throw AuthenticationError("No permissions setup for user")
}
val jsonUser = jsValue.validate[SimpleJsonUser].get
Future.successful {
new Fields(Map[String, InputValue](
"login" -> StringInputValue(jsonUser.username),
"name" -> StringInputValue(jsonUser.name),
"roles" -> StringInputValue(defaultRoles)))
val fields = for {
login (jsValue \ loginAttrName).validate[String]
name (jsValue \ nameAttrName).validate[String]
roles = rolesAttrName.fold(defaultRoles)(r (jsValue \ r).asOpt[Seq[String]].getOrElse(defaultRoles))
} yield Fields(Json.obj(
"login" -> login,
"name" -> name,
"roles" -> roles))
fields match {
case JsSuccess(f, _) Future.successful(f)
case JsError(errors) Future.failed(AuthenticationError(s"User info fails: ${errors.map(_._1).mkString}"))
}
}

}
2 changes: 0 additions & 2 deletions thehive-backend/app/services/mappers/UserMapper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import scala.concurrent.Future
* the SSO process to create new users if the option is selected.
*/
trait UserMapper {

val name: String
val defaultRoles: Seq[String]
def getUserFields(jsValue: JsValue, authHeader: Option[(String, String)] = None): Future[Fields]
}
15 changes: 0 additions & 15 deletions thehive-backend/app/services/mappers/package.scala

This file was deleted.

0 comments on commit 0d8a45e

Please sign in to comment.