Skip to content

Commit 11f4573

Browse files
committed
Add OAuth2AuthCredentials class and tests (#1030)
1 parent fceeb65 commit 11f4573

File tree

4 files changed

+225
-5
lines changed

4 files changed

+225
-5
lines changed

README.md

+11-4
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ First, ensure that the necessary Google Cloud APIs are enabled for your project.
107107
Next, choose a method for authenticating API requests from within your project:
108108

109109
1. When using `gcloud-java` libraries from within Compute/App Engine, no additional authentication steps are necessary.
110-
2. When using `gcloud-java` libraries elsewhere, there are two options:
110+
2. When using `gcloud-java` libraries elsewhere, there are three options:
111111
* [Generate a JSON service account key](https://cloud.google.com/storage/docs/authentication?hl=en#service_accounts). After downloading that key, you must do one of the following:
112112
* Define the environment variable GOOGLE_APPLICATION_CREDENTIALS to be the location of the key. For example:
113113
```bash
@@ -116,11 +116,18 @@ Next, choose a method for authenticating API requests from within your project:
116116
* Supply the JSON credentials file when building the service options. For example, this Storage object has the necessary permissions to interact with your Google Cloud Storage data:
117117
```java
118118
Storage storage = StorageOptions.builder()
119-
.authCredentials(AuthCredentials.createForJson(new FileInputStream("/path/to/my/key.json"))
119+
.authCredentials(AuthCredentials.createForJson(new FileInputStream("/path/to/my/key.json"))
120+
.build()
121+
.service();
122+
```
123+
* If running locally for development/testing, you can use Google Cloud SDK. Download the SDK if you haven't already, then login using the SDK (`gcloud auth login` in command line). Be sure to set your project ID as described above.
124+
* If you already have an OAuth2 access token, you can use it to authenticate (notice that in this case the access token will not be automatically refreshed):
125+
```java
126+
Storage storage = StorageOptions.builder()
127+
.authCredentials(AuthCredentials.createFor("your_access_token"))
120128
.build()
121129
.service();
122-
```
123-
* If running locally for development/testing, you can use use Google Cloud SDK. Download the SDK if you haven't already, then login using the SDK (`gcloud auth login` in command line). Be sure to set your project ID as described above.
130+
```
124131
125132
`gcloud-java` looks for credentials in the following order, stopping once it finds credentials:
126133

gcloud-java-core/src/main/java/com/google/cloud/AuthCredentials.java

+83
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,67 @@ public RestorableState<AuthCredentials> capture() {
341341
}
342342
}
343343

344+
/**
345+
* Represents OAuth2 credentials. These credentials can be created given an OAuth2 access token.
346+
* The access token will not be automatically refreshed.
347+
*/
348+
public static class OAuth2AuthCredentials extends AuthCredentials {
349+
350+
private final GoogleCredentials credentials;
351+
private final String accessToken;
352+
private final Date expirationTime;
353+
354+
private static class OAuth2AuthCredentialsState
355+
implements RestorableState<AuthCredentials>, Serializable {
356+
357+
private static final long serialVersionUID = -7760693952274496205L;
358+
359+
private final String accessToken;
360+
private final Date expirationTime;
361+
362+
private OAuth2AuthCredentialsState(String accessToken, Date expirationTime) {
363+
this.accessToken = accessToken;
364+
this.expirationTime = expirationTime;
365+
}
366+
367+
@Override
368+
public AuthCredentials restore() {
369+
return new OAuth2AuthCredentials(accessToken, expirationTime);
370+
}
371+
372+
@Override
373+
public int hashCode() {
374+
return Objects.hash(accessToken, expirationTime);
375+
}
376+
377+
@Override
378+
public boolean equals(Object obj) {
379+
if (!(obj instanceof OAuth2AuthCredentialsState)) {
380+
return false;
381+
}
382+
OAuth2AuthCredentialsState other = (OAuth2AuthCredentialsState) obj;
383+
return Objects.equals(accessToken, other.accessToken)
384+
&& Objects.equals(expirationTime, other.expirationTime);
385+
}
386+
}
387+
388+
OAuth2AuthCredentials(String accessToken, Date expirationTime) {
389+
this.accessToken = checkNotNull(accessToken);
390+
this.expirationTime = expirationTime;
391+
this.credentials = new GoogleCredentials(new AccessToken(accessToken, expirationTime));
392+
}
393+
394+
@Override
395+
public GoogleCredentials credentials() {
396+
return credentials;
397+
}
398+
399+
@Override
400+
public RestorableState<AuthCredentials> capture() {
401+
return new OAuth2AuthCredentialsState(accessToken, expirationTime);
402+
}
403+
}
404+
344405
/**
345406
* A placeholder for credentials to signify that requests sent to the server should not be
346407
* authenticated. This is typically useful when using the local service emulators, such as
@@ -428,6 +489,28 @@ public static ServiceAccountAuthCredentials createFor(String account, PrivateKey
428489
return new ServiceAccountAuthCredentials(account, privateKey);
429490
}
430491

492+
/**
493+
* Creates OAuth2 Credentials given the string representation of an access token. The access token
494+
* will not be automatically refreshed.
495+
*
496+
* @param accessToken string representation of an access token
497+
* @return the credentials instance
498+
*/
499+
public static OAuth2AuthCredentials createFor(String accessToken) {
500+
return createFor(accessToken, (Date) null);
501+
}
502+
503+
/**
504+
* Creates OAuth2 Credentials given the string representation of an access token and its
505+
* expiration time. The access token will not be automatically refreshed.
506+
*
507+
* @param accessToken string representation of an access token
508+
* @return the credentials instance
509+
*/
510+
public static OAuth2AuthCredentials createFor(String accessToken, Date expirationTime) {
511+
return new OAuth2AuthCredentials(accessToken, expirationTime);
512+
}
513+
431514
/**
432515
* Creates a placeholder denoting that no credentials should be used. This is typically useful
433516
* when using the local service emulators, such as {@code LocalDatastoreHelper} and
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud;
18+
19+
import static com.google.common.base.Charsets.UTF_8;
20+
import static org.junit.Assert.assertArrayEquals;
21+
import static org.junit.Assert.assertEquals;
22+
import static org.junit.Assert.assertNull;
23+
import static org.junit.Assert.assertSame;
24+
25+
import com.google.auth.oauth2.AccessToken;
26+
import com.google.auth.oauth2.ServiceAccountCredentials;
27+
import com.google.cloud.AuthCredentials.OAuth2AuthCredentials;
28+
import com.google.cloud.AuthCredentials.ServiceAccountAuthCredentials;
29+
import com.google.common.io.BaseEncoding;
30+
31+
import org.junit.BeforeClass;
32+
import org.junit.Test;
33+
34+
import java.io.ByteArrayInputStream;
35+
import java.io.IOException;
36+
import java.security.InvalidKeyException;
37+
import java.security.KeyFactory;
38+
import java.security.NoSuchAlgorithmException;
39+
import java.security.PrivateKey;
40+
import java.security.Signature;
41+
import java.security.SignatureException;
42+
import java.security.spec.InvalidKeySpecException;
43+
import java.security.spec.PKCS8EncodedKeySpec;
44+
import java.util.Date;
45+
46+
public class AuthCredentialsTest {
47+
48+
private static final String ACCESS_TOKEN = "accessToken";
49+
private static final Date EXPIRATION_DATE = new Date();
50+
private static final String SERVICE_ACCOUNT = "[email protected]";
51+
private static final String PRIVATE_KEY_STRING = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoG"
52+
+ "BAL2xolH1zrISQ8+GzOV29BNjjzq4/HIP8Psd1+cZb81vDklSF+95wB250MSE0BDc81pvIMwj5OmIfLg1NY6uB1xav"
53+
+ "OPpVdx1z664AGc/BEJ1zInXGXaQ6s+SxGenVq40Yws57gikQGMZjttpf1Qbz4DjkxsbRoeaRHn06n9pH1ejAgMBAAE"
54+
+ "CgYEAkWcm0AJF5LMhbWKbjkxm/LG06UNApkHX6vTOOOODkonM/qDBnhvKCj8Tan+PaU2j7679Cd19qxCm4SBQJET7e"
55+
+ "BhqLD9L2j9y0h2YUQnLbISaqUS1/EXcr2C1Lf9VCEn1y/GYuDYqs85rGoQ4ZYfM9ClROSq86fH+cbIIssqJqukCQQD"
56+
+ "18LjfJz/ichFeli5/l1jaFid2XoCH3T6TVuuysszVx68fh60gSIxEF/0X2xB+wuPxTP4IQ+t8tD/ktd232oWXAkEAx"
57+
+ "XPych2QBHePk9/lek4tOkKBgfnDzex7S/pI0G1vpB3VmzBbCsokn9lpOv7JV8071GDlW/7R6jlLfpQy3hN31QJAE10"
58+
+ "osSk99m5Uv8XDU3hvHnywDrnSFOBulNs7I47AYfSe7TSZhPkxUgsxejddTR27JLyTI8N1PxRSE4feNSOXcQJAMMKJR"
59+
+ "JT4U6IS2rmXubREhvaVdLtxFxEnAYQ1JwNfZm/XqBMw6GEy2iaeTetNXVlZRQEIoscyn1y2v/No/F5iYQJBAKBOGAS"
60+
+ "oQcBjGTOg/H/SfcE8QVNsKEpthRrs6CkpT80aZ/AV+ksfoIf2zw2M3mAHfrO+TBLdz4sicuFQvlN9SEc=";
61+
private static final String JSON_KEY = "{\n"
62+
+ " \"private_key_id\": \"somekeyid\",\n"
63+
+ " \"private_key\": \"-----BEGIN PRIVATE KEY-----\\n" + PRIVATE_KEY_STRING
64+
+ "\\n-----END PRIVATE KEY-----\\n\",\n"
65+
+ " \"client_email\": \"[email protected]\",\n"
66+
+ " \"client_id\": \"someclientid.apps.googleusercontent.com\",\n"
67+
+ " \"type\": \"service_account\"\n"
68+
+ "}";
69+
private static final AuthCredentials NO_AUTH_CREDENTIALS = AuthCredentials.noAuth();
70+
private static final OAuth2AuthCredentials OAUTH2_AUTH_CREDENTIALS =
71+
AuthCredentials.createFor(ACCESS_TOKEN, EXPIRATION_DATE);
72+
private static final byte[] BYTES_TO_SIGN = PRIVATE_KEY_STRING.getBytes(UTF_8);
73+
74+
private static PrivateKey privateKey;
75+
private static byte[] signedBytes;
76+
77+
@BeforeClass
78+
public static void beforeClass() throws NoSuchAlgorithmException, InvalidKeySpecException,
79+
InvalidKeyException, SignatureException {
80+
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
81+
privateKey = keyFactory.generatePrivate(
82+
new PKCS8EncodedKeySpec(BaseEncoding.base64().decode(PRIVATE_KEY_STRING)));
83+
Signature signature = Signature.getInstance("SHA256withRSA");
84+
signature.initSign(privateKey);
85+
signature.update(BYTES_TO_SIGN);
86+
signedBytes = signature.sign();
87+
}
88+
89+
@Test
90+
public void testNoAuthCredentials() {
91+
assertSame(NO_AUTH_CREDENTIALS, AuthCredentials.noAuth());
92+
assertNull(NO_AUTH_CREDENTIALS.credentials());
93+
}
94+
95+
@Test
96+
public void testOAuth2AuthCredentials() {
97+
AccessToken accessToken = OAUTH2_AUTH_CREDENTIALS.credentials().getAccessToken();
98+
assertEquals(ACCESS_TOKEN, accessToken.getTokenValue());
99+
assertEquals(EXPIRATION_DATE, accessToken.getExpirationTime());
100+
OAuth2AuthCredentials oAuth2AuthCredentials =
101+
AuthCredentials.createFor(ACCESS_TOKEN);
102+
accessToken = oAuth2AuthCredentials.credentials().getAccessToken();
103+
assertEquals(ACCESS_TOKEN, accessToken.getTokenValue());
104+
assertNull(accessToken.getExpirationTime());
105+
}
106+
107+
@Test
108+
public void testServiceAccountFromJson() throws IOException, SignatureException {
109+
ServiceAccountAuthCredentials serviceAccountAuthCredentials =
110+
AuthCredentials.createForJson(new ByteArrayInputStream(JSON_KEY.getBytes()));
111+
ServiceAccountCredentials credentials = serviceAccountAuthCredentials.credentials();
112+
assertEquals(SERVICE_ACCOUNT, serviceAccountAuthCredentials.account());
113+
assertEquals(SERVICE_ACCOUNT, credentials.getClientEmail());
114+
assertEquals(privateKey, credentials.getPrivateKey());
115+
assertArrayEquals(signedBytes, serviceAccountAuthCredentials.sign(BYTES_TO_SIGN));
116+
}
117+
118+
@Test
119+
public void testServiceAccountFromKey() throws IOException, SignatureException {
120+
ServiceAccountAuthCredentials serviceAccountAuthCredentials =
121+
AuthCredentials.createFor(SERVICE_ACCOUNT, privateKey);
122+
ServiceAccountCredentials credentials = serviceAccountAuthCredentials.credentials();
123+
assertEquals(SERVICE_ACCOUNT, serviceAccountAuthCredentials.account());
124+
assertEquals(SERVICE_ACCOUNT, credentials.getClientEmail());
125+
assertEquals(privateKey, credentials.getPrivateKey());
126+
assertArrayEquals(signedBytes, serviceAccountAuthCredentials.sign(BYTES_TO_SIGN));
127+
}
128+
}

gcloud-java-core/src/test/java/com/google/cloud/SerializationTest.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.io.ByteArrayInputStream;
2323
import java.io.IOException;
2424
import java.io.Serializable;
25+
import java.util.Date;
2526

2627
public class SerializationTest extends BaseSerializationTest {
2728

@@ -94,7 +95,8 @@ protected Serializable[] serializableObjects() {
9495
protected Restorable<?>[] restorableObjects() {
9596
try {
9697
return new Restorable<?>[]{AuthCredentials.createForAppEngine(), AuthCredentials.noAuth(),
97-
AuthCredentials.createForJson(new ByteArrayInputStream(JSON_KEY.getBytes()))};
98+
AuthCredentials.createForJson(new ByteArrayInputStream(JSON_KEY.getBytes())),
99+
AuthCredentials.createFor("accessToken", new Date())};
98100
} catch (IOException ex) {
99101
// never reached
100102
throw new RuntimeException(ex);

0 commit comments

Comments
 (0)