This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // F[G[_]] ~> H[_] | |
| type MapNestedK[F[_], G[_], H[_]] = FunctionK[λ[a => F[G[a]]], H] | |
| // This compiles | |
| implicit def flatten[F[_]](implicit F: FlatMap[F]): MapNestedK[F, F, F] = | |
| λ[λ[a => F[F[a]]] ~> F](F.flatten(_)) | |
| // But for some reason this doesn't | |
| implicit def flatten[F[_]](implicit F: FlatMap[F]): MapNestedK[F, F, F] = | |
| λ[MapNestedK[F, F, F]](F.flatten(_)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| private def exchangeToken(token: String, userId: String): Future[TokenResponse] = { | |
| import io.circe.generic.auto._ | |
| sttp | |
| .post(uri"${config.authServerUrl}/realms/${config.realm}/protocol/openid-connect/token") | |
| .body( | |
| "grant_type" -> "urn:ietf:params:oauth:grant-type:token-exchange", | |
| "client_id" -> config.clientId, | |
| "requested_subject" -> userId, | |
| "subject_token" -> token | |
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| def impersonate(username: String): Future[(String, Duration)] = { | |
| for { | |
| impersonatorToken <- obtainImpersonatorToken() | |
| userId <- getUserId(username, impersonatorToken) | |
| impersonatedUserCookies <- impersonateUser(impersonatorToken, userId) | |
| userTokenAndTtl <- obtainTokenBasedOnIdentity(impersonatedUserCookies) | |
| } yield userTokenAndTtl | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| private def extractTokenAndExpiration(response: Response[String]): Option[(String, Duration)] = { | |
| for { | |
| location <- response.headers.find(_._1 == "Location").map(_._2) | |
| queryParams <- extractQueryParams(location) | |
| token <- queryParams.get("access_token") | |
| expiration <- queryParams.get("expires_in").map(parseExpirationTime) | |
| } yield (token, expiration) | |
| } | |
| private def extractQueryParams(location: String): Option[Map[String, String]] = { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| private def obtainTokenBasedOnIdentity(impersonatedUserCookies: Seq[(String, String)]): Future[(String, Duration)] = { | |
| val authUri = uri"${config.authServerUrl}/realms/${config.realm}/protocol/openid-connect/auth" | |
| sttp | |
| .get(uri"$authUri?response_mode=fragment&response_type=token&client_id=${config.clientId}&redirect_uri=${config.redirectUri}") | |
| .cookies(impersonatedUserCookies: _*) | |
| .followRedirects(false) // The response comes in the form of redirect | |
| .send() | |
| .map(extractTokenAndExpiration) | |
| .flatMap { | |
| case Some(tokenAndDuration) => Future.successful(tokenAndDuration) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| private def impersonateUser(token: String, userId: String): Future[Seq[(String, String)]] = { | |
| sttp | |
| .post(uri"${config.authServerUrl}/admin/realms/${config.realm}/users/$userId/impersonation") | |
| .auth.bearer(token) | |
| .send() | |
| .map { r => | |
| val currentTime = ZonedDateTime.now() | |
| r.cookies | |
| .filter(_.expires.forall(_.isAfter(currentTime))) | |
| .map(cookie => (cookie.name, cookie.value)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| private def getUserId(username: String, token: String): Future[String] = { | |
| import io.circe.generic.auto._ | |
| sttp | |
| .get(uri"${config.authServerUrl}/admin/realms/${config.realm}/users?username=$username") | |
| .auth.bearer(token) | |
| .response(asJson[List[UserResponse]]) | |
| .send() | |
| .flatMap { | |
| _.body match { | |
| case Left(error) => Future.failed(new RuntimeException(error)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| case class TokenResponse( | |
| access_token: String | |
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJIc1liUXhWaXVSTWtZVzEyYnpHMlhZTDYyZ0tTalNlUnhwU2NZamJBTWpNIn0.eyJqdGkiOiJjZjI4NTlmZC01ZGVmLTQyOTQtODQzZi0zNjE0ZWUyYTMyNWMiLCJleHAiOjE1MTcxNTQ3NjksIm5iZiI6MCwiaWF0IjoxNTE3MTU0NzA5LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvbWFzdGVyIiwiYXVkIjoidGVzdCIsInN1YiI6IjI4NGE0ODRhLTgxMTAtNGJkMS04MGVkLTVhMTM5NmI5ZWM4ZCIsInR5cCI6IkJlYXJlciIsImF6cCI6InRlc3QiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJlZTA5OTExMi1iZjZlLTRkZjMtOWJmZC1hZjQyZDY1ZTFjMmYiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImNyZWF0ZS1yZWFsbSIsImFkbWluIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJtYXN0ZXItcmVhbG0iOnsicm9sZXMiOlsidmlldy1pZGVudGl0eS1wcm92aWRlcnMiLCJ2aWV3LXJlYWxtIiwibWFuYWdlLWlkZW50aXR5LXByb3ZpZGVycyIsImltcGVyc29uYXRpb24iLCJjcmVhdGUtY2xpZW50IiwibWFuYWdlLXVzZXJzIiwicXVlcnktcmVhbG1zIiwidmlldy1hdXRob3JpemF0aW9uIiwicXVlcnktY2xpZW50cyIsInF1ZXJ5LXVzZXJzIiwibWFuYWdlLWV2ZW50cyIsIm1hbmFnZS1yZWFsbSIsInZpZXctZXZlbnRzIiwidmlld |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| private def obtainImpersonatorToken(): Future[String] = { | |
| import io.circe.generic.auto._ | |
| sttp | |
| .post(uri"${config.authServerUrl}/realms/${config.realm}/protocol/openid-connect/token") | |
| .body( | |
| "grant_type" -> "password", | |
| "username" -> config.impersonatorUsername, | |
| "password" -> config.impersonatorPassword, | |
| "client_id" -> config.clientId | |
| ) |
NewerOlder