This file contains 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 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 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 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 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 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 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 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 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 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