From 10a86c70a0d3255cba6c58caddc14a5ed60c84ca Mon Sep 17 00:00:00 2001 From: Roberto Tyley Date: Fri, 27 Nov 2020 10:18:02 +0000 Subject: [PATCH] Remove use of deprecated `GoogleCredential` class This change is only related to the Google-Group-checking code in `play-googleauth`, which was using a deprecated class: The `com.google.api.client.googleapis.auth.oauth2.GoogleCredential` class in https://github.com/googleapis/google-api-java-client was deprecated in May 2019 by https://github.com/googleapis/google-api-java-client/pull/1258 ...the deprecation advice recommends using the classes from the new library: https://github.com/googleapis/google-auth-library-java This commit switches to using the new `com.google.auth.oauth2.ServiceAccountCredentials` class from the `google-auth-library-oauth2-http` artifact. Given this new class, our custom `com.gu.googleauth.GoogleServiceAccount` Scala case class is no longer necessary, so has been deprecated, in favour of the user passing us an instance of `ServiceAccountCredentials` - which is actually easier for a user to generate! Here's an example of how you make and use an instance of the new credentials: ``` import org.apache.commons.io.Charsets.UTF_8 import org.apache.commons.io.IOUtils import com.google.auth.oauth2.ServiceAccountCredentials import com.gu.googleauth.GoogleGroupChecker val impersonatedUser: String = ... // email address of the 'impersonated' account val serviceAccountCert: String = ... // from Google Developers Console val credentials = ServiceAccountCredentials.fromStream(IOUtils.toInputStream(serviceAccountCert, UTF_8)) val groupChecker = new GoogleGroupChecker(impersonatedUser, credentials) ``` --- build.sbt | 8 +-- .../main/scala/com/gu/googleauth/groups.scala | 53 +++++++++++++------ project/Dependencies.scala | 3 +- version.sbt | 2 +- 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/build.sbt b/build.sbt index fa8a243..2fd7fc7 100644 --- a/build.sbt +++ b/build.sbt @@ -50,8 +50,8 @@ val sonatypeReleaseSettings = Seq( def projectWithPlayVersion(majorMinorVersion: String) = Project(s"play-v$majorMinorVersion", file(s"play-v$majorMinorVersion")).settings( - scalaVersion := "2.12.10", - + scalaVersion := "2.12.12", + crossScalaVersions := Seq(scalaVersion.value, "2.13.4"), scalacOptions ++= Seq("-feature", "-deprecation"), libraryDependencies ++= Seq( @@ -65,8 +65,8 @@ def projectWithPlayVersion(majorMinorVersion: String) = sonatypeReleaseSettings ) -lazy val `play-v27` = projectWithPlayVersion("27").settings(crossScalaVersions := Seq(scalaVersion.value, "2.13.1")) -lazy val `play-v28` = projectWithPlayVersion("28").settings(crossScalaVersions := Seq(scalaVersion.value, "2.13.1")) +lazy val `play-v27` = projectWithPlayVersion("27") +lazy val `play-v28` = projectWithPlayVersion("28") lazy val `play-googleauth-root` = (project in file(".")).aggregate( `play-v27`, diff --git a/play-v27/src/main/scala/com/gu/googleauth/groups.scala b/play-v27/src/main/scala/com/gu/googleauth/groups.scala index 2374beb..71c00a3 100644 --- a/play-v27/src/main/scala/com/gu/googleauth/groups.scala +++ b/play-v27/src/main/scala/com/gu/googleauth/groups.scala @@ -2,10 +2,11 @@ package com.gu.googleauth import java.security.PrivateKey -import com.google.api.client.googleapis.auth.oauth2.GoogleCredential import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport import com.google.api.client.json.jackson2.JacksonFactory import com.google.api.services.admin.directory.{Directory, DirectoryScopes} +import com.google.auth.http.HttpCredentialsAdapter +import com.google.auth.oauth2.ServiceAccountCredentials import scala.collection.JavaConverters._ import scala.concurrent._ @@ -22,6 +23,7 @@ import scala.concurrent._ * @param privateKey the Service Account's private key - from the P12 file generated when the Service Account was created * @param impersonatedUser the email address of the user the application will be impersonating */ +@deprecated("Use com.google.auth.oauth2.ServiceAccountCredentials instead", "play-googleauth 2.1.0") case class GoogleServiceAccount( email: String, privateKey: PrivateKey, @@ -35,25 +37,44 @@ case class GoogleServiceAccount( * doesn't seem to work?). The Service Account needs the following scope: * https://www.googleapis.com/auth/admin.directory.group.readonly * - * You also need a separate domain user account (eg example@guardian.co.uk), which - * will be 'impersonated' when making the calls. + * So long as you have the Service Account certificate as a string, you can easily make + * an instance of com.google.auth.oauth2.ServiceAccountCredentials like this: + * + * {{{ + * import org.apache.commons.io.Charsets.UTF_8 + * import org.apache.commons.io.IOUtils + * import com.google.auth.oauth2.ServiceAccountCredentials + * + * val serviceAccountCert: String = ... // certificate from Google Developers Console + * val credentials = ServiceAccountCredentials.fromStream(IOUtils.toInputStream(serviceAccountCert, UTF_8)) + * }}} + * + * @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. */ -class GoogleGroupChecker(directoryServiceAccount: GoogleServiceAccount) { +class GoogleGroupChecker(impersonatedUser: String, serviceAccountCredentials: ServiceAccountCredentials) { - val directoryService = { + @deprecated( + "this constructor is deprecated, use the constructor accepting com.google.auth.oauth2.ServiceAccountCredentials instead", + "play-googleauth 2.1.0" + ) + def this(googleServiceAccount: GoogleServiceAccount) = { + this( + googleServiceAccount.impersonatedUser, + ServiceAccountCredentials.newBuilder() + .setPrivateKey(googleServiceAccount.privateKey) + .setServiceAccountUser(googleServiceAccount.email) + .build() + ) + } + + val directoryService: Directory = { + val credentials = serviceAccountCredentials + .createDelegated(impersonatedUser) + .createScoped(DirectoryScopes.ADMIN_DIRECTORY_GROUP_READONLY) val transport = GoogleNetHttpTransport.newTrustedTransport() val jsonFactory = JacksonFactory.getDefaultInstance - - val credential = new GoogleCredential.Builder() - .setTransport(transport) - .setJsonFactory(jsonFactory) - .setServiceAccountId(directoryServiceAccount.email) - .setServiceAccountUser(directoryServiceAccount.impersonatedUser) - .setServiceAccountPrivateKey(directoryServiceAccount.privateKey) - .setServiceAccountScopes(Seq(DirectoryScopes.ADMIN_DIRECTORY_GROUP_READONLY).asJava) - .build() - - new Directory.Builder(transport, jsonFactory, null).setHttpRequestInitializer(credential).build + new Directory.Builder(transport, jsonFactory, new HttpCredentialsAdapter(credentials)).build } def retrieveGroupsFor(userEmail: String)(implicit ec: ExecutionContext): Future[Set[String]] = for { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 890572c..b0e32d7 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -46,7 +46,8 @@ object Dependencies { val googleDirectoryAPI = Seq( "com.google.apis" % "google-api-services-admin-directory" % "directory_v1-rev118-1.25.0" exclude("com.google.guava", "guava-jdk5"), "com.google.api-client" % "google-api-client" % "1.30.10", // Required as it fixes https://github.com/googleapis/google-api-java-client/issues/1487 - "com.google.guava" % "guava" % "25.0-jre" + "com.google.auth" % "google-auth-library-oauth2-http" % "0.22.0", + "com.google.guava" % "guava" % "30.0-jre" ) } diff --git a/version.sbt b/version.sbt index c670469..4ce70f5 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "2.0.1-SNAPSHOT" +version in ThisBuild := "2.1.0-SNAPSHOT"