Skip to content

Commit 8981e50

Browse files
Merge pull request #864 from googleads/adcForJava
Implement application default credentials
2 parents 5894a27 + c54957a commit 8981e50

3 files changed

Lines changed: 145 additions & 60 deletions

File tree

ads.properties.sample

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# https://developers.google.com/google-ads/api/docs/client-libs/java/quick-start
77
#
88
# - By default this is searched for in ~/ads.properties.
9-
# e.g. GoogleAdsClient.newBuilder().fromPropertiesFile()
9+
# e.g. GoogleAdsClient.newBuilder().fromPropertiesFile()
1010
#
1111
# - Can load from an arbitrary path.
1212
# e.g. GoogleAdsClient.newBuilder().fromPropertiesFile("some/path")
@@ -16,7 +16,11 @@
1616
# .fromPropertiesFile() // optionally load from config file.
1717
# .setDeveloperToken("your_token")
1818

19-
# ------------------------------- OAuth ---------------------------------------
19+
# ------------------------------- OAuth 2.0 ---------------------------------------
20+
#
21+
# The following properties are required for OAuth 2.0 and not required for
22+
# Application Default Credentials. See comments below for Application Default
23+
# Credentials.
2024

2125
# OAuth client ID
2226
#
@@ -29,28 +33,41 @@
2933

3034
# OAuth client secret
3135
#
32-
# Specifies the client secret that is used for Google's OAuth 2 API.
36+
# Specifies the client secret that is used for Google's OAuth 2 API.
3337
#
3438
# - Must be the secret associated with the OAuth clientId.
3539
# - Created and managed via http://console.developers.google.com.
3640
#api.googleads.clientSecret=
3741

3842
# OAuth refresh token
3943
#
40-
# Credential which authorizes access to a Google account.
44+
# Credential which authorizes access to a Google account.
4145
#
4246
# - See guide for instructions on how to obtain a refresh token.
4347
# https://developers.google.com/google-ads/api/docs/oauth/client-library
4448
# - Provides access to all Google Ads accounts on which the associated Google
4549
# account is a user.
46-
# - Ad account is specified by the customerId in each request.
50+
# - Ad account is specified by the customerId in each request.
4751
#api.googleads.refreshToken=
4852

53+
# ------------------- Application Default Credentials --------------------------
54+
#
55+
# Specifies whether to use application default credentials.
56+
#
57+
# - If true, the library will attempt to authenticate using application default
58+
# credentials.
59+
# - If false or not specified, the library will use the OAuth credentials
60+
# specified above.
61+
# - See https://developers.google.com/identity/protocols/application-default-credentials
62+
# for more information on how to configure ADC.
63+
#
64+
#api.googleads.useApplicationDefaultCredentials=false
65+
4966
# -------------------------- Google Ads API -----------------------------------
5067

5168
# Developer token
52-
#
53-
# Specifies the credential used to access the Google Ads API.
69+
#
70+
# Specifies the credential used to access the Google Ads API.
5471
#
5572
# - Grants access to the API, not any Google account or Google Ads account.
5673
# - Associated with a Google Ads manager accoount.
@@ -65,7 +82,7 @@
6582
# Specifies an ad account which grants access to ad accounts.
6683
#
6784
# - Must be specified if your access to an ad account is via a manager account.
68-
# - May be specified in all requests if your Google user has direct access to
85+
# - May be specified in all requests if your Google user has direct access to
6986
# the ad account.
7087
# - Ad account access can be managed via http://ads.google.com and navigating
7188
# to Tools & Settings -> Account Access

google-ads/src/main/java/com/google/ads/googleads/lib/GoogleAdsClient.java

Lines changed: 84 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ public abstract static class Builder {
217217
*/
218218
private Optional<GoogleCredentials.Builder> credentialsBuilder = Optional.empty();
219219

220+
/** Specifies whether to use application default credentials. */
221+
private boolean useApplicationDefaultCredentials = false;
222+
220223
/** Returns the credentials currently configured. */
221224
public abstract Credentials getCredentials();
222225

@@ -230,61 +233,81 @@ public abstract static class Builder {
230233
*/
231234
public abstract Builder setCredentials(Credentials credentials);
232235

236+
public Builder enableApplicationDefaultCredentials() {
237+
this.useApplicationDefaultCredentials = true;
238+
return this;
239+
}
240+
233241
private void setCredentials(Properties properties) throws IOException {
234-
// Validates that entries are present for exactly one of installed app/web flow credentials or
235-
// service account credentials.
242+
boolean useAdc =
243+
Boolean.parseBoolean(
244+
ConfigPropertyKey.USE_APPLICATION_DEFAULT_CREDENTIALS.getPropertyValue(properties));
245+
236246
boolean hasInstalledAppKeys =
237247
INSTALLED_APP_OAUTH_KEYS.stream().anyMatch(k -> k.getPropertyValue(properties) != null);
238248
boolean hasServiceAccountKeys =
239249
SERVICE_ACCOUNT_OAUTH_KEYS.stream().anyMatch(k -> k.getPropertyValue(properties) != null);
240-
if (!(hasInstalledAppKeys || hasServiceAccountKeys)) {
241-
// Entries missing for both types of credentials. Skips any further processing so user can
242-
// merge this builder if needed.
243-
return;
244-
}
245250

246-
if (hasInstalledAppKeys && hasServiceAccountKeys) {
247-
// Entries specified for both types of credentials.
248-
throw new IllegalArgumentException(
249-
String.format(
250-
"Entries found in properties for both %s and %s. Please modify properties to either"
251-
+ " include entries for %s if using installed application/web flow credentials,"
252-
+ " or %s if using service account credentials.",
253-
ConfigPropertyKey.SERVICE_ACCOUNT_SECRETS_PATH,
254-
ConfigPropertyKey.REFRESH_TOKEN,
255-
INSTALLED_APP_OAUTH_KEYS,
256-
SERVICE_ACCOUNT_OAUTH_KEYS));
257-
}
258-
259-
if (credentialsBuilder.isPresent()) {
260-
if ((credentialsBuilder.get() instanceof UserCredentials.Builder)
261-
&& hasServiceAccountKeys) {
251+
if (useAdc) {
252+
if (hasInstalledAppKeys || hasServiceAccountKeys) {
262253
throw new IllegalArgumentException(
263-
"Entries found in properties for service account credentials, "
264-
+ "but this builder is already partially configured for installed application/"
265-
+ "web flow credentials.");
266-
} else if ((credentialsBuilder.get() instanceof ServiceAccountCredentials.Builder)
267-
&& hasInstalledAppKeys) {
254+
"Cannot use application default credentials when other credential types are"
255+
+ " specified in the configuration. Please remove other credential keys or set"
256+
+ " useApplicationDefaultCredentials to false.");
257+
}
258+
enableApplicationDefaultCredentials();
259+
} else {
260+
// Validates that entries are present for exactly one of installed app/web flow credentials
261+
// or service account credentials.
262+
if (!(hasInstalledAppKeys || hasServiceAccountKeys)) {
263+
// Entries missing for all types of credentials. Skips any further processing so user can
264+
// merge this builder if needed.
265+
return;
266+
}
267+
268+
if (hasInstalledAppKeys && hasServiceAccountKeys) {
269+
// Entries specified for both types of credentials.
268270
throw new IllegalArgumentException(
269-
"Entries found in properties for installed application/web flow credentials, but"
270-
+ " this builder is already partially configured for service account"
271-
+ " credentials.");
271+
String.format(
272+
"Entries found in properties for both %s and %s. Please modify properties to either"
273+
+ " include entries for %s if using installed application/web flow credentials,"
274+
+ " or %s if using service account credentials.",
275+
ConfigPropertyKey.SERVICE_ACCOUNT_SECRETS_PATH,
276+
ConfigPropertyKey.REFRESH_TOKEN,
277+
INSTALLED_APP_OAUTH_KEYS,
278+
SERVICE_ACCOUNT_OAUTH_KEYS));
279+
}
280+
281+
if (credentialsBuilder.isPresent()) {
282+
if ((credentialsBuilder.get() instanceof UserCredentials.Builder)
283+
&& hasServiceAccountKeys) {
284+
throw new IllegalArgumentException(
285+
"Entries found in properties for service account credentials, "
286+
+ "but this builder is already partially configured for installed application/"
287+
+ "web flow credentials.");
288+
} else if ((credentialsBuilder.get() instanceof ServiceAccountCredentials.Builder)
289+
&& hasInstalledAppKeys) {
290+
throw new IllegalArgumentException(
291+
"Entries found in properties for installed application/web flow credentials, but"
292+
+ " this builder is already partially configured for service account"
293+
+ " credentials.");
294+
}
272295
}
273-
}
274296

275-
// Clears the explicitly set credentials. This ensures that the credentials configured here
276-
// will be used, even if the user previously explicitly set credentials. See build() for
277-
// details.
278-
setCredentials((Credentials) null);
297+
// Clears the explicitly set credentials. This ensures that the credentials configured here
298+
// will be used, even if the user previously explicitly set credentials. See build() for
299+
// details.
300+
setCredentials((Credentials) null);
279301

280-
// Updates the credentials builder using the appropriate set of properties.
281-
GoogleCredentials.Builder updatedBuilder;
282-
if (hasInstalledAppKeys) {
283-
updatedBuilder = configureUserCredentials(properties, credentialsBuilder);
284-
} else {
285-
updatedBuilder = configureServiceAccountCredentials(properties, credentialsBuilder);
302+
// Updates the credentials builder using the appropriate set of properties.
303+
GoogleCredentials.Builder updatedBuilder;
304+
if (hasInstalledAppKeys) {
305+
updatedBuilder = configureUserCredentials(properties, credentialsBuilder);
306+
} else {
307+
updatedBuilder = configureServiceAccountCredentials(properties, credentialsBuilder);
308+
}
309+
credentialsBuilder = Optional.of(updatedBuilder);
286310
}
287-
credentialsBuilder = Optional.of(updatedBuilder);
288311
}
289312

290313
/**
@@ -342,8 +365,7 @@ private GoogleCredentials.Builder configureServiceAccountCredentials(
342365
builder = (ServiceAccountCredentials.Builder) optionalBuilder.get();
343366
} else {
344367
builder =
345-
ServiceAccountCredentials.newBuilder()
346-
.setScopes(Collections.singleton(GOOGLE_ADS_API_SCOPE));
368+
ServiceAccountCredentials.newBuilder().setScopes(Collections.singleton(GOOGLE_ADS_API_SCOPE));
347369
}
348370

349371
String serviceAccountSecretsPath =
@@ -614,14 +636,23 @@ public GoogleAdsClient build() {
614636
// with properties/environment variables that include credential settings. Note that
615637
// fromProperties() will set credentials to null in this case.
616638
// - The user explicitly set credentials via setCredentials(Credentials).
617-
if (getCredentials() == null) {
618-
if (credentialsBuilder.isPresent()) {
619-
// Sets the credentials using the credentials builder.
620-
setCredentials(credentialsBuilder.get().build());
621-
} else {
622-
throw new IllegalStateException("Property \"credentials\" has not been set");
639+
if (useApplicationDefaultCredentials) {
640+
try {
641+
GoogleCredentials credentials =
642+
GoogleCredentials.getApplicationDefault().createScoped(Collections.singleton(GOOGLE_ADS_API_SCOPE));
643+
setCredentials(credentials);
644+
} catch (IOException e) {
645+
throw new RuntimeException("Failed to get application default credentials", e);
623646
}
624647
} else {
648+
if (getCredentials() == null) {
649+
if (credentialsBuilder.isPresent()) {
650+
// Sets the credentials using the credentials builder.
651+
setCredentials(credentialsBuilder.get().build());
652+
} else {
653+
throw new IllegalStateException("Property \"credentials\" has not been set");
654+
}
655+
}
625656
// The last action by the user was to invoke setCredentials(Credentials), so no further
626657
// action is needed.
627658
}
@@ -777,7 +808,8 @@ public enum ConfigPropertyKey {
777808
// Service account keys
778809
SERVICE_ACCOUNT_SECRETS_PATH("api.googleads.serviceAccountSecretsPath"),
779810
SERVICE_ACCOUNT_USER("api.googleads.serviceAccountUser"),
780-
MAX_INBOUND_MESSAGE_BYTES("api.googleads.maxInboundMessageSize");
811+
MAX_INBOUND_MESSAGE_BYTES("api.googleads.maxInboundMessageSize"),
812+
USE_APPLICATION_DEFAULT_CREDENTIALS("api.googleads.useApplicationDefaultCredentials");
781813

782814
private final String key;
783815

google-ads/src/test/java/com/google/ads/googleads/lib/GoogleAdsClientTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,42 @@ public void buildFromProperties_neitherCredentialsPresent_throwsException_atBuil
403403
assertThat(throwable.getMessage(), Matchers.containsString("credentials"));
404404
}
405405

406+
@Test
407+
public void build_withEnableApplicationDefaultCredentials_succeeds() {
408+
// Note: this test will fail if run in an environment where ADC are not configured.
409+
// For tests, this is configured via the build system.
410+
GoogleAdsClient client =
411+
GoogleAdsClient.newBuilder()
412+
.enableApplicationDefaultCredentials()
413+
.setDeveloperToken(DEVELOPER_TOKEN)
414+
.build();
415+
assertNotNull(client.getCredentials());
416+
}
417+
418+
@Test
419+
public void buildFromProperties_withUseApplicationDefaultCredentials_succeeds() {
420+
// Note: this test will fail if run in an environment where ADC are not configured.
421+
// For tests, this is configured via the build system.
422+
testProperties.remove(ConfigPropertyKey.CLIENT_ID.getPropertyKey());
423+
testProperties.remove(ConfigPropertyKey.CLIENT_SECRET.getPropertyKey());
424+
testProperties.remove(ConfigPropertyKey.REFRESH_TOKEN.getPropertyKey());
425+
testProperties.setProperty(
426+
ConfigPropertyKey.USE_APPLICATION_DEFAULT_CREDENTIALS.getPropertyKey(), "true");
427+
GoogleAdsClient client = GoogleAdsClient.newBuilder().fromProperties(testProperties).build();
428+
assertNotNull(client.getCredentials());
429+
}
430+
431+
@Test
432+
public void buildFromProperties_withUseApplicationDefaultCredentialsAndOtherCredentials_fails() {
433+
testProperties.setProperty(
434+
ConfigPropertyKey.USE_APPLICATION_DEFAULT_CREDENTIALS.getPropertyKey(), "true");
435+
IllegalArgumentException exception =
436+
assertThrows(
437+
IllegalArgumentException.class,
438+
() -> GoogleAdsClient.newBuilder().fromProperties(testProperties));
439+
assertThat(exception.getMessage(), Matchers.containsString("other credential types"));
440+
}
441+
406442
@Test
407443
public void buildFromEnvironment_mergeWithProperties_withUserCredentials() throws IOException {
408444
Map<String, String> environment =

0 commit comments

Comments
 (0)