Skip to content

Commit edc8d6e

Browse files
feat: allow custom scopes for compute engine creds (#514)
* feat: allow custom scopes for compute engine creds * update
1 parent 5e49463 commit edc8d6e

File tree

2 files changed

+124
-5
lines changed

2 files changed

+124
-5
lines changed

oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,17 @@
4343
import com.google.auth.ServiceAccountSigner;
4444
import com.google.auth.http.HttpTransportFactory;
4545
import com.google.common.annotations.Beta;
46+
import com.google.common.base.Joiner;
4647
import com.google.common.base.MoreObjects;
48+
import com.google.common.collect.ImmutableSet;
4749
import java.io.IOException;
4850
import java.io.InputStream;
4951
import java.io.ObjectInputStream;
5052
import java.net.SocketTimeoutException;
5153
import java.net.UnknownHostException;
54+
import java.util.ArrayList;
55+
import java.util.Arrays;
56+
import java.util.Collection;
5257
import java.util.Collections;
5358
import java.util.Date;
5459
import java.util.List;
@@ -94,6 +99,8 @@ public class ComputeEngineCredentials extends GoogleCredentials
9499

95100
private final String transportFactoryClassName;
96101

102+
private final Collection<String> scopes;
103+
97104
private transient HttpTransportFactory transportFactory;
98105
private transient String serviceAccountEmail;
99106

@@ -102,13 +109,28 @@ public class ComputeEngineCredentials extends GoogleCredentials
102109
*
103110
* @param transportFactory HTTP transport factory, creates the transport used to get access
104111
* tokens.
112+
* @param scopes scope strings for the APIs to be called. May be null or an empty collection.
105113
*/
106-
private ComputeEngineCredentials(HttpTransportFactory transportFactory) {
114+
private ComputeEngineCredentials(
115+
HttpTransportFactory transportFactory, Collection<String> scopes) {
107116
this.transportFactory =
108117
firstNonNull(
109118
transportFactory,
110119
getFromServiceLoader(HttpTransportFactory.class, OAuth2Utils.HTTP_TRANSPORT_FACTORY));
111120
this.transportFactoryClassName = this.transportFactory.getClass().getName();
121+
if (scopes == null) {
122+
this.scopes = ImmutableSet.<String>of();
123+
} else {
124+
List<String> scopeList = new ArrayList<String>(scopes);
125+
scopeList.removeAll(Arrays.asList("", null));
126+
this.scopes = ImmutableSet.<String>copyOf(scopeList);
127+
}
128+
}
129+
130+
/** Clones the compute engine account with the specified scopes. */
131+
@Override
132+
public GoogleCredentials createScoped(Collection<String> newScopes) {
133+
return new ComputeEngineCredentials(this.transportFactory, newScopes);
112134
}
113135

114136
/**
@@ -117,13 +139,30 @@ private ComputeEngineCredentials(HttpTransportFactory transportFactory) {
117139
* @return new ComputeEngineCredentials
118140
*/
119141
public static ComputeEngineCredentials create() {
120-
return new ComputeEngineCredentials(null);
142+
return new ComputeEngineCredentials(null, null);
143+
}
144+
145+
public final Collection<String> getScopes() {
146+
return scopes;
147+
}
148+
149+
/**
150+
* If scopes is specified, add "?scopes=comma-separated-list-of-scopes" to the token url.
151+
*
152+
* @return token url with the given scopes
153+
*/
154+
String createTokenUrlWithScopes() {
155+
GenericUrl tokenUrl = new GenericUrl(getTokenServerEncodedUrl());
156+
if (!scopes.isEmpty()) {
157+
tokenUrl.set("scopes", Joiner.on(',').join(scopes));
158+
}
159+
return tokenUrl.toString();
121160
}
122161

123162
/** Refresh the access token by getting it from the GCE metadata server */
124163
@Override
125164
public AccessToken refreshAccessToken() throws IOException {
126-
HttpResponse response = getMetadataResponse(getTokenServerEncodedUrl());
165+
HttpResponse response = getMetadataResponse(createTokenUrlWithScopes());
127166
int statusCode = response.getStatusCode();
128167
if (statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
129168
throw new IOException(
@@ -307,7 +346,8 @@ public boolean equals(Object obj) {
307346
return false;
308347
}
309348
ComputeEngineCredentials other = (ComputeEngineCredentials) obj;
310-
return Objects.equals(this.transportFactoryClassName, other.transportFactoryClassName);
349+
return Objects.equals(this.transportFactoryClassName, other.transportFactoryClassName)
350+
&& Objects.equals(this.scopes, other.scopes);
311351
}
312352

313353
private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException {
@@ -399,24 +439,35 @@ private String getDefaultServiceAccount() throws IOException {
399439

400440
public static class Builder extends GoogleCredentials.Builder {
401441
private HttpTransportFactory transportFactory;
442+
private Collection<String> scopes;
402443

403444
protected Builder() {}
404445

405446
protected Builder(ComputeEngineCredentials credentials) {
406447
this.transportFactory = credentials.transportFactory;
448+
this.scopes = credentials.scopes;
407449
}
408450

409451
public Builder setHttpTransportFactory(HttpTransportFactory transportFactory) {
410452
this.transportFactory = transportFactory;
411453
return this;
412454
}
413455

456+
public Builder setScopes(Collection<String> scopes) {
457+
this.scopes = scopes;
458+
return this;
459+
}
460+
414461
public HttpTransportFactory getHttpTransportFactory() {
415462
return transportFactory;
416463
}
417464

465+
public Collection<String> getScopes() {
466+
return scopes;
467+
}
468+
418469
public ComputeEngineCredentials build() {
419-
return new ComputeEngineCredentials(transportFactory);
470+
return new ComputeEngineCredentials(transportFactory, scopes);
420471
}
421472
}
422473
}

oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
import java.io.IOException;
5757
import java.net.URI;
5858
import java.util.Arrays;
59+
import java.util.Collection;
60+
import java.util.Collections;
5961
import java.util.List;
6062
import java.util.Map;
6163
import org.junit.Test;
@@ -68,6 +70,9 @@ public class ComputeEngineCredentialsTest extends BaseSerializationTest {
6870

6971
private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo");
7072

73+
private static final String TOKEN_URL =
74+
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
75+
7176
// Id Token which includes basic default claims
7277
public static final String STANDARD_ID_TOKEN =
7378
"eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmMzc1ODkwOGI3OTIyO"
@@ -113,6 +118,69 @@ public HttpTransport create() {
113118
}
114119
}
115120

121+
@Test
122+
public void createTokenUrlWithScopes_null_scopes() {
123+
ComputeEngineCredentials credentials =
124+
ComputeEngineCredentials.newBuilder().setScopes(null).build();
125+
Collection<String> scopes = credentials.getScopes();
126+
String tokenUrlWithScopes = credentials.createTokenUrlWithScopes();
127+
128+
assertEquals(TOKEN_URL, tokenUrlWithScopes);
129+
assertTrue(scopes.isEmpty());
130+
}
131+
132+
@Test
133+
public void createTokenUrlWithScopes_empty_scopes() {
134+
ComputeEngineCredentials.Builder builder =
135+
ComputeEngineCredentials.newBuilder().setScopes(Collections.<String>emptyList());
136+
ComputeEngineCredentials credentials = builder.build();
137+
Collection<String> scopes = credentials.getScopes();
138+
String tokenUrlWithScopes = credentials.createTokenUrlWithScopes();
139+
140+
assertEquals(TOKEN_URL, tokenUrlWithScopes);
141+
assertTrue(scopes.isEmpty());
142+
assertTrue(builder.getScopes().isEmpty());
143+
}
144+
145+
@Test
146+
public void createTokenUrlWithScopes_single_scope() {
147+
ComputeEngineCredentials credentials =
148+
ComputeEngineCredentials.newBuilder().setScopes(Arrays.asList("foo")).build();
149+
String tokenUrlWithScopes = credentials.createTokenUrlWithScopes();
150+
Collection<String> scopes = credentials.getScopes();
151+
152+
assertEquals(TOKEN_URL + "?scopes=foo", tokenUrlWithScopes);
153+
assertEquals(1, scopes.size());
154+
assertEquals("foo", scopes.toArray()[0]);
155+
}
156+
157+
@Test
158+
public void createTokenUrlWithScopes_multiple_scopes() {
159+
ComputeEngineCredentials credentials =
160+
ComputeEngineCredentials.newBuilder()
161+
.setScopes(Arrays.asList(null, "foo", "", "bar"))
162+
.build();
163+
Collection<String> scopes = credentials.getScopes();
164+
String tokenUrlWithScopes = credentials.createTokenUrlWithScopes();
165+
166+
assertEquals(TOKEN_URL + "?scopes=foo,bar", tokenUrlWithScopes);
167+
assertEquals(2, scopes.size());
168+
assertEquals("foo", scopes.toArray()[0]);
169+
assertEquals("bar", scopes.toArray()[1]);
170+
}
171+
172+
@Test
173+
public void createScoped() {
174+
ComputeEngineCredentials credentials =
175+
ComputeEngineCredentials.newBuilder().setScopes(null).build();
176+
ComputeEngineCredentials credentialsWithScopes =
177+
(ComputeEngineCredentials) credentials.createScoped(Arrays.asList("foo"));
178+
Collection<String> scopes = credentialsWithScopes.getScopes();
179+
180+
assertEquals(1, scopes.size());
181+
assertEquals("foo", scopes.toArray()[0]);
182+
}
183+
116184
@Test
117185
public void getRequestMetadata_hasAccessToken() throws IOException {
118186
String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2";

0 commit comments

Comments
 (0)