All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gu.identity.play.IdentityPlayAuthService.scala Maven / Gradle / Ivy

There is a newer version: 4.33
Show newest version
package com.gu.identity.play

import cats.effect.IO
import cats.implicits.catsSyntaxApplicativeError
import cats.syntax.either._
import com.gu.identity.auth._
import play.api.mvc.{Cookie, RequestHeader}

import scala.concurrent.ExecutionContext

// Class that builds upon functionality of IdentityAuthService
// to make user authentication convenient in the context of a Play application.
// It handles extracting the user credentials from a Play request before authenticating them
// using the underlying IdentityAuthService.
// Target client should be set if this service is being used to authenticate using crypto access tokens
// in addition to SC_GU_U cookies. See comments on UserCredentials.CryptoAccessToken for more context.
class IdentityPlayAuthService(
    identityAuthService: IdentityAuthService,
    targetClient: Option[String]
) {

  // just convenience vals to avoid having to repeat the same okta or idapi specific methods here or asking clients to initialise the services themselves
  lazy val idapiPlayAuthService = new IdapiPlayAuthService(identityAuthService.idapiAuthService, targetClient)
  lazy val oktaPlayAuthService = new OktaPlayAuthService(identityAuthService.oktaAuthService)

  import IdentityPlayAuthService._

  // -----
  def getOktaCredentialsFromRequest(requestHeader: RequestHeader): IO[UserCredentials] = IO.fromEither {
    Helpers
      .fetchBearerTokenFromAuthHeader(requestHeader.headers.get)
      .map(OktaUserCredentials)
      .left
      .map(OktaValidationException)
  }

  def getCredentialsFromRequest(request: RequestHeader): IO[UserCredentials] =
    getOktaCredentialsFromRequest(request) recoverWith { case OktaValidationException(MissingHeader) =>
      getIdapiUserCredentialsFromRequest(request, targetClient)
    }

  def validateCredentialsFromRequest[I <: IdentityClaims, A <: AccessClaims](
      request: RequestHeader,
      requiredScopes: List[AccessScope] = List.empty,
  )(implicit
      accessClaimsParser: AccessClaimsParser[A],
      userInfoResponseParser: UserInfoResponseParser[I]
  ): IO[(UserCredentials, AuthenticatedUserData[I, A])] = {
    for {
      credentials <- getCredentialsFromRequest(request)
      user <- identityAuthService.validateCredentials(credentials, requiredScopes)
    } yield (credentials, user)
  }

}

object IdentityPlayAuthService {

  case class UserCredentialsMissingError(message: String) extends Exception {
    override def getMessage: String = message
  }

  def getSCGUUCookieFromRequest(request: RequestHeader): IO[Cookie] =
    IO.fromEither(
      Either.fromOption(
        request.cookies.get("SC_GU_U"),
        UserCredentialsMissingError("SC_GU_U cookie not set")
      )
    )

  def getCryptoAccessTokenFromRequest(
      request: RequestHeader,
      targetClient: String
  ): IO[IdapiUserCredentials.CryptoAccessToken] =
    IO.fromEither(
      Either.fromOption(
        request.headers
          .get("GU-IdentityToken")
          .map(token => IdapiUserCredentials.CryptoAccessToken(token, targetClient)),
        UserCredentialsMissingError("GU-IdentityToken header not set")
      )
    )

  def getIdapiUserCredentialsFromRequest(
      request: RequestHeader,
      targetClient: Option[String]
  ): IO[IdapiUserCredentials] =
    getSCGUUCookieFromRequest(request)
      .redeemWith(
        // If no SC_GU_U cookie set, attempt to get crypto access token if target client is defined.
        err => {
          // If target client isn't set, then this indicates authentication with crypto access tokens is not supported.
          // In this case just return the original error.
          targetClient.fold(IO.raiseError[IdapiUserCredentials](err)) { client =>
            // Otherwise attempt to extract a crypto access toke for the target client.
            getCryptoAccessTokenFromRequest(request, client)
              .handleErrorWith { _ =>
                // At this point we know attempts were made to extract both credential types,
                // and that both attempts were unsuccessful due to the respective credentials not being present.
                IO.raiseError(UserCredentialsMissingError("neither SC_GU_U cookie or GU-IdentityToken header set"))
              }
          }
        },
        cookie => IO(IdapiUserCredentials.SCGUUCookie(cookie.value))
      )

  // Supports creating an IdentityPlayAuthService outside of an IO context e.g. a Play application (typically)
  def unsafeInit(idapiAuthConfig: IdapiAuthConfig, oktaTokenValidationConfig: OktaTokenValidationConfig)(implicit
      ec: ExecutionContext
  ): IdentityPlayAuthService = {
    val identityAuthService = IdentityAuthService.unsafeInit(
      IdapiAuthConfig(idapiAuthConfig.identityApiUri, idapiAuthConfig.accessToken),
      oktaTokenValidationConfig
    )
    new IdentityPlayAuthService(identityAuthService, idapiAuthConfig.targetClient)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy