Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authorization Header Specification fixes #110

Merged
merged 2 commits into from
Jan 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ trait AuthorizationHandler[U] {

/**
* Verify proper client with parameters for issue an access token.
* Note that per the OAuth Specification, a Client may be valid if it only contains a client ID but no client
* secret (common with Public Clients). However, if the registered client has a client secret value the specification
* requires that a client secret must always be provided and verified for that client ID.
*
* @param request Request sent by client.
* @return true if request is a regular client, false if request is a illegal client.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,32 @@ class AuthorizationRequest(headers: Map[String, Seq[String]], params: Map[String

def grantType: String = requireParam("grant_type")

lazy val clientCredential: Option[ClientCredential] =
def parseClientCredential: Option[Either[InvalidClient, ClientCredential]] =
findAuthorization
.flatMap(clientCredentialByAuthorization)
.orElse(clientCredentialByParam)

private def findAuthorization = for {
authorization <- header("Authorization")
matcher <- """^\s*Basic\s+(.+?)\s*$""".r.findFirstMatchIn(authorization)
} yield matcher.group(1)

private def clientCredentialByAuthorization(s: String) =
.flatMap(x => Some(x.fold(
left => Left(left),
header => clientCredentialByAuthorization(header)
)))
.orElse(clientCredentialByParam.map(Right(_)))

private def findAuthorization: Option[Either[InvalidClient, String]] = {
header("Authorization").map { auth =>
val basicAuthCred = for {
matcher <- """^\s*Basic\s+(.+?)\s*$""".r.findFirstMatchIn(auth)
} yield matcher.group(1)

basicAuthCred.fold[Either[InvalidClient, String]](Left(new InvalidClient("Authorization header could not be parsed")))(x => Right(x))
}
}

private def clientCredentialByAuthorization(s: String): Either[InvalidClient, ClientCredential] =
Try(new String(Base64.getDecoder.decode(s), "UTF-8"))
.map(_.split(":", 2))
.getOrElse(Array.empty) match {
case Array(clientId, clientSecret) =>
Some(ClientCredential(clientId, if (clientSecret.isEmpty) None else Some(clientSecret)))
Right(ClientCredential(clientId, if (clientSecret.isEmpty) None else Some(clientSecret)))
case _ =>
None
Left(new InvalidClient())
}

private def clientCredentialByParam = param("client_id").map(ClientCredential(_, param("client_secret")))
Expand All @@ -44,8 +52,6 @@ case class RefreshTokenRequest(request: AuthorizationRequest) extends Authorizat
* @throws InvalidRequest if the parameter is not found
*/
def refreshToken: String = requireParam("refresh_token")

override lazy val clientCredential = request.clientCredential
}

case class PasswordRequest(request: AuthorizationRequest) extends AuthorizationRequest(request.headers, request.params) {
Expand All @@ -64,13 +70,9 @@ case class PasswordRequest(request: AuthorizationRequest) extends AuthorizationR
* @throws InvalidRequest if the parameter is not found
*/
def password = requireParam("password")

override lazy val clientCredential = request.clientCredential
}

case class ClientCredentialsRequest(request: AuthorizationRequest) extends AuthorizationRequest(request.headers, request.params) {
override lazy val clientCredential = request.clientCredential
}
case class ClientCredentialsRequest(request: AuthorizationRequest) extends AuthorizationRequest(request.headers, request.params)

case class AuthorizationCodeRequest(request: AuthorizationRequest) extends AuthorizationRequest(request.headers, request.params) {
/**
Expand All @@ -87,11 +89,6 @@ case class AuthorizationCodeRequest(request: AuthorizationRequest) extends Autho
* @return redirect_uri
*/
def redirectUri: Option[String] = param("redirect_uri")

override lazy val clientCredential = request.clientCredential

}

case class ImplicitRequest(request: AuthorizationRequest) extends AuthorizationRequest(request.headers, request.params) {
override lazy val clientCredential = request.clientCredential
}
case class ImplicitRequest(request: AuthorizationRequest) extends AuthorizationRequest(request.headers, request.params)
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ trait GrantHandler {
*/
def clientCredentialRequired = true

def handleRequest[U](request: AuthorizationRequest, authorizationHandler: AuthorizationHandler[U])(implicit ctx: ExecutionContext): Future[GrantHandlerResult[U]]
def handleRequest[U](maybeValidatedClientCred: Option[ClientCredential], request: AuthorizationRequest, authorizationHandler: AuthorizationHandler[U])(implicit ctx: ExecutionContext): Future[GrantHandlerResult[U]]

/**
* Returns valid access token.
Expand Down Expand Up @@ -53,51 +53,51 @@ trait GrantHandler {

class RefreshToken extends GrantHandler {

override def handleRequest[U](request: AuthorizationRequest, handler: AuthorizationHandler[U])(implicit ctx: ExecutionContext): Future[GrantHandlerResult[U]] = {
override def handleRequest[U](maybeValidatedClientCred: Option[ClientCredential], request: AuthorizationRequest, handler: AuthorizationHandler[U])(implicit ctx: ExecutionContext): Future[GrantHandlerResult[U]] = {
val clientId = maybeValidatedClientCred.getOrElse(throw new InvalidRequest("Client credential is required")).clientId
val refreshTokenRequest = RefreshTokenRequest(request)
val clientCredential = refreshTokenRequest.clientCredential.getOrElse(throw new InvalidRequest("Client credential is required"))
val refreshToken = refreshTokenRequest.refreshToken

handler.findAuthInfoByRefreshToken(refreshToken).flatMap { authInfoOption =>
val authInfo = authInfoOption.getOrElse(throw new InvalidGrant("Authorized information is not found by the refresh token"))
if (!authInfo.clientId.contains(clientCredential.clientId)) {
throw new InvalidClient
}

if (!authInfo.clientId.contains(clientId)) throw new InvalidClient
handler.refreshAccessToken(authInfo, refreshToken).map(createGrantHandlerResult(authInfo, _))
}
}
}

class Password extends GrantHandler {

override def handleRequest[U](request: AuthorizationRequest, handler: AuthorizationHandler[U])(implicit ctx: ExecutionContext): Future[GrantHandlerResult[U]] = {
val passwordRequest = PasswordRequest(request)
if (clientCredentialRequired && passwordRequest.clientCredential.isEmpty) {
override def handleRequest[U](maybeValidatedClientCred: Option[ClientCredential], request: AuthorizationRequest, handler: AuthorizationHandler[U])(implicit ctx: ExecutionContext): Future[GrantHandlerResult[U]] = {
/**
* Given that client credentials may be optional, if they are required, they must be fully validated before
* further processing.
*/
if (clientCredentialRequired && maybeValidatedClientCred.isEmpty) {
throw new InvalidRequest("Client credential is required")
}

handler.findUser(passwordRequest).flatMap { maybeUser =>
val user = maybeUser.getOrElse(throw new InvalidGrant("username or password is incorrect"))
val scope = passwordRequest.scope
val maybeClientId = passwordRequest.clientCredential.map(_.clientId)
val authInfo = AuthInfo(user, maybeClientId, scope, None)

issueAccessToken(handler, authInfo)
} else {
val passwordRequest = PasswordRequest(request)
handler.findUser(passwordRequest).flatMap { maybeUser =>
val user = maybeUser.getOrElse(throw new InvalidGrant("username or password is incorrect"))
val scope = passwordRequest.scope
val authInfo = AuthInfo(user, maybeValidatedClientCred.map(_.clientId), scope, None)

issueAccessToken(handler, authInfo)
}
}
}
}

class ClientCredentials extends GrantHandler {

override def handleRequest[U](request: AuthorizationRequest, handler: AuthorizationHandler[U])(implicit ctx: ExecutionContext): Future[GrantHandlerResult[U]] = {
override def handleRequest[U](maybeValidatedClientCred: Option[ClientCredential], request: AuthorizationRequest, handler: AuthorizationHandler[U])(implicit ctx: ExecutionContext): Future[GrantHandlerResult[U]] = {
val clientId = maybeValidatedClientCred.getOrElse(throw new InvalidRequest("Client credential is required")).clientId
val clientCredentialsRequest = ClientCredentialsRequest(request)
val clientCredential = clientCredentialsRequest.clientCredential.getOrElse(throw new InvalidRequest("Client credential is required"))
val scope = clientCredentialsRequest.scope

handler.findUser(clientCredentialsRequest).flatMap { optionalUser =>
val user = optionalUser.getOrElse(throw new InvalidGrant("client_id or client_secret or scope is incorrect"))
val authInfo = AuthInfo(user, Some(clientCredential.clientId), scope, None)
val authInfo = AuthInfo(user, Some(clientId), scope, None)

issueAccessToken(handler, authInfo)
}
Expand All @@ -107,10 +107,9 @@ class ClientCredentials extends GrantHandler {

class AuthorizationCode extends GrantHandler {

override def handleRequest[U](request: AuthorizationRequest, handler: AuthorizationHandler[U])(implicit ctx: ExecutionContext): Future[GrantHandlerResult[U]] = {
override def handleRequest[U](maybeValidatedClientCred: Option[ClientCredential], request: AuthorizationRequest, handler: AuthorizationHandler[U])(implicit ctx: ExecutionContext): Future[GrantHandlerResult[U]] = {
val clientId = maybeValidatedClientCred.getOrElse(throw new InvalidRequest("Client credential is required")).clientId
val authorizationCodeRequest = AuthorizationCodeRequest(request)
val clientCredential = authorizationCodeRequest.clientCredential.getOrElse(throw new InvalidRequest("Client credential is required"))
val clientId = clientCredential.clientId
val code = authorizationCodeRequest.code
val redirectUri = authorizationCodeRequest.redirectUri

Expand All @@ -136,14 +135,14 @@ class AuthorizationCode extends GrantHandler {

class Implicit extends GrantHandler {

override def handleRequest[U](request: AuthorizationRequest, handler: AuthorizationHandler[U])(implicit ctx: ExecutionContext): Future[GrantHandlerResult[U]] = {
override def handleRequest[U](maybeValidatedClientCred: Option[ClientCredential], request: AuthorizationRequest, handler: AuthorizationHandler[U])(implicit ctx: ExecutionContext): Future[GrantHandlerResult[U]] = {
val clientId = maybeValidatedClientCred.getOrElse(throw new InvalidRequest("Client credential is required")).clientId
val implicitRequest = ImplicitRequest(request)
val clientCredential = implicitRequest.clientCredential.getOrElse(throw new InvalidRequest("Client credential is required"))

handler.findUser(implicitRequest).flatMap { maybeUser =>
val user = maybeUser.getOrElse(throw new InvalidGrant("user cannot be authenticated"))
val scope = implicitRequest.scope
val authInfo = AuthInfo(user, Some(clientCredential.clientId), scope, None)
val authInfo = AuthInfo(user, Some(clientId), scope, None)

issueAccessToken(handler, authInfo)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,29 @@ trait TokenEndpoint {

def handleRequest[U](request: AuthorizationRequest, handler: AuthorizationHandler[U])(implicit ctx: ExecutionContext): Future[Either[OAuthError, GrantHandlerResult[U]]] = try {
val grantType = request.grantType
val grantHandler = handlers.getOrElse(grantType, throw new UnsupportedGrantType(s"${grantType} is not supported"))
val grantHandler = () => handlers.getOrElse(grantType, throw new UnsupportedGrantType(s"$grantType is not supported"))

request.clientCredential.map { clientCredential =>
handler.validateClient(request).flatMap { validClient =>
if (!validClient) {
Future.successful(Left(new InvalidClient("Invalid client is detected")))
} else {
grantHandler.handleRequest(request, handler).map(Right(_))
request.parseClientCredential.map { maybeCredential =>
maybeCredential.fold(
invalid => Future.successful(Left(invalid)),
clientCredential => {
handler.validateClient(request).flatMap { isValidClient =>
if (!isValidClient) {
Future.successful(Left(new InvalidClient("Invalid client or client is not authorized")))
} else {
grantHandler().handleRequest(Some(clientCredential), request, handler).map(Right(_))
}
}.recover {
case e: OAuthError => Left(e)
}
}
}.recover {
case e: OAuthError => Left(e)
}
)
}.getOrElse {
if (grantHandler.clientCredentialRequired) {
val gh = grantHandler()
if (gh.clientCredentialRequired) {
throw new InvalidRequest("Client credential is not found")
} else {
grantHandler.handleRequest(request, handler).map(Right(_)).recover {
gh.handleRequest(None, request, handler).map(Right(_)).recover {
case e: OAuthError => Left(e)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ class AuthorizationCodeSpec extends FlatSpec with ScalaFutures with OptionValues
val authorizationCode = new AuthorizationCode()
val request = new AuthorizationRequest(Map(), Map("client_id" -> Seq("clientId1"), "client_secret" -> Seq("clientSecret1"), "code" -> Seq("code1"), "redirect_uri" -> Seq("http://example.com/")))
var codeDeleted: Boolean = false
val f = authorizationCode.handleRequest(request, new MockDataHandler() {
val clientCred = request.parseClientCredential.fold[Option[ClientCredential]](None)(_.fold(_ => None, c => Some(c)))
val f = authorizationCode.handleRequest(clientCred, request, new MockDataHandler() {

override def findAuthInfoByCode(code: String): Future[Option[AuthInfo[User]]] = Future.successful(Some(
AuthInfo(user = MockUser(10000, "username"), clientId = Some("clientId1"), scope = Some("all"), redirectUri = Some("http://example.com/"))
Expand Down Expand Up @@ -42,7 +43,8 @@ class AuthorizationCodeSpec extends FlatSpec with ScalaFutures with OptionValues
it should "handle request if redirectUrl is none" in {
val authorizationCode = new AuthorizationCode()
val request = new AuthorizationRequest(Map(), Map("client_id" -> Seq("clientId1"), "client_secret" -> Seq("clientSecret1"), "code" -> Seq("code1"), "redirect_uri" -> Seq("http://example.com/")))
val f = authorizationCode.handleRequest(request, new MockDataHandler() {
val clientCred = request.parseClientCredential.fold[Option[ClientCredential]](None)(_.fold(_ => None, c => Some(c)))
val f = authorizationCode.handleRequest(clientCred, request, new MockDataHandler() {

override def findAuthInfoByCode(code: String): Future[Option[AuthInfo[MockUser]]] = Future.successful(Some(
AuthInfo(user = MockUser(10000, "username"), clientId = Some("clientId1"), scope = Some("all"), redirectUri = None)
Expand All @@ -63,7 +65,8 @@ class AuthorizationCodeSpec extends FlatSpec with ScalaFutures with OptionValues
it should "return a Failure Future" in {
val authorizationCode = new AuthorizationCode()
val request = new AuthorizationRequest(Map(), Map("client_id" -> Seq("clientId1"), "client_secret" -> Seq("clientSecret1"), "code" -> Seq("code1"), "redirect_uri" -> Seq("http://example.com/")))
val f = authorizationCode.handleRequest(request, new MockDataHandler() {
val clientCred = request.parseClientCredential.fold[Option[ClientCredential]](None)(_.fold(_ => None, c => Some(c)))
val f = authorizationCode.handleRequest(clientCred, request, new MockDataHandler() {

override def findAuthInfoByCode(code: String): Future[Option[AuthInfo[User]]] = Future.successful(Some(
AuthInfo(user = MockUser(10000, "username"), clientId = Some("clientId1"), scope = Some("all"), redirectUri = Some("http://example.com/"))
Expand Down
Loading