Skip to content

Commit e4974a4

Browse files
committed
api: Default credential support for well known file from Cloud SDK.
https://codereview.appspot.com/92730043/
1 parent b016a2d commit e4974a4

File tree

3 files changed

+164
-51
lines changed

3 files changed

+164
-51
lines changed

google-api-client/src/main/java/com/google/api/client/googleapis/auth/oauth2/DefaultCredentialProvider.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import java.io.IOException;
2929
import java.io.InputStream;
3030
import java.lang.reflect.Constructor;
31+
import java.security.AccessControlException;
32+
import java.util.Locale;
3133

3234
/**
3335
* {@link Beta} <br/>
@@ -41,6 +43,10 @@ class DefaultCredentialProvider {
4143

4244
static final String CREDENTIAL_ENV_VAR = "GOOGLE_CREDENTIALS_DEFAULT";
4345

46+
static final String WELL_KNOWN_CREDENTIALS_FILE = "credentials_default.json";
47+
48+
static final String CLOUDSDK_CONFIG_DIRECTORY = "gcloud";
49+
4450
static final String HELP_PERMALINK =
4551
"https://developers.google.com/accounts/docs/default-credential";
4652

@@ -112,6 +118,8 @@ private final GoogleCredential getDefaultCredentialUnsynchronized(
112118
throw OAuth2Utils.exceptionWithCause(new IOException(String.format(
113119
"Error reading credential file from environment variable %s, value '%s': %s",
114120
CREDENTIAL_ENV_VAR, credentialsPath, e.getMessage())), e);
121+
} catch (AccessControlException expected) {
122+
// Exception querying file system is expected on App-Engine
115123
}
116124
finally {
117125
if (credentialsStream != null) {
@@ -120,6 +128,29 @@ private final GoogleCredential getDefaultCredentialUnsynchronized(
120128
}
121129
}
122130

131+
// Then try the well-known file
132+
File wellKnownFileLocation = getWellKnownCredentialsFile();
133+
try {
134+
if (fileExists(wellKnownFileLocation)) {
135+
InputStream credentialsStream = null;
136+
try {
137+
credentialsStream = new FileInputStream(wellKnownFileLocation);
138+
credential = GoogleCredential.fromStream(credentialsStream, transport, jsonFactory);
139+
} catch (IOException e) {
140+
throw new IOException(String.format(
141+
"Error reading credential file from location %s: %s",
142+
wellKnownFileLocation, e.getMessage()));
143+
}
144+
finally {
145+
if (credentialsStream != null) {
146+
credentialsStream.close();
147+
}
148+
}
149+
}
150+
} catch (AccessControlException expected) {
151+
// Exception querying file system is expected on App-Engine
152+
}
153+
123154
// Then try App Engine
124155
if (credential == null) {
125156
credential = tryGetAppEngineCredential(transport, jsonFactory);
@@ -132,13 +163,41 @@ private final GoogleCredential getDefaultCredentialUnsynchronized(
132163
return credential;
133164
}
134165

166+
private final File getWellKnownCredentialsFile() {
167+
File cloudConfigPath = null;
168+
String os = getProperty("os.name", "").toLowerCase(Locale.US);
169+
if (os.indexOf("windows") >= 0) {
170+
File appDataPath = new File(getEnv("APPDATA"));
171+
cloudConfigPath = new File(appDataPath, CLOUDSDK_CONFIG_DIRECTORY);
172+
} else {
173+
File configPath = new File(getProperty("user.home", ""), ".config");
174+
cloudConfigPath = new File(configPath, CLOUDSDK_CONFIG_DIRECTORY);
175+
}
176+
File credentialFilePath = new File(cloudConfigPath, WELL_KNOWN_CREDENTIALS_FILE);
177+
return credentialFilePath;
178+
}
179+
135180
/**
136181
* Override in test code to isolate from environment.
137182
*/
138183
String getEnv(String name) {
139184
return System.getenv(name);
140185
}
141186

187+
/**
188+
* Override in test code to isolate from environment.
189+
*/
190+
boolean fileExists(File file) {
191+
return file.exists() && !file.isDirectory();
192+
}
193+
194+
/**
195+
* Override in test code to isolate from environment.
196+
*/
197+
String getProperty(String property, String def) {
198+
return System.getProperty(property, def);
199+
}
200+
142201
/**
143202
* Override in test code to isolate from environment
144203
*/

google-api-client/src/test/java/com/google/api/client/googleapis/auth/oauth2/DefaultCredentialProviderTest.java

Lines changed: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import com.google.api.client.json.jackson2.JacksonFactory;
2525
import com.google.api.client.testing.http.MockHttpTransport;
2626
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
27-
import com.google.api.client.util.Joiner;
2827

2928
import junit.framework.TestCase;
3029

@@ -34,7 +33,9 @@
3433
import java.util.Arrays;
3534
import java.util.Collection;
3635
import java.util.HashMap;
36+
import java.util.HashSet;
3737
import java.util.Map;
38+
import java.util.Set;
3839
import java.util.concurrent.locks.Lock;
3940
import java.util.concurrent.locks.ReentrantLock;
4041

@@ -225,6 +226,61 @@ public void testDefaultCredentialUser() throws IOException {
225226
if (userCredentialFile.exists()) {
226227
userCredentialFile.delete();
227228
}
229+
230+
TestDefaultCredentialProvider testProvider = new TestDefaultCredentialProvider();
231+
// Point the default credential to the file
232+
testProvider.setEnv(DefaultCredentialProvider.CREDENTIAL_ENV_VAR,
233+
userCredentialFile.getAbsolutePath());
234+
235+
testDefaultCredentialUser(userCredentialFile, testProvider);
236+
}
237+
238+
public void testDefaultCredentialWellKnownFileNonWindows() throws IOException {
239+
// Simulate where the SDK puts the well-known file on non-Windows platforms
240+
File homeDir = getTempDirectory();
241+
File configDir = new File(homeDir, ".config");
242+
if (!configDir.exists()) {
243+
configDir.mkdir();
244+
}
245+
File cloudConfigDir = new File(configDir, DefaultCredentialProvider.CLOUDSDK_CONFIG_DIRECTORY);
246+
if (!cloudConfigDir.exists()) {
247+
cloudConfigDir.mkdir();
248+
}
249+
File wellKnownFile = new File(
250+
cloudConfigDir, DefaultCredentialProvider.WELL_KNOWN_CREDENTIALS_FILE);
251+
if (wellKnownFile.exists()) {
252+
wellKnownFile.delete();
253+
}
254+
TestDefaultCredentialProvider testProvider = new TestDefaultCredentialProvider();
255+
testProvider.addFile(wellKnownFile.getAbsolutePath());
256+
testProvider.setProperty("os.name", "linux");
257+
testProvider.setProperty("user.home", homeDir.getAbsolutePath());
258+
259+
testDefaultCredentialUser(wellKnownFile, testProvider);
260+
}
261+
262+
public void testDefaultCredentialWellKnownFileWindows() throws IOException {
263+
// Simulate where the SDK puts the well-known file on Windows
264+
File appDataDir = getTempDirectory();
265+
File cloudConfigDir = new File(appDataDir, DefaultCredentialProvider.CLOUDSDK_CONFIG_DIRECTORY);
266+
if (!cloudConfigDir.exists()) {
267+
cloudConfigDir.mkdir();
268+
}
269+
File wellKnownFile = new File(
270+
cloudConfigDir, DefaultCredentialProvider.WELL_KNOWN_CREDENTIALS_FILE);
271+
if (wellKnownFile.exists()) {
272+
wellKnownFile.delete();
273+
}
274+
TestDefaultCredentialProvider testProvider = new TestDefaultCredentialProvider();
275+
testProvider.addFile(wellKnownFile.getAbsolutePath());
276+
testProvider.setProperty("os.name", "windows");
277+
testProvider.setEnv("APPDATA", appDataDir.getAbsolutePath());
278+
279+
testDefaultCredentialUser(wellKnownFile, testProvider);
280+
}
281+
282+
private void testDefaultCredentialUser(File userFile, TestDefaultCredentialProvider testProvider)
283+
throws IOException {
228284
final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2";
229285
final String CLIENT_SECRET = "jakuaL9YyieakhECKL2SwZcu";
230286
final String CLIENT_ID = "ya29.1.AADtN_UtlxH8cruGAxrN2XQnZTVRvDyVWnYq4I6dws";
@@ -235,26 +291,14 @@ public void testDefaultCredentialUser() throws IOException {
235291
transport.addClient(CLIENT_ID, CLIENT_SECRET);
236292
transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
237293

238-
TestDefaultCredentialProvider testProvider = new TestDefaultCredentialProvider();
294+
String json = GoogleCredentialTest.createUserJson(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN);
295+
239296
try {
240297
// Write out user file
241-
GenericJson userCredentialContents = new GenericJson();
242-
userCredentialContents.setFactory(JSON_FACTORY);
243-
userCredentialContents.put("client_id", CLIENT_ID);
244-
userCredentialContents.put("client_secret", CLIENT_SECRET);
245-
userCredentialContents.put("refresh_token", REFRESH_TOKEN);
246-
String scopesAsString = Joiner.on(' ').join(SCOPES);
247-
userCredentialContents.put("scopes", scopesAsString);
248-
userCredentialContents.put("type", GoogleCredential.USER_FILE_TYPE);
249-
PrintWriter writer = new PrintWriter(userCredentialFile);
250-
String json = userCredentialContents.toPrettyString();
298+
PrintWriter writer = new PrintWriter(userFile);
251299
writer.println(json);
252300
writer.close();
253301

254-
// Point the default credential to the file
255-
testProvider.setEnv(DefaultCredentialProvider.CREDENTIAL_ENV_VAR,
256-
userCredentialFile.getAbsolutePath());
257-
258302
Credential credential = testProvider.getDefaultCredential(transport, JSON_FACTORY);
259303

260304
assertNotNull(credential);
@@ -263,8 +307,8 @@ public void testDefaultCredentialUser() throws IOException {
263307
assertTrue(credential.refreshToken());
264308
assertEquals(ACCESS_TOKEN, credential.getAccessToken());
265309
} finally {
266-
if (userCredentialFile.exists()) {
267-
userCredentialFile.delete();
310+
if (userFile.exists()) {
311+
userFile.delete();
268312
}
269313
}
270314
}
@@ -321,11 +365,17 @@ private static class TestDefaultCredentialProvider extends DefaultCredentialProv
321365

322366
private Map<String, Class<?>> types = new HashMap<String, Class<?>>();
323367
private Map<String, String> variables = new HashMap<String, String>();
368+
private Map<String, String> properties = new HashMap<String, String>();
369+
private Set<String> files = new HashSet<String>();
324370
private int forNameCallCount = 0;
325371

326372
TestDefaultCredentialProvider() {
327373
}
328374

375+
void addFile(String file) {
376+
files.add(file);
377+
}
378+
329379
void addType(String className, Class<?> type) {
330380
types.put(className, type);
331381
}
@@ -339,6 +389,21 @@ void setEnv(String name, String value) {
339389
variables.put(name, value);
340390
}
341391

392+
@Override
393+
String getProperty(String property, String def) {
394+
String value = properties.get(property);
395+
return value == null ? def : value;
396+
}
397+
398+
void setProperty(String property, String value) {
399+
properties.put(property, value);
400+
}
401+
402+
@Override
403+
boolean fileExists(File file) {
404+
return files.contains(file.getAbsolutePath());
405+
}
406+
342407
@Override
343408
Class<?> forName(String className) throws ClassNotFoundException {
344409
forNameCallCount++;

google-api-client/src/test/java/com/google/api/client/googleapis/auth/oauth2/GoogleCredentialTest.java

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import com.google.api.client.json.jackson2.JacksonFactory;
2121
import com.google.api.client.testing.http.MockHttpTransport;
2222
import com.google.api.client.testing.util.SecurityTestUtils;
23-
import com.google.api.client.util.Joiner;
2423

2524
import junit.framework.TestCase;
2625

@@ -311,15 +310,7 @@ public void testFromStreamUser() throws IOException {
311310
transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
312311

313312
// Create user stream.
314-
GenericJson userCredentialContents = new GenericJson();
315-
userCredentialContents.setFactory(JSON_FACTORY);
316-
userCredentialContents.put("client_id", CLIENT_ID);
317-
userCredentialContents.put("client_secret", CLIENT_SECRET);
318-
userCredentialContents.put("refresh_token", REFRESH_TOKEN);
319-
String scopesAsString = Joiner.on(' ').join(SCOPES);
320-
userCredentialContents.put("scopes", scopesAsString);
321-
userCredentialContents.put("type", GoogleCredential.USER_FILE_TYPE);
322-
String json = userCredentialContents.toPrettyString();
313+
String json = createUserJson(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN);
323314
InputStream userStream = new ByteArrayInputStream(json.getBytes());
324315

325316
GoogleCredential defaultCredential = GoogleCredential
@@ -343,14 +334,7 @@ public void testFromStreamUsertMissingClientIdThrows() throws IOException {
343334
transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
344335

345336
// Write out user file
346-
GenericJson userCredentialContents = new GenericJson();
347-
userCredentialContents.setFactory(JSON_FACTORY);
348-
userCredentialContents.put("client_secret", CLIENT_SECRET);
349-
userCredentialContents.put("refresh_token", REFRESH_TOKEN);
350-
String scopesAsString = Joiner.on(' ').join(SCOPES);
351-
userCredentialContents.put("scopes", scopesAsString);
352-
userCredentialContents.put("type", GoogleCredential.USER_FILE_TYPE);
353-
String json = userCredentialContents.toPrettyString();
337+
String json = createUserJson(null, CLIENT_SECRET, REFRESH_TOKEN);
354338
InputStream userStream = new ByteArrayInputStream(json.getBytes());
355339

356340
try {
@@ -372,14 +356,7 @@ public void testFromStreamUsertMissingClientSecretThrows() throws IOException {
372356
transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
373357

374358
// Write out user file
375-
GenericJson userCredentialContents = new GenericJson();
376-
userCredentialContents.setFactory(JSON_FACTORY);
377-
userCredentialContents.put("client_id", CLIENT_ID);
378-
userCredentialContents.put("refresh_token", REFRESH_TOKEN);
379-
String scopesAsString = Joiner.on(' ').join(SCOPES);
380-
userCredentialContents.put("scopes", scopesAsString);
381-
userCredentialContents.put("type", GoogleCredential.USER_FILE_TYPE);
382-
String json = userCredentialContents.toPrettyString();
359+
String json = createUserJson(CLIENT_ID, null, REFRESH_TOKEN);
383360
InputStream userStream = new ByteArrayInputStream(json.getBytes());
384361

385362
try {
@@ -401,13 +378,7 @@ public void testFromStreamUsertMissingRefreshTokenThrows() throws IOException {
401378
transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
402379

403380
// Write out user file
404-
GenericJson userCredentialContents = new GenericJson();
405-
userCredentialContents.setFactory(JSON_FACTORY);
406-
userCredentialContents.put("client_id", CLIENT_ID);
407-
String scopesAsString = Joiner.on(' ').join(SCOPES);
408-
userCredentialContents.put("scopes", scopesAsString);
409-
userCredentialContents.put("type", GoogleCredential.USER_FILE_TYPE);
410-
String json = userCredentialContents.toPrettyString();
381+
String json = createUserJson(CLIENT_ID, CLIENT_SECRET, null);
411382
InputStream userStream = new ByteArrayInputStream(json.getBytes());
412383

413384
try {
@@ -417,4 +388,22 @@ public void testFromStreamUsertMissingRefreshTokenThrows() throws IOException {
417388
assertTrue(expected.getMessage().contains("refresh_token"));
418389
}
419390
}
391+
392+
static String createUserJson(String clientId, String clientSecret, String refreshToken)
393+
throws IOException {
394+
GenericJson userCredentialContents = new GenericJson();
395+
userCredentialContents.setFactory(JSON_FACTORY);
396+
if (clientId != null) {
397+
userCredentialContents.put("client_id", clientId);
398+
}
399+
if (clientSecret != null) {
400+
userCredentialContents.put("client_secret", clientSecret);
401+
}
402+
if (refreshToken != null) {
403+
userCredentialContents.put("refresh_token", refreshToken);
404+
}
405+
userCredentialContents.put("type", GoogleCredential.USER_FILE_TYPE);
406+
String json = userCredentialContents.toPrettyString();
407+
return json;
408+
}
420409
}

0 commit comments

Comments
 (0)