Skip to content

Commit

Permalink
Cache realms
Browse files Browse the repository at this point in the history
  • Loading branch information
olivergrabinski committed Oct 4, 2023
1 parent d8ab831 commit b862700
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import ch.epfl.bluebrain.nexus.delta.kernel.kamon.KamonMetricComponent
import ch.epfl.bluebrain.nexus.delta.kernel.search.Pagination.FromPagination
import ch.epfl.bluebrain.nexus.delta.sdk.http.HttpClient
import ch.epfl.bluebrain.nexus.delta.sdk.http.HttpClientError.HttpClientStatusError
import ch.epfl.bluebrain.nexus.delta.sdk.identities.IdentitiesImpl.{extractGroups, logger, GroupsCache}
import ch.epfl.bluebrain.nexus.delta.sdk.identities.IdentitiesImpl.{extractGroups, logger, GroupsCache, RealmCache}
import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.TokenRejection.{GetGroupsFromOidcError, InvalidAccessToken, UnknownAccessTokenIssuer}
import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.{AuthToken, Caller}
import ch.epfl.bluebrain.nexus.delta.sdk.model.ResourceF
Expand All @@ -31,6 +31,7 @@ import io.circe.{Decoder, HCursor, Json}
import scala.util.Try

class IdentitiesImpl private[identities] (
realm: RealmCache,
findActiveRealm: String => IO[Option[Realm]],
getUserInfo: (Uri, OAuth2BearerToken) => IO[Json],
groups: GroupsCache
Expand Down Expand Up @@ -61,6 +62,14 @@ class IdentitiesImpl private[identities] (
)
}

def fetchRealm(parsedToken: ParsedToken): IO[Realm] =
realm
.getOrElseUpdate(parsedToken.rawToken, findActiveRealm(parsedToken.issuer))
.flatMap {
case Some(realm) => IO.pure(realm)
case None => IO.raiseError(UnknownAccessTokenIssuer)
}

def fetchGroups(parsedToken: ParsedToken, realm: Realm): IO[Set[Group]] = {
parsedToken.groups
.map { s =>
Expand All @@ -77,11 +86,10 @@ class IdentitiesImpl private[identities] (
}

val result = for {
parsedToken <- IO.fromEither(ParsedToken.fromToken(token))
activeRealmOption <- findActiveRealm(parsedToken.issuer)
activeRealm <- IO.fromOption(activeRealmOption)(UnknownAccessTokenIssuer)
_ <- validate(activeRealm.acceptedAudiences, parsedToken, realmKeyset(activeRealm))
groups <- fetchGroups(parsedToken, activeRealm)
parsedToken <- IO.fromEither(ParsedToken.fromToken(token))
activeRealm <- fetchRealm(parsedToken)
_ <- validate(activeRealm.acceptedAudiences, parsedToken, realmKeyset(activeRealm))
groups <- fetchGroups(parsedToken, activeRealm)
} yield {
val user = User(parsedToken.subject, activeRealm.label)
Caller(user, groups ++ Set(Anonymous, user, Authenticated(activeRealm.label)))
Expand All @@ -95,6 +103,7 @@ class IdentitiesImpl private[identities] (
object IdentitiesImpl {

type GroupsCache = LocalCache[String, Set[Group]]
type RealmCache = LocalCache[String, Option[Realm]]

private val logger = Logger.cats[this.type]

Expand Down Expand Up @@ -133,10 +142,14 @@ object IdentitiesImpl {
* the cache configuration
*/
def apply(realms: Realms, hc: HttpClient, config: CacheConfig): IO[Identities] = {
val groupsCache = LocalCache[String, Set[Group]](config)
val realmCache = LocalCache[String, Option[Realm]](config)

val findActiveRealm: String => IO[Option[Realm]] = { (issuer: String) =>
val pagination = FromPagination(0, 1000)
val params = RealmSearchParams(issuer = Some(issuer), deprecated = Some(false))
val sort = ResourceF.defaultSort[Realm]

realms.list(pagination, params, sort).map {
_.results.map(entry => entry.source.value).headOption
}
Expand All @@ -145,8 +158,8 @@ object IdentitiesImpl {
hc.toJson(HttpRequest(uri = uri, headers = List(Authorization(token))))
}

LocalCache[String, Set[Group]](config).map { groups =>
new IdentitiesImpl(findActiveRealm, getUserInfo, groups)
(realmCache, groupsCache).mapN { (realm, groups) =>
new IdentitiesImpl(realm, findActiveRealm, getUserInfo, groups)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import cats.implicits._
import ch.epfl.bluebrain.nexus.delta.kernel.cache.LocalCache
import ch.epfl.bluebrain.nexus.delta.sdk.generators.{RealmGen, WellKnownGen}
import ch.epfl.bluebrain.nexus.delta.sdk.http.HttpClientError.HttpUnexpectedError
import ch.epfl.bluebrain.nexus.delta.sdk.identities.IdentitiesImpl.{GroupsCache, RealmCache}
import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.TokenRejection.{AccessTokenDoesNotContainAnIssuer, AccessTokenDoesNotContainSubject, GetGroupsFromOidcError, InvalidAccessToken, InvalidAccessTokenFormat, UnknownAccessTokenIssuer}
import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.{AuthToken, Caller}
import ch.epfl.bluebrain.nexus.delta.sdk.realms.model.Realm
Expand Down Expand Up @@ -120,15 +121,18 @@ class IdentitiesImplSuite extends CatsEffectSuite with TestHelpers with IOFromMa
(_: Uri) => HttpUnexpectedError(HttpRequest(), "Error while getting response")
)(uri)

private val identities: Identities = LocalCache[String, Set[Group]]()
.map { cache =>
new IdentitiesImpl(
findActiveRealm,
(uri: Uri, _: OAuth2BearerToken) => userInfo(uri),
cache
)
}
.unsafeRunSync()
private val realmCache = LocalCache[String, Option[Realm]]()
private val groupsCache = LocalCache[String, Set[Group]]()

private val identitiesFromCaches: (RealmCache, GroupsCache) => Identities = (realmCache, groupsCache) =>
new IdentitiesImpl(
realmCache,
findActiveRealm,
(uri: Uri, _: OAuth2BearerToken) => userInfo(uri),
groupsCache
)

private val identities = identitiesFromCaches(realmCache.unsafeRunSync(), groupsCache.unsafeRunSync())

private val auth = Authenticated(githubLabel)
private val group1 = Group("group1", githubLabel)
Expand Down Expand Up @@ -326,4 +330,26 @@ class IdentitiesImplSuite extends CatsEffectSuite with TestHelpers with IOFromMa
identities.exchange(token).intercept(expectedError)
}

test("Cache realm and groups") {
val token = generateToken(
subject = "Bobby",
issuer = githubLabel,
rsaKey = rsaKey,
expires = nowPlus1h,
groups = None,
useCommas = true
)

for {
parsedToken <- IO.fromEither(ParsedToken.fromToken(token))
realm <- realmCache
groups <- groupsCache
_ <- realm.get(parsedToken.rawToken).assertNone
_ <- groups.get(parsedToken.rawToken).assertNone
_ <- identitiesFromCaches(realm, groups).exchange(token)
_ <- realm.get(parsedToken.rawToken).assertSome(Some(github))
_ <- groups.get(parsedToken.rawToken).assertSome(Set(group3, group4))
} yield ()
}

}

0 comments on commit b862700

Please sign in to comment.