From 62fda6769a4b88259819f7e1bed5d9adc05fe454 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) ``` --- README.md | 20 +++---- build.sbt | 8 +-- .../main/scala/com/gu/googleauth/groups.scala | 53 +++++++++++++------ project/Dependencies.scala | 1 + version.sbt | 2 +- 5 files changed, 50 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index a1b8252..dea2d02 100644 --- a/README.md +++ b/README.md @@ -123,24 +123,18 @@ Once you have completed those 3 steps, you should be able to integrate it in you - This is how you can build your credentials from the json cert file you have downloaded: ```scala -import java.io.FileInputStream - -import com.google.api.client.googleapis.auth.oauth2.GoogleCredential -import com.gu.googleauth.GoogleServiceAccount +import org.apache.commons.io.Charsets.UTF_8 +import org.apache.commons.io.IOUtils +import com.google.auth.oauth2.ServiceAccountCredentials object GoogleAuthConf { - val impersonatedUser = ??? // read from config -} - -private lazy val credentials: GoogleCredential = { - val fileInputStream = new FileInputStream("/path/to/your-service-account-cert.json") - GoogleCredential.fromStream(fileInputStream) + val impersonatedUser: String = ??? // read from config + val serviceAccountCert: String = ??? // JSON certificate from Google Developers Console - read from secure storage } private val serviceAccount = GoogleServiceAccount( - credentials.getServiceAccountId, // This should contain the *email address* that is associated with your service account - credentials.getServiceAccountPrivateKey, // This should contain the *private key* that is associated with your service account - GoogleAuthConf.impersonatedUser // This is the admin user email address we mentioned earlier + GoogleAuthConf.impersonatedUser, // This is the admin user email address we mentioned earlier + ServiceAccountCredentials.fromStream(IOUtils.toInputStream(serviceAccountCert, UTF_8)) ) ``` diff --git a/build.sbt b/build.sbt index 3fa9500..f0970da 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 a7af3a6..d006abd 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.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 646abf7..0fb1276 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -46,6 +46,7 @@ object Dependencies { val googleDirectoryAPI = Seq( "com.google.apis" % "google-api-services-admin-directory" % "directory_v1-rev20191003-1.30.8" exclude("com.google.guava", "guava-jdk5"), "com.google.api-client" % "google-api-client" % "1.31.1", // https://app.snyk.io/vuln/SNYK-JAVA-COMGOOGLEOAUTHCLIENT-575276 + "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 160ce7a..4ce70f5 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "2.0.3-SNAPSHOT" +version in ThisBuild := "2.1.0-SNAPSHOT"