From 6eb4c4fbe45000c5c14311b252f90c9adf0512fe Mon Sep 17 00:00:00 2001 From: Tom Forbes Date: Thu, 9 Jan 2025 09:37:45 +0000 Subject: [PATCH 1/3] Cache results of group checker --- .../main/scala/com/gu/googleauth/groups.scala | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/play-v29/src/main/scala/com/gu/googleauth/groups.scala b/play-v29/src/main/scala/com/gu/googleauth/groups.scala index 1db0a1f..5942f99 100644 --- a/play-v29/src/main/scala/com/gu/googleauth/groups.scala +++ b/play-v29/src/main/scala/com/gu/googleauth/groups.scala @@ -3,8 +3,10 @@ package com.gu.googleauth import com.google.api.services.directory.Directory import com.google.api.services.directory.DirectoryScopes.ADMIN_DIRECTORY_GROUP_READONLY import com.google.auth.oauth2.{GoogleCredentials, ServiceAccountCredentials} +import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache} import com.gu.googleauth.internal.DirectoryService +import java.time.Duration import scala.concurrent._ import scala.jdk.CollectionConverters._ @@ -23,15 +25,38 @@ import scala.jdk.CollectionConverters._ * * @param impersonatedUser a separate domain-user account email address (eg 'example@guardian.co.uk'), the email address * of the user the application will be impersonating when making calls. + * @param serviceAccountCredentials Google OAuth2 credentials. + * @param cacheDuration how long to cache each user's groups for (defaults to 1 minute). + * */ -class GoogleGroupChecker(impersonatedUser: String, serviceAccountCredentials: ServiceAccountCredentials) { +class GoogleGroupChecker( + impersonatedUser: String, + serviceAccountCredentials: ServiceAccountCredentials, + cacheDuration: Duration = Duration.ofMinutes(1) +) { private val googleCredentials: GoogleCredentials = serviceAccountCredentials.createDelegated(impersonatedUser) private val directoryService: Directory = DirectoryService(googleCredentials, ADMIN_DIRECTORY_GROUP_READONLY) - def retrieveGroupsFor(userEmail: String)(implicit ec: ExecutionContext): Future[Set[String]] = for { - resp <- Future { blocking { directoryService.groups.list.setUserKey(userEmail).execute() } } - } yield resp.getGroups.asScala.map(_.getEmail).toSet - + type Email = String + private val cache: LoadingCache[Email, Set[String]] = CacheBuilder.newBuilder() + .expireAfterWrite(cacheDuration) + .build( + new CacheLoader[Email, Set[String]]() { + def load(email: Email): Set[String] = { + println(s"loading $email") + val result = directoryService.groups.list.setUserKey(email).execute() + result.getGroups.asScala.map(_.getEmail).toSet + } + } + ) + + def retrieveGroupsFor(userEmail: String)(implicit ec: ExecutionContext): Future[Set[String]] = Future { + val start = System.currentTimeMillis() + val result = blocking { cache.get(userEmail) } + val end = System.currentTimeMillis() + println(s"took ${end - start}") + result + } } From b3dd8f14c7fee3d2c3286ccc1eaaa90dcc807723 Mon Sep 17 00:00:00 2001 From: Tom Forbes Date: Thu, 9 Jan 2025 09:38:11 +0000 Subject: [PATCH 2/3] remove debug --- play-v29/src/main/scala/com/gu/googleauth/groups.scala | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/play-v29/src/main/scala/com/gu/googleauth/groups.scala b/play-v29/src/main/scala/com/gu/googleauth/groups.scala index 5942f99..0961d36 100644 --- a/play-v29/src/main/scala/com/gu/googleauth/groups.scala +++ b/play-v29/src/main/scala/com/gu/googleauth/groups.scala @@ -45,7 +45,6 @@ class GoogleGroupChecker( .build( new CacheLoader[Email, Set[String]]() { def load(email: Email): Set[String] = { - println(s"loading $email") val result = directoryService.groups.list.setUserKey(email).execute() result.getGroups.asScala.map(_.getEmail).toSet } @@ -53,10 +52,6 @@ class GoogleGroupChecker( ) def retrieveGroupsFor(userEmail: String)(implicit ec: ExecutionContext): Future[Set[String]] = Future { - val start = System.currentTimeMillis() - val result = blocking { cache.get(userEmail) } - val end = System.currentTimeMillis() - println(s"took ${end - start}") - result + blocking { cache.get(userEmail) } } } From 6a9d92ef13a913f88a6047b250d0ec3e2698729f Mon Sep 17 00:00:00 2001 From: Tom Forbes Date: Thu, 9 Jan 2025 09:51:39 +0000 Subject: [PATCH 3/3] comment --- play-v29/src/main/scala/com/gu/googleauth/groups.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/play-v29/src/main/scala/com/gu/googleauth/groups.scala b/play-v29/src/main/scala/com/gu/googleauth/groups.scala index 0961d36..aa375f9 100644 --- a/play-v29/src/main/scala/com/gu/googleauth/groups.scala +++ b/play-v29/src/main/scala/com/gu/googleauth/groups.scala @@ -44,6 +44,7 @@ class GoogleGroupChecker( .expireAfterWrite(cacheDuration) .build( new CacheLoader[Email, Set[String]]() { + // If the loading function throws then nothing is cached, and calls to cache.get() also throw def load(email: Email): Set[String] = { val result = directoryService.groups.list.setUserKey(email).execute() result.getGroups.asScala.map(_.getEmail).toSet