Skip to content

Commit 081ec5c

Browse files
committed
Add support for WIF credentials in App Distribution Gradle Plugin
1 parent d09782d commit 081ec5c

13 files changed

Lines changed: 157 additions & 141 deletions

File tree

firebase-appdistribution-gradle/src/main/java/com/google/firebase/appdistribution/gradle/AppDistributionEnvironment.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616

1717
package com.google.firebase.appdistribution.gradle
1818

19-
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential
20-
import com.google.api.client.http.HttpTransport
19+
import com.google.auth.http.HttpCredentialsAdapter
2120

2221
interface AppDistributionEnvironment {
23-
fun getFirebaseCliLoginCredentials(transport: HttpTransport): GoogleCredential?
22+
fun getFirebaseCliLoginCredentials(): HttpCredentialsAdapter?
2423

2524
companion object {
2625
const val ENV_FIREBASE_TOKEN = "FIREBASE_TOKEN"

firebase-appdistribution-gradle/src/main/java/com/google/firebase/appdistribution/gradle/AppDistributionEnvironmentImpl.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616

1717
package com.google.firebase.appdistribution.gradle
1818

19-
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential
20-
import com.google.api.client.http.HttpTransport
19+
import com.google.auth.http.HttpCredentialsAdapter
2120
import com.google.firebase.appdistribution.gradle.models.FirebaseCliConfig
2221
import com.google.gson.FieldNamingPolicy
2322
import com.google.gson.GsonBuilder
@@ -28,7 +27,7 @@ import java.nio.file.Paths
2827
import org.gradle.api.logging.Logging
2928

3029
class AppDistributionEnvironmentImpl : AppDistributionEnvironment {
31-
override fun getFirebaseCliLoginCredentials(transport: HttpTransport): GoogleCredential? {
30+
override fun getFirebaseCliLoginCredentials(): HttpCredentialsAdapter? {
3231
val gson =
3332
GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create()
3433

@@ -46,7 +45,7 @@ class AppDistributionEnvironmentImpl : AppDistributionEnvironment {
4645

4746
// Step 3: Generate new credential using refresh token
4847
if (config?.tokens?.refreshToken != null) {
49-
val refreshToken = RefreshToken(config.tokens.refreshToken, transport)
48+
val refreshToken = RefreshToken(config.tokens.refreshToken)
5049
return refreshToken.generateNewCredentials()
5150
}
5251
} catch (e: IOException) {

firebase-appdistribution-gradle/src/main/java/com/google/firebase/appdistribution/gradle/AuthenticatedHttpClient.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616

1717
package com.google.firebase.appdistribution.gradle
1818

19-
import com.google.api.client.auth.oauth2.Credential
2019
import com.google.api.client.googleapis.GoogleUtils
2120
import com.google.api.client.http.GenericUrl
2221
import com.google.api.client.http.HttpContent
2322
import com.google.api.client.http.HttpRequestFactory
23+
import com.google.api.client.http.HttpRequestInitializer
2424
import com.google.api.client.http.HttpTransport
2525
import com.google.api.client.http.javanet.NetHttpTransport
2626
import java.io.IOException
@@ -34,8 +34,8 @@ private constructor(
3434
private val httpRequestFactory: HttpRequestFactory,
3535
) {
3636
constructor(
37-
credential: Credential
38-
) : this(newGoogleHttpTransport().createRequestFactory(credential))
37+
requestInitializer: HttpRequestInitializer
38+
) : this(newGoogleHttpTransport().createRequestFactory(requestInitializer))
3939

4040
constructor(httpTransport: HttpTransport) : this(httpTransport.createRequestFactory())
4141

firebase-appdistribution-gradle/src/main/java/com/google/firebase/appdistribution/gradle/CredentialsRetriever.kt

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,37 +16,32 @@
1616

1717
package com.google.firebase.appdistribution.gradle
1818

19-
import com.google.api.client.auth.oauth2.Credential
20-
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport
21-
import com.google.api.client.http.HttpTransport
2219
import com.google.firebase.appdistribution.gradle.AppDistributionEnvironment.Companion.ENV_FIREBASE_TOKEN
2320
import com.google.firebase.appdistribution.gradle.AppDistributionEnvironment.Companion.ENV_GOOGLE_APPLICATION_CREDENTIALS
2421
import com.google.firebase.appdistribution.gradle.AppDistributionException.Reason.REFRESH_TOKEN_ERROR
2522
import com.google.firebase.appdistribution.gradle.AppDistributionException.Reason.SERVICE_CREDENTIALS_NOT_FOUND
2623
import com.google.firebase.appdistribution.gradle.OptionsUtils.ensureFileExists
2724
import com.google.firebase.appdistribution.gradle.models.ServiceAccountCredentials
25+
import com.google.auth.http.HttpCredentialsAdapter
26+
import com.google.auth.oauth2.GoogleCredentials
2827
import java.io.IOException
2928
import org.gradle.api.logging.Logging
3029

3130
/** Helper class for retrieving auth credentials from one of the four possible auth mechanisms. */
3231
class CredentialsRetriever(
33-
private val httpTransport: HttpTransport = GoogleNetHttpTransport.newTrustedTransport(),
3432
private val appDistributionEnvironment: AppDistributionEnvironment =
35-
AppDistributionEnvironmentImpl()
33+
AppDistributionEnvironmentImpl(),
34+
private val adcCredentialsProvider: () -> GoogleCredentials = { GoogleCredentials.getApplicationDefault() }
3635
) {
3736

38-
internal constructor(
39-
appDistributionEnvironment: AppDistributionEnvironment
40-
) : this(GoogleNetHttpTransport.newTrustedTransport(), appDistributionEnvironment)
41-
4237
/** Returns the auth credential, if found. Otherwise returns null. */
43-
fun getAuthCredential(serviceCredentialsPath: String? = null): Credential? {
38+
fun getAuthCredential(serviceCredentialsPath: String? = null): HttpCredentialsAdapter? {
4439
// Check the explicitly passed in service credentials first
4540
if (serviceCredentialsPath != null) {
4641
val serviceCredentialsFile =
4742
ensureFileExists(serviceCredentialsPath, SERVICE_CREDENTIALS_NOT_FOUND)
4843
return try {
49-
ServiceAccountCredentials.fromFile(serviceCredentialsFile).googleCredential
44+
ServiceAccountCredentials.fromFile(serviceCredentialsFile).credentialsAdapter
5045
} catch (e: IOException) {
5146
throw AppDistributionException(
5247
SERVICE_CREDENTIALS_NOT_FOUND,
@@ -64,7 +59,7 @@ class CredentialsRetriever(
6459
"Using credentials token specified by environment variable {}",
6560
ENV_FIREBASE_TOKEN
6661
)
67-
RefreshToken(envRefreshToken, httpTransport).generateNewCredentials()
62+
RefreshToken(envRefreshToken).generateNewCredentials()
6863
} catch (e: Exception) {
6964
throw AppDistributionException(
7065
REFRESH_TOKEN_ERROR,
@@ -75,8 +70,7 @@ class CredentialsRetriever(
7570
}
7671

7772
// Then check for cached Firebase CLI tokens
78-
val firebaseCliLoginCreds =
79-
appDistributionEnvironment.getFirebaseCliLoginCredentials(httpTransport)
73+
val firebaseCliLoginCreds = appDistributionEnvironment.getFirebaseCliLoginCredentials()
8074
if (firebaseCliLoginCreds != null) {
8175
logger.info("Using cached Firebase CLI credentials")
8276
return firebaseCliLoginCreds
@@ -95,7 +89,7 @@ class CredentialsRetriever(
9589
return try {
9690
val envServiceAccountCredentials =
9791
ServiceAccountCredentials.fromFile(serviceCredentialsFile)
98-
envServiceAccountCredentials.googleCredential
92+
envServiceAccountCredentials.credentialsAdapter
9993
} catch (e: IOException) {
10094
throw AppDistributionException(
10195
SERVICE_CREDENTIALS_NOT_FOUND,
@@ -105,6 +99,15 @@ class CredentialsRetriever(
10599
}
106100
}
107101

102+
// Lastly, check for standard Application Default Credentials (ADC) as a fallback
103+
try {
104+
val credentials = adcCredentialsProvider().createScoped(ApiEndpoints.SCOPES)
105+
logger.info("Using Application Default Credentials (ADC)")
106+
return HttpCredentialsAdapter(credentials)
107+
} catch (e: IOException) {
108+
logger.debug("Failed to load Application Default Credentials (ADC)", e)
109+
}
110+
108111
// If we reach this point, we were unable to find valid credentials
109112
return null
110113
}

firebase-appdistribution-gradle/src/main/java/com/google/firebase/appdistribution/gradle/FirebaseAppDistribution.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package com.google.firebase.appdistribution.gradle
1818

19-
import com.google.api.client.auth.oauth2.Credential
19+
import com.google.api.client.http.HttpRequestInitializer
2020
import com.google.api.client.http.HttpResponseException
2121
import com.google.firebase.appdistribution.gradle.AppDistributionException.Reason.MISSING_CREDENTIALS
2222
import java.io.IOException
@@ -76,9 +76,11 @@ object FirebaseAppDistribution {
7676
private fun getCredential(options: TesterManagementOptions) =
7777
options.credential ?: throw AppDistributionException(MISSING_CREDENTIALS)
7878

79-
private fun getAuthenticatedHttpClient(credential: Credential): AuthenticatedHttpClient {
79+
private fun getAuthenticatedHttpClient(
80+
requestInitializer: HttpRequestInitializer
81+
): AuthenticatedHttpClient {
8082
return try {
81-
AuthenticatedHttpClient(credential)
83+
AuthenticatedHttpClient(requestInitializer)
8284
} catch (e: Exception) {
8385
when (e) {
8486
is GeneralSecurityException,

firebase-appdistribution-gradle/src/main/java/com/google/firebase/appdistribution/gradle/RefreshToken.kt

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,50 +16,38 @@
1616

1717
package com.google.firebase.appdistribution.gradle
1818

19-
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential
20-
import com.google.api.client.googleapis.auth.oauth2.GoogleRefreshTokenRequest
21-
import com.google.api.client.http.HttpTransport
22-
import com.google.api.client.json.gson.GsonFactory
23-
import java.io.IOException
19+
import com.google.auth.http.HttpCredentialsAdapter
20+
import com.google.auth.oauth2.UserCredentials
2421

2522
/**
26-
* This class encapsulates a refresh token that can be used for generate a new [GoogleCredential] to
27-
* be used when authenticating requests. By using the refresh token the is returned after the oauth
28-
* flow, we can generate new access tokens for future requests.
23+
* This class encapsulates a refresh token that can be used for generate a new
24+
* [HttpCredentialsAdapter] to be used when authenticating requests. By using the refresh token that
25+
* is returned after the oauth flow, we can generate new access tokens for future requests.
2926
*/
30-
class RefreshToken(private val refreshToken: String, private val transport: HttpTransport) {
27+
class RefreshToken(private val refreshToken: String) {
3128
/**
3229
* Using the provided refresh token, this makes a request to generate a new access token that can
3330
* be used to authenticate requests
3431
*
35-
* @return the credentials that were generated from the refresh token. Its worth noting that these
36-
* credentials do not include the refresh token, only a new access token
37-
* @throws IOException thrown if there was a problem making a request to the oauth provider
32+
* @return the credentials that were generated from the refresh token.
3833
*/
39-
fun generateNewCredentials(): GoogleCredential {
40-
val response =
41-
GoogleRefreshTokenRequest(
42-
transport,
43-
GsonFactory.getDefaultInstance(),
44-
refreshToken,
45-
CLIENT_ID,
46-
CLIENT_SECRET
47-
)
48-
.execute()
49-
50-
val credential = GoogleCredential.Builder().build()
51-
credential.setFromTokenResponse(response)
52-
53-
return credential
34+
fun generateNewCredentials(): HttpCredentialsAdapter {
35+
return HttpCredentialsAdapter(
36+
UserCredentials.newBuilder()
37+
.setClientId(CLIENT_ID)
38+
.setClientSecret(CLIENT_SECRET)
39+
.setRefreshToken(refreshToken)
40+
.build()
41+
)
5442
}
5543

5644
companion object {
5745
// Firebase CLI ClientID and ClientSecret
5846
// In this type of application, the client secret is not treated as a secret.
5947
// See: https://developers.google.com/identity/protocols/OAuth2InstalledApp
6048
// TODO: Use separate ID's/secrets for each tool
61-
private const val CLIENT_ID =
49+
internal const val CLIENT_ID =
6250
"563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com"
63-
private const val CLIENT_SECRET = "j9iVZfS8kkCEFUPaAeJV0sAi"
51+
internal const val CLIENT_SECRET = "j9iVZfS8kkCEFUPaAeJV0sAi"
6452
}
6553
}

firebase-appdistribution-gradle/src/main/java/com/google/firebase/appdistribution/gradle/TesterManagementOptions.kt

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package com.google.firebase.appdistribution.gradle
1717

18-
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport
18+
import com.google.auth.http.HttpCredentialsAdapter
1919
import com.google.firebase.appdistribution.gradle.AppDistributionException.Reason.MISSING_PROJECT_NUMBER
2020
import com.google.firebase.appdistribution.gradle.AppDistributionException.Reason.MISSING_TESTER_EMAILS
2121
import com.google.firebase.appdistribution.gradle.OptionsUtils.getValueFromStringOrFile
@@ -27,14 +27,11 @@ class TesterManagementOptions(
2727
emailsValue: String? = null,
2828
emailsFile: String? = null,
2929
serviceCredentialsFile: String? = null,
30-
credentialsRetriever: CredentialsRetriever =
31-
CredentialsRetriever(
32-
GoogleNetHttpTransport.newTrustedTransport(),
33-
AppDistributionEnvironmentImpl()
34-
)
30+
credentialsRetriever: CredentialsRetriever = CredentialsRetriever()
3531
) {
3632
val emails = splitCommaOrNewlineSeparatedString(getValueFromStringOrFile(emailsValue, emailsFile))
37-
val credential = credentialsRetriever.getAuthCredential(serviceCredentialsFile)
33+
val credential: HttpCredentialsAdapter? =
34+
credentialsRetriever.getAuthCredential(serviceCredentialsFile)
3835

3936
init {
4037
// If project number is set it would be greater than 0, the default primitive value

firebase-appdistribution-gradle/src/main/java/com/google/firebase/appdistribution/gradle/UploadDistributionOptions.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616

1717
package com.google.firebase.appdistribution.gradle
1818

19-
import com.google.api.client.auth.oauth2.Credential
20-
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport
21-
import com.google.api.client.http.HttpTransport
19+
import com.google.auth.http.HttpCredentialsAdapter
2220
import com.google.common.base.Splitter
2321
import com.google.firebase.appdistribution.gradle.AppDistributionException.Reason.Companion.binaryNotFoundError
2422
import com.google.firebase.appdistribution.gradle.AppDistributionException.Reason.INVALID_APP_ID
@@ -47,7 +45,6 @@ internal constructor(
4745
val debug: Boolean = false,
4846
val testNonBlocking: Boolean = false,
4947
binaryPath: String,
50-
transport: HttpTransport = GoogleNetHttpTransport.newTrustedTransport(),
5148
appDistributionEnvironment: AppDistributionEnvironment = AppDistributionEnvironmentImpl(),
5249
serviceCredentialsFile: String? = null,
5350
releaseNotesValue: String? = null,
@@ -71,8 +68,8 @@ internal constructor(
7168
val testers = extractValues(testersValue, testersPath)
7269
val groups = extractValues(groupsValue, groupsPath)
7370
val testDevices = extractTestDevices(testDevicesValue, testDevicesPath)
74-
val credential: Credential? =
75-
CredentialsRetriever(transport, appDistributionEnvironment)
71+
val credential: HttpCredentialsAdapter? =
72+
CredentialsRetriever(appDistributionEnvironment)
7673
.getAuthCredential(serviceCredentialsFile)
7774
val testCases = extractValues(testCasesValue, testCasesPath)
7875

firebase-appdistribution-gradle/src/main/java/com/google/firebase/appdistribution/gradle/models/ServiceAccountCredentials.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,21 @@
1616

1717
package com.google.firebase.appdistribution.gradle.models
1818

19-
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential
19+
import com.google.auth.http.HttpCredentialsAdapter
20+
import com.google.auth.oauth2.GoogleCredentials
2021
import com.google.firebase.appdistribution.gradle.ApiEndpoints
2122
import java.io.File
2223
import java.nio.file.Files
2324

24-
class ServiceAccountCredentials private constructor(val googleCredential: GoogleCredential) {
25+
class ServiceAccountCredentials
26+
private constructor(val credentialsAdapter: HttpCredentialsAdapter) {
2527
companion object {
2628
fun fromFile(credentials: File): ServiceAccountCredentials =
2729
ServiceAccountCredentials(
28-
GoogleCredential.fromStream(Files.newInputStream(credentials.toPath()))
29-
.createScoped(ApiEndpoints.SCOPES)
30+
HttpCredentialsAdapter(
31+
GoogleCredentials.fromStream(Files.newInputStream(credentials.toPath()))
32+
.createScoped(ApiEndpoints.SCOPES)
33+
)
3034
)
3135
}
3236
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "external_account",
3+
"audience": "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/my-pool/providers/my-provider",
4+
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
5+
"token_url": "https://sts.googleapis.com/v1/token",
6+
"credential_source": {
7+
"file": "/var/run/secrets/token"
8+
}
9+
}

0 commit comments

Comments
 (0)