Skip to content

Instantly share code, notes, and snippets.

View bandrzejczak's full-sized avatar

Bartek Andrzejczak bandrzejczak

View GitHub Profile
// 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(_))
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
)
def impersonate(username: String): Future[(String, Duration)] = {
for {
impersonatorToken <- obtainImpersonatorToken()
userId <- getUserId(username, impersonatorToken)
impersonatedUserCookies <- impersonateUser(impersonatorToken, userId)
userTokenAndTtl <- obtainTokenBasedOnIdentity(impersonatedUserCookies)
} yield userTokenAndTtl
}
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]] = {
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)
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))
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))
case class TokenResponse(
access_token: String
)
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJIc1liUXhWaXVSTWtZVzEyYnpHMlhZTDYyZ0tTalNlUnhwU2NZamJBTWpNIn0.eyJqdGkiOiJjZjI4NTlmZC01ZGVmLTQyOTQtODQzZi0zNjE0ZWUyYTMyNWMiLCJleHAiOjE1MTcxNTQ3NjksIm5iZiI6MCwiaWF0IjoxNTE3MTU0NzA5LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvbWFzdGVyIiwiYXVkIjoidGVzdCIsInN1YiI6IjI4NGE0ODRhLTgxMTAtNGJkMS04MGVkLTVhMTM5NmI5ZWM4ZCIsInR5cCI6IkJlYXJlciIsImF6cCI6InRlc3QiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJlZTA5OTExMi1iZjZlLTRkZjMtOWJmZC1hZjQyZDY1ZTFjMmYiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImNyZWF0ZS1yZWFsbSIsImFkbWluIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJtYXN0ZXItcmVhbG0iOnsicm9sZXMiOlsidmlldy1pZGVudGl0eS1wcm92aWRlcnMiLCJ2aWV3LXJlYWxtIiwibWFuYWdlLWlkZW50aXR5LXByb3ZpZGVycyIsImltcGVyc29uYXRpb24iLCJjcmVhdGUtY2xpZW50IiwibWFuYWdlLXVzZXJzIiwicXVlcnktcmVhbG1zIiwidmlldy1hdXRob3JpemF0aW9uIiwicXVlcnktY2xpZW50cyIsInF1ZXJ5LXVzZXJzIiwibWFuYWdlLWV2ZW50cyIsIm1hbmFnZS1yZWFsbSIsInZpZXctZXZlbnRzIiwidmlld
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
)