Skip to content

Commit

Permalink
Add OAuth2AuthCredentials class and tests (#1030)
Browse files Browse the repository at this point in the history
  • Loading branch information
mziccard committed May 28, 2016
1 parent fceeb65 commit 11f4573
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 5 deletions.
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ First, ensure that the necessary Google Cloud APIs are enabled for your project.
Next, choose a method for authenticating API requests from within your project:

1. When using `gcloud-java` libraries from within Compute/App Engine, no additional authentication steps are necessary.
2. When using `gcloud-java` libraries elsewhere, there are two options:
2. When using `gcloud-java` libraries elsewhere, there are three options:
* [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:
* Define the environment variable GOOGLE_APPLICATION_CREDENTIALS to be the location of the key. For example:
```bash
Expand All @@ -116,11 +116,18 @@ Next, choose a method for authenticating API requests from within your project:
* 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:
```java
Storage storage = StorageOptions.builder()
.authCredentials(AuthCredentials.createForJson(new FileInputStream("/path/to/my/key.json"))
.authCredentials(AuthCredentials.createForJson(new FileInputStream("/path/to/my/key.json"))
.build()
.service();
```
* 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.
* 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):
```java
Storage storage = StorageOptions.builder()
.authCredentials(AuthCredentials.createFor("your_access_token"))
.build()
.service();
```
* 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.
```
`gcloud-java` looks for credentials in the following order, stopping once it finds credentials:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,67 @@ public RestorableState<AuthCredentials> capture() {
}
}

/**
* Represents OAuth2 credentials. These credentials can be created given an OAuth2 access token.
* The access token will not be automatically refreshed.
*/
public static class OAuth2AuthCredentials extends AuthCredentials {

private final GoogleCredentials credentials;
private final String accessToken;
private final Date expirationTime;

private static class OAuth2AuthCredentialsState
implements RestorableState<AuthCredentials>, Serializable {

private static final long serialVersionUID = -7760693952274496205L;

private final String accessToken;
private final Date expirationTime;

private OAuth2AuthCredentialsState(String accessToken, Date expirationTime) {
this.accessToken = accessToken;
this.expirationTime = expirationTime;
}

@Override
public AuthCredentials restore() {
return new OAuth2AuthCredentials(accessToken, expirationTime);
}

@Override
public int hashCode() {
return Objects.hash(accessToken, expirationTime);
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof OAuth2AuthCredentialsState)) {
return false;
}
OAuth2AuthCredentialsState other = (OAuth2AuthCredentialsState) obj;
return Objects.equals(accessToken, other.accessToken)
&& Objects.equals(expirationTime, other.expirationTime);
}
}

OAuth2AuthCredentials(String accessToken, Date expirationTime) {
this.accessToken = checkNotNull(accessToken);
this.expirationTime = expirationTime;
this.credentials = new GoogleCredentials(new AccessToken(accessToken, expirationTime));
}

@Override
public GoogleCredentials credentials() {
return credentials;
}

@Override
public RestorableState<AuthCredentials> capture() {
return new OAuth2AuthCredentialsState(accessToken, expirationTime);
}
}

/**
* A placeholder for credentials to signify that requests sent to the server should not be
* authenticated. This is typically useful when using the local service emulators, such as
Expand Down Expand Up @@ -428,6 +489,28 @@ public static ServiceAccountAuthCredentials createFor(String account, PrivateKey
return new ServiceAccountAuthCredentials(account, privateKey);
}

/**
* Creates OAuth2 Credentials given the string representation of an access token. The access token
* will not be automatically refreshed.
*
* @param accessToken string representation of an access token
* @return the credentials instance
*/
public static OAuth2AuthCredentials createFor(String accessToken) {
return createFor(accessToken, (Date) null);
}

/**
* Creates OAuth2 Credentials given the string representation of an access token and its
* expiration time. The access token will not be automatically refreshed.
*
* @param accessToken string representation of an access token
* @return the credentials instance
*/
public static OAuth2AuthCredentials createFor(String accessToken, Date expirationTime) {
return new OAuth2AuthCredentials(accessToken, expirationTime);
}

/**
* Creates a placeholder denoting that no credentials should be used. This is typically useful
* when using the local service emulators, such as {@code LocalDatastoreHelper} and
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud;

import static com.google.common.base.Charsets.UTF_8;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;

import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.cloud.AuthCredentials.OAuth2AuthCredentials;
import com.google.cloud.AuthCredentials.ServiceAccountAuthCredentials;
import com.google.common.io.BaseEncoding;

import org.junit.BeforeClass;
import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Date;

public class AuthCredentialsTest {

private static final String ACCESS_TOKEN = "accessToken";
private static final Date EXPIRATION_DATE = new Date();
private static final String SERVICE_ACCOUNT = "someclientid@developer.gserviceaccount.com";
private static final String PRIVATE_KEY_STRING = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoG"
+ "BAL2xolH1zrISQ8+GzOV29BNjjzq4/HIP8Psd1+cZb81vDklSF+95wB250MSE0BDc81pvIMwj5OmIfLg1NY6uB1xav"
+ "OPpVdx1z664AGc/BEJ1zInXGXaQ6s+SxGenVq40Yws57gikQGMZjttpf1Qbz4DjkxsbRoeaRHn06n9pH1ejAgMBAAE"
+ "CgYEAkWcm0AJF5LMhbWKbjkxm/LG06UNApkHX6vTOOOODkonM/qDBnhvKCj8Tan+PaU2j7679Cd19qxCm4SBQJET7e"
+ "BhqLD9L2j9y0h2YUQnLbISaqUS1/EXcr2C1Lf9VCEn1y/GYuDYqs85rGoQ4ZYfM9ClROSq86fH+cbIIssqJqukCQQD"
+ "18LjfJz/ichFeli5/l1jaFid2XoCH3T6TVuuysszVx68fh60gSIxEF/0X2xB+wuPxTP4IQ+t8tD/ktd232oWXAkEAx"
+ "XPych2QBHePk9/lek4tOkKBgfnDzex7S/pI0G1vpB3VmzBbCsokn9lpOv7JV8071GDlW/7R6jlLfpQy3hN31QJAE10"
+ "osSk99m5Uv8XDU3hvHnywDrnSFOBulNs7I47AYfSe7TSZhPkxUgsxejddTR27JLyTI8N1PxRSE4feNSOXcQJAMMKJR"
+ "JT4U6IS2rmXubREhvaVdLtxFxEnAYQ1JwNfZm/XqBMw6GEy2iaeTetNXVlZRQEIoscyn1y2v/No/F5iYQJBAKBOGAS"
+ "oQcBjGTOg/H/SfcE8QVNsKEpthRrs6CkpT80aZ/AV+ksfoIf2zw2M3mAHfrO+TBLdz4sicuFQvlN9SEc=";
private static final String JSON_KEY = "{\n"
+ " \"private_key_id\": \"somekeyid\",\n"
+ " \"private_key\": \"-----BEGIN PRIVATE KEY-----\\n" + PRIVATE_KEY_STRING
+ "\\n-----END PRIVATE KEY-----\\n\",\n"
+ " \"client_email\": \"someclientid@developer.gserviceaccount.com\",\n"
+ " \"client_id\": \"someclientid.apps.googleusercontent.com\",\n"
+ " \"type\": \"service_account\"\n"
+ "}";
private static final AuthCredentials NO_AUTH_CREDENTIALS = AuthCredentials.noAuth();
private static final OAuth2AuthCredentials OAUTH2_AUTH_CREDENTIALS =
AuthCredentials.createFor(ACCESS_TOKEN, EXPIRATION_DATE);
private static final byte[] BYTES_TO_SIGN = PRIVATE_KEY_STRING.getBytes(UTF_8);

private static PrivateKey privateKey;
private static byte[] signedBytes;

@BeforeClass
public static void beforeClass() throws NoSuchAlgorithmException, InvalidKeySpecException,
InvalidKeyException, SignatureException {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
privateKey = keyFactory.generatePrivate(
new PKCS8EncodedKeySpec(BaseEncoding.base64().decode(PRIVATE_KEY_STRING)));
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(BYTES_TO_SIGN);
signedBytes = signature.sign();
}

@Test
public void testNoAuthCredentials() {
assertSame(NO_AUTH_CREDENTIALS, AuthCredentials.noAuth());
assertNull(NO_AUTH_CREDENTIALS.credentials());
}

@Test
public void testOAuth2AuthCredentials() {
AccessToken accessToken = OAUTH2_AUTH_CREDENTIALS.credentials().getAccessToken();
assertEquals(ACCESS_TOKEN, accessToken.getTokenValue());
assertEquals(EXPIRATION_DATE, accessToken.getExpirationTime());
OAuth2AuthCredentials oAuth2AuthCredentials =
AuthCredentials.createFor(ACCESS_TOKEN);
accessToken = oAuth2AuthCredentials.credentials().getAccessToken();
assertEquals(ACCESS_TOKEN, accessToken.getTokenValue());
assertNull(accessToken.getExpirationTime());
}

@Test
public void testServiceAccountFromJson() throws IOException, SignatureException {
ServiceAccountAuthCredentials serviceAccountAuthCredentials =
AuthCredentials.createForJson(new ByteArrayInputStream(JSON_KEY.getBytes()));
ServiceAccountCredentials credentials = serviceAccountAuthCredentials.credentials();
assertEquals(SERVICE_ACCOUNT, serviceAccountAuthCredentials.account());
assertEquals(SERVICE_ACCOUNT, credentials.getClientEmail());
assertEquals(privateKey, credentials.getPrivateKey());
assertArrayEquals(signedBytes, serviceAccountAuthCredentials.sign(BYTES_TO_SIGN));
}

@Test
public void testServiceAccountFromKey() throws IOException, SignatureException {
ServiceAccountAuthCredentials serviceAccountAuthCredentials =
AuthCredentials.createFor(SERVICE_ACCOUNT, privateKey);
ServiceAccountCredentials credentials = serviceAccountAuthCredentials.credentials();
assertEquals(SERVICE_ACCOUNT, serviceAccountAuthCredentials.account());
assertEquals(SERVICE_ACCOUNT, credentials.getClientEmail());
assertEquals(privateKey, credentials.getPrivateKey());
assertArrayEquals(signedBytes, serviceAccountAuthCredentials.sign(BYTES_TO_SIGN));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.Date;

public class SerializationTest extends BaseSerializationTest {

Expand Down Expand Up @@ -94,7 +95,8 @@ protected Serializable[] serializableObjects() {
protected Restorable<?>[] restorableObjects() {
try {
return new Restorable<?>[]{AuthCredentials.createForAppEngine(), AuthCredentials.noAuth(),
AuthCredentials.createForJson(new ByteArrayInputStream(JSON_KEY.getBytes()))};
AuthCredentials.createForJson(new ByteArrayInputStream(JSON_KEY.getBytes())),
AuthCredentials.createFor("accessToken", new Date())};
} catch (IOException ex) {
// never reached
throw new RuntimeException(ex);
Expand Down

0 comments on commit 11f4573

Please sign in to comment.