Promote the OAuth v2 API

This commit is contained in:
Ignasi Barrera 2015-04-15 10:56:55 +02:00
commit 1c8b7764f6
22 changed files with 1418 additions and 0 deletions

16
apis/oauth/README Normal file
View File

@ -0,0 +1,16 @@
In order to use oauth applications must specify the following properties:
Mandatory:
<myprovider>.identity - the oauth identity (e.g., service account email in Google Api's)
<myprovider>.credential - the private key used to sign requests, in pem format
oauth.endpoint - the endpoint to use for authentication (e.g., "http://accounts.google.com/o/oauth2/token" in Google Api's)
oauth.audience - the "audience" of the token request (e.g., "http://accounts.google.com/o/oauth2/token" in Google Api's)
Running the live test:
mvn clean install -Plive\
-Dtest.oauth.identity=<accout email>\
-Dtest.oauth.credential=<accout pk in pem format>\
-Dtest.oauth.endpoint=https://accounts.google.com/o/oauth2/token\
-Dtest.jclouds.oauth.audience=https://accounts.google.com/o/oauth2/token\
-Dtest.jclouds.oauth.scope=https://www.googleapis.com/auth/prediction

135
apis/oauth/pom.xml Normal file
View File

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.jclouds.labs</groupId>
<artifactId>jclouds-labs-google</artifactId>
<version>2.0.0-SNAPSHOT</version>
</parent>
<!-- TODO: when out of labs, switch to org.jclouds.api -->
<groupId>org.apache.jclouds.labs</groupId>
<artifactId>oauth</artifactId>
<name>jclouds OAuth core</name>
<description>jclouds components to access OAuth</description>
<properties>
<test.oauth.identity>FIX_ME</test.oauth.identity>
<test.oauth.credential>FIX_ME</test.oauth.credential>
<test.oauth.endpoint>FIX_ME</test.oauth.endpoint>
<test.jclouds.oauth.audience>FIX_ME</test.jclouds.oauth.audience>
<test.jclouds.oauth.scope>FIX_ME</test.jclouds.oauth.scope>
<test.oauth.api-version>2</test.oauth.api-version>
<test.oauth.build-version />
</properties>
<dependencies>
<dependency>
<groupId>org.apache.jclouds</groupId>
<artifactId>jclouds-core</artifactId>
<version>${jclouds.version}</version>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.jclouds</groupId>
<artifactId>jclouds-core</artifactId>
<version>${jclouds.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.jclouds.driver</groupId>
<artifactId>jclouds-slf4j</artifactId>
<version>${jclouds.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>live</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>default-test</id>
<configuration>
<skipTests>true</skipTests>
</configuration>
</execution>
<execution>
<id>integration</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<systemPropertyVariables>
<test.oauth.identity>${test.oauth.identity}</test.oauth.identity>
<test.oauth.credential>${test.oauth.credential}</test.oauth.credential>
<test.oauth.endpoint>${test.oauth.endpoint}</test.oauth.endpoint>
<test.oauth.api-version>${test.oauth.api-version}</test.oauth.api-version>
<test.oauth.build-version>${test.oauth.build-version}</test.oauth.build-version>
<test.jclouds.oauth.jws-alg>${test.jclouds.oauth.jws-alg}</test.jclouds.oauth.jws-alg>
<test.jclouds.oauth.audience>${test.jclouds.oauth.audience}</test.jclouds.oauth.audience>
<test.jclouds.oauth.scope>${test.jclouds.oauth.scope}</test.jclouds.oauth.scope>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import java.io.Closeable;
import javax.inject.Named;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import org.jclouds.oauth.v2.functions.ClaimsToAssertion;
import org.jclouds.oauth.v2.config.Authorization;
import org.jclouds.oauth.v2.domain.Claims;
import org.jclouds.oauth.v2.domain.Token;
import org.jclouds.rest.annotations.Endpoint;
import org.jclouds.rest.annotations.FormParams;
import org.jclouds.rest.annotations.ParamParser;
/**
* Binds to an OAuth2 <a href="http://tools.ietf.org/html/rfc6749#section-3.1">authorization endpoint</a>.
*/
@Endpoint(Authorization.class)
public interface AuthorizationApi extends Closeable {
@Named("oauth2:authorize")
@POST
@FormParams(keys = "grant_type", values = "urn:ietf:params:oauth:grant-type:jwt-bearer")
@Consumes(APPLICATION_JSON)
Token authorize(@FormParam("assertion") @ParamParser(ClaimsToAssertion.class) Claims claims);
}

View File

@ -0,0 +1,30 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Qualifier
public @interface Authorization {
}

View File

@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2.config;
import static com.google.common.base.CaseFormat.LOWER_CAMEL;
import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
import static com.google.common.base.Preconditions.checkNotNull;
/** Defines the contents of the credential field in {@link org.jclouds.ContextBuilder#credentials(String, String)}. */
public enum CredentialType {
BEARER_TOKEN_CREDENTIALS,
/** Contents are a PEM-encoded P12 Private Key. */
P12_PRIVATE_KEY_CREDENTIALS;
@Override public String toString() {
return UPPER_UNDERSCORE.to(LOWER_CAMEL, name());
}
public static CredentialType fromValue(String credentialType) {
return valueOf(LOWER_CAMEL.to(UPPER_UNDERSCORE, checkNotNull(credentialType, "credentialType")));
}
}

View File

@ -0,0 +1,83 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2.config;
import static org.jclouds.oauth.v2.config.CredentialType.P12_PRIVATE_KEY_CREDENTIALS;
import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE;
import static org.jclouds.rest.config.BinderUtils.bindHttpApi;
import java.net.URI;
import java.security.PrivateKey;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.oauth.v2.AuthorizationApi;
import org.jclouds.oauth.v2.filters.BearerTokenFromCredentials;
import org.jclouds.oauth.v2.filters.JWTBearerTokenFlow;
import org.jclouds.oauth.v2.filters.OAuthFilter;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
public final class OAuthModule extends AbstractModule {
@Override protected void configure() {
bindHttpApi(binder(), AuthorizationApi.class);
bind(CredentialType.class).toProvider(CredentialTypeFromPropertyOrDefault.class);
bind(new TypeLiteral<Supplier<PrivateKey>>() {}).annotatedWith(Authorization.class).to(PrivateKeySupplier.class);
}
@Provides
@Authorization
protected Supplier<URI> oauthEndpoint(@javax.inject.Named("oauth.endpoint") String endpoint) {
return Suppliers.ofInstance(URI.create(endpoint));
}
@Singleton
public static class CredentialTypeFromPropertyOrDefault implements Provider<CredentialType> {
@Inject(optional = true)
@Named(CREDENTIAL_TYPE)
String credentialType = P12_PRIVATE_KEY_CREDENTIALS.toString();
@Override
public CredentialType get() {
return CredentialType.fromValue(credentialType);
}
}
@Provides
@Singleton
protected OAuthFilter authenticationFilterForCredentialType(CredentialType credentialType,
JWTBearerTokenFlow serviceAccountAuth,
BearerTokenFromCredentials bearerTokenAuth) {
switch (credentialType) {
case P12_PRIVATE_KEY_CREDENTIALS:
return serviceAccountAuth;
case BEARER_TOKEN_CREDENTIALS:
return bearerTokenAuth;
default:
throw new IllegalArgumentException("Unsupported credential type: " + credentialType);
}
}
}

View File

@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2.config;
public final class OAuthProperties {
/** The JSON Web Signature alg, must be {@code RS256} or {@code none}. */
public static final String JWS_ALG = "jclouds.oauth.jws-alg";
/**
* The oauth audience, who this token is intended for. For instance in JWT and for
* google API's this property maps to: {"aud","https://accounts.google.com/o/oauth2/token"}
*
* @see <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30">doc</a>
*/
public static final String AUDIENCE = "jclouds.oauth.audience";
/**
* Specify if credentials are id + private key or if you are reusing an oauth2 token.
*
* @see org.jclouds.oauth.v2.config.CredentialType
*/
public static final String CREDENTIAL_TYPE = "jclouds.oauth.credential-type";
private OAuthProperties() {
}
}

View File

@ -0,0 +1,72 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2.config;
import java.util.List;
import org.jclouds.http.HttpRequest;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
/**
* Implementations are api-specific, typically routing GET or HEAD requests to a read-only role, and others to a
* read-write one.
*/
public interface OAuthScopes {
/** Returns a list of scopes needed to perform the request. */
List<String> forRequest(HttpRequest input);
@AutoValue public abstract static class SingleScope implements OAuthScopes {
abstract List<String> scopes();
public static SingleScope create(String scope) {
return new AutoValue_OAuthScopes_SingleScope(ImmutableList.of(scope));
}
@Override public List<String> forRequest(HttpRequest input) {
return scopes();
}
SingleScope() {
}
}
@AutoValue public abstract static class ReadOrWriteScopes implements OAuthScopes {
abstract List<String> readScopes();
abstract List<String> writeScopes();
public static ReadOrWriteScopes create(String readScope, String writeScope) {
return new AutoValue_OAuthScopes_ReadOrWriteScopes( //
ImmutableList.of(readScope), //
ImmutableList.of(writeScope) //
);
}
@Override public List<String> forRequest(HttpRequest input) {
if (input.getMethod().equals("GET") || input.getMethod().equals("HEAD")) {
return readScopes();
}
return writeScopes();
}
ReadOrWriteScopes() {
}
}
}

View File

@ -0,0 +1,100 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2.config;
import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.propagate;
import static org.jclouds.crypto.Pems.privateKeySpec;
import static org.jclouds.util.Throwables2.getFirstThrowableOfType;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.jclouds.domain.Credentials;
import org.jclouds.location.Provider;
import org.jclouds.rest.AuthorizationException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Supplier;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.io.ByteSource;
import com.google.common.util.concurrent.UncheckedExecutionException;
/**
* Loads {@link PrivateKey} from a pem private key using and RSA KeyFactory. The pem pk algorithm must match the
* KeyFactory algorithm.
*/
@Singleton // due to cache
final class PrivateKeySupplier implements Supplier<PrivateKey> {
private final Supplier<Credentials> creds;
private final LoadingCache<Credentials, PrivateKey> keyCache;
@Inject PrivateKeySupplier(@Provider Supplier<Credentials> creds, PrivateKeyForCredentials loader) {
this.creds = creds;
// throw out the private key related to old credentials
this.keyCache = CacheBuilder.newBuilder().maximumSize(2).build(checkNotNull(loader, "loader"));
}
/**
* it is relatively expensive to extract a private key from a PEM. cache the relationship between current
* credentials
* so that the private key is only recalculated once.
*/
@VisibleForTesting
static final class PrivateKeyForCredentials extends CacheLoader<Credentials, PrivateKey> {
@Override public PrivateKey load(Credentials in) {
try {
String privateKeyInPemFormat = checkNotNull(in.credential, "credential in PEM format");
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(privateKeySpec(ByteSource.wrap(privateKeyInPemFormat.getBytes(UTF_8))));
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (IOException e) {
throw propagate(e);
} catch (InvalidKeySpecException e) {
throw new AuthorizationException("security exception loading credentials. " + e.getMessage(), e);
// catch IAE that is thrown when parsing the pk fails
} catch (IllegalArgumentException e) {
throw new AuthorizationException("cannot parse pk. " + e.getMessage(), e);
}
}
}
@Override public PrivateKey get() {
try {
// loader always throws UncheckedExecutionException so no point in using get()
return keyCache.getUnchecked(checkNotNull(creds.get(), "credential supplier returned null"));
} catch (UncheckedExecutionException e) {
AuthorizationException authorizationException = getFirstThrowableOfType(e, AuthorizationException.class);
if (authorizationException != null) {
throw authorizationException;
}
throw e;
}
}
}

View File

@ -0,0 +1,55 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2.domain;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
/**
* Claims corresponding to a {@linkplain Token JWT Token}.
*
* @see <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4">registered list</a>
*/
@AutoValue
public abstract class Claims {
/** The issuer of this token. In google, the service account email. */
public abstract String iss();
/** A comma-separated list of scopes needed to perform the request. */
public abstract String scope();
/**
* The oauth audience, who this token is intended for. For instance in JWT and for
* google API's, this maps to: {@code https://accounts.google.com/o/oauth2/token}
*/
public abstract String aud();
/** The expiration time, in seconds since {@link #iat()}. */
public abstract long exp();
/** The time at which the JWT was issued, in seconds since the epoch. */
public abstract long iat();
@SerializedNames({ "iss", "scope", "aud", "exp", "iat" })
public static Claims create(String iss, String scope, String aud, long exp, long iat) {
return new AutoValue_Claims(iss, scope, aud, exp, iat);
}
Claims() {
}
}

View File

@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2.domain;
import org.jclouds.json.SerializedNames;
import com.google.auto.value.AutoValue;
/**
* The oauth token, obtained upon a successful token request and ready to embed in requests.
*/
@AutoValue
public abstract class Token {
/** The access token obtained from the OAuth server. */
public abstract String accessToken();
/** The type of the token, e.g., {@code Bearer}. */
public abstract String tokenType();
/** In how many seconds this token expires. */
public abstract long expiresIn();
@SerializedNames({"access_token", "token_type", "expires_in"})
public static Token create(String accessToken, String tokenType, long expiresIn) {
return new AutoValue_Token(accessToken, tokenType, expiresIn);
}
}

View File

@ -0,0 +1,44 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2.filters;
import static java.lang.String.format;
import javax.inject.Inject;
import org.jclouds.domain.Credentials;
import org.jclouds.http.HttpException;
import org.jclouds.http.HttpRequest;
import org.jclouds.location.Provider;
import com.google.common.base.Supplier;
/**
* When the user supplies {@link org.jclouds.oauth.v2.config.CredentialType#BEARER_TOKEN_CREDENTIALS}, the credential
* is a literal bearer token. This filter applies that to the request.
*/
public final class BearerTokenFromCredentials implements OAuthFilter {
private final Supplier<Credentials> creds;
@Inject BearerTokenFromCredentials(@Provider Supplier<Credentials> creds) {
this.creds = creds;
}
@Override public HttpRequest filter(HttpRequest request) throws HttpException {
return request.toBuilder().addHeader("Authorization", format("%s %s", "Bearer", creds.get().credential)).build();
}
}

View File

@ -0,0 +1,113 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2.filters;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
import javax.inject.Inject;
import javax.inject.Named;
import org.jclouds.domain.Credentials;
import org.jclouds.http.HttpException;
import org.jclouds.http.HttpRequest;
import org.jclouds.location.Provider;
import org.jclouds.oauth.v2.AuthorizationApi;
import org.jclouds.oauth.v2.config.OAuthScopes;
import org.jclouds.oauth.v2.domain.Claims;
import org.jclouds.oauth.v2.domain.Token;
import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
/**
* Authorizes new Bearer Tokens at runtime by authorizing claims needed for the http request.
*
* <h3>Cache</h3>
* This maintains a time-based Bearer Token cache. By default expires after 59 minutes
* (the maximum time a token is valid is 60 minutes).
* This cache and expiry period is system-wide and does not attend to per-instance expiry time
* (e.g. "expires_in" from Google Compute -- which is set to the standard 3600 seconds).
*/
public class JWTBearerTokenFlow implements OAuthFilter {
private static final Joiner ON_COMMA = Joiner.on(",");
private final String audience;
private final Supplier<Credentials> credentialsSupplier;
private final OAuthScopes scopes;
private final long tokenDuration;
private final LoadingCache<Claims, Token> tokenCache;
public static class TestJWTBearerTokenFlow extends JWTBearerTokenFlow {
@Inject TestJWTBearerTokenFlow(AuthorizeToken loader, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration,
@Named(AUDIENCE) String audience, @Provider Supplier<Credentials> credentialsSupplier, OAuthScopes scopes) {
super(loader, tokenDuration, audience, credentialsSupplier, scopes);
}
/** Constant time for testing. */
long currentTimeSeconds() {
return 0;
}
}
@Inject JWTBearerTokenFlow(AuthorizeToken loader, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration,
@Named(AUDIENCE) String audience, @Provider Supplier<Credentials> credentialsSupplier, OAuthScopes scopes) {
this.audience = audience;
this.credentialsSupplier = credentialsSupplier;
this.scopes = scopes;
this.tokenDuration = tokenDuration;
// since the session interval is also the token expiration time requested to the server make the token expire a
// bit before the deadline to make sure there aren't session expiration exceptions
long cacheExpirationSeconds = tokenDuration > 30 ? tokenDuration - 30 : tokenDuration;
this.tokenCache = CacheBuilder.newBuilder().expireAfterWrite(tokenDuration, SECONDS).build(loader);
}
static final class AuthorizeToken extends CacheLoader<Claims, Token> {
private final AuthorizationApi api;
@Inject AuthorizeToken(AuthorizationApi api) {
this.api = api;
}
@Override public Token load(Claims key) throws Exception {
return api.authorize(key);
}
}
@Override public HttpRequest filter(HttpRequest request) throws HttpException {
long now = currentTimeSeconds();
Claims claims = Claims.create( //
credentialsSupplier.get().identity, // iss
ON_COMMA.join(scopes.forRequest(request)), // scope
audience, // aud
now + tokenDuration, // exp
now // iat
);
Token token = tokenCache.getUnchecked(claims);
String authorization = String.format("%s %s", token.tokenType(), token.accessToken());
return request.toBuilder().addHeader("Authorization", authorization).build();
}
long currentTimeSeconds() {
return System.currentTimeMillis() / 1000;
}
}

View File

@ -0,0 +1,23 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2.filters;
import org.jclouds.http.HttpRequestFilter;
/** Indicates use of auth mechanism according to {@link org.jclouds.oauth.v2.config.OAuthProperties#CREDENTIAL_TYPE). */
public interface OAuthFilter extends HttpRequestFilter {
}

View File

@ -0,0 +1,87 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2.functions;
import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Joiner.on;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.io.BaseEncoding.base64Url;
import static org.jclouds.oauth.v2.config.OAuthProperties.JWS_ALG;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import org.jclouds.json.Json;
import org.jclouds.oauth.v2.config.Authorization;
import org.jclouds.rest.AuthorizationException;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
public final class ClaimsToAssertion implements Function<Object, String> {
private static final List<String> SUPPORTED_ALGS = ImmutableList.of("RS256", "none");
private final Supplier<PrivateKey> privateKey;
private final Json json;
private final String alg;
@Inject ClaimsToAssertion(@Named(JWS_ALG) String alg, @Authorization Supplier<PrivateKey> privateKey, Json json) {
this.alg = alg;
checkArgument(SUPPORTED_ALGS.contains(alg), "%s %s not in supported list", JWS_ALG, alg, SUPPORTED_ALGS);
this.privateKey = privateKey;
this.json = json;
}
@Override public String apply(Object input) {
String encodedHeader = String.format("{\"alg\":\"%s\",\"typ\":\"JWT\"}", alg);
String encodedClaimSet = json.toJson(input);
encodedHeader = base64Url().omitPadding().encode(encodedHeader.getBytes(UTF_8));
encodedClaimSet = base64Url().omitPadding().encode(encodedClaimSet.getBytes(UTF_8));
byte[] signature = alg.equals("none")
? null
: sha256(privateKey.get(), on(".").join(encodedHeader, encodedClaimSet).getBytes(UTF_8));
String encodedSignature = signature != null ? base64Url().omitPadding().encode(signature) : "";
// the final assertion in base 64 encoded {header}.{claimSet}.{signature} format
return on(".").join(encodedHeader, encodedClaimSet, encodedSignature);
}
static byte[] sha256(PrivateKey privateKey, byte[] input) {
try {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(input);
return signature.sign();
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (SignatureException e) {
throw new AuthorizationException(e);
} catch (InvalidKeyException e) {
throw new AuthorizationException(e);
}
}
}

View File

@ -0,0 +1,94 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.oauth.v2.OAuthTestUtils.setCredential;
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
import static org.jclouds.oauth.v2.config.OAuthProperties.JWS_ALG;
import static org.jclouds.oauth.v2.config.OAuthScopes.SingleScope;
import static org.jclouds.providers.AnonymousProviderMetadata.forApiOnEndpoint;
import static org.testng.Assert.assertNotNull;
import java.util.Properties;
import org.jclouds.apis.BaseApiLiveTest;
import org.jclouds.oauth.v2.config.OAuthModule;
import org.jclouds.oauth.v2.config.OAuthScopes;
import org.jclouds.oauth.v2.domain.Claims;
import org.jclouds.oauth.v2.domain.Token;
import org.jclouds.providers.ProviderMetadata;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import com.google.inject.Binder;
import com.google.inject.Module;
import com.google.inject.name.Names;
@Test(groups = "live", singleThreaded = true)
public class AuthorizationApiLiveTest extends BaseApiLiveTest<AuthorizationApi> {
private final String jwsAlg = "RS256";
private String scope;
private String audience;
public AuthorizationApiLiveTest() {
provider = "oauth";
}
public void authenticateJWTToken() throws Exception {
long now = System.currentTimeMillis() / 1000;
Claims claims = Claims.create(
identity, // iss
scope, // scope
audience, // aud
now + 3600, // exp
now // iat
);
Token token = api.authorize(claims);
assertNotNull(token, "no token when authorizing " + claims);
}
/** OAuth isn't registered as a provider intentionally, so we fake one. */
@Override protected ProviderMetadata createProviderMetadata() {
return forApiOnEndpoint(AuthorizationApi.class, endpoint).toBuilder().id("oauth").build();
}
@Override protected Properties setupProperties() {
Properties props = super.setupProperties();
props.setProperty(JWS_ALG, jwsAlg);
credential = setCredential(props, "oauth.credential");
audience = checkNotNull(setIfTestSystemPropertyPresent(props, AUDIENCE), "test.jclouds.oauth.audience");
scope = checkNotNull(setIfTestSystemPropertyPresent(props, "jclouds.oauth.scope"), "test.jclouds.oauth.scope");
return props;
}
@Override protected Iterable<Module> setupModules() {
return ImmutableList.<Module>builder() //
.add(new OAuthModule()) //
.add(new Module() {
@Override public void configure(Binder binder) {
// ContextBuilder erases oauth.endpoint, as that's the same name as the provider key.
binder.bindConstant().annotatedWith(Names.named("oauth.endpoint")).to(endpoint);
binder.bind(OAuthScopes.class).toInstance(SingleScope.create(scope));
}
}).addAll(super.setupModules()).build();
}
}

View File

@ -0,0 +1,125 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2;
import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.io.BaseEncoding.base64Url;
import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.jclouds.Constants.PROPERTY_MAX_RETRIES;
import static org.jclouds.oauth.v2.config.CredentialType.P12_PRIVATE_KEY_CREDENTIALS;
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE;
import static org.jclouds.oauth.v2.config.OAuthProperties.JWS_ALG;
import static org.jclouds.util.Strings2.toStringAndClose;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.net.URL;
import java.util.Properties;
import org.jclouds.ContextBuilder;
import org.jclouds.concurrent.config.ExecutorServiceModule;
import org.jclouds.oauth.v2.config.OAuthModule;
import org.jclouds.oauth.v2.config.OAuthScopes;
import org.jclouds.oauth.v2.config.OAuthScopes.SingleScope;
import org.jclouds.oauth.v2.domain.Claims;
import org.jclouds.oauth.v2.domain.Token;
import org.jclouds.rest.AnonymousHttpApiMetadata;
import org.testng.annotations.Test;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.BaseEncoding;
import com.google.inject.Binder;
import com.google.inject.Module;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import com.squareup.okhttp.mockwebserver.RecordedRequest;
@Test(groups = "unit", testName = "OAuthApiMockTest")
public class AuthorizationApiMockTest {
private static final String SCOPE = "https://www.googleapis.com/auth/prediction";
private static final String header = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";
private static final String claims = "{\"iss\":\"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer" +
".gserviceaccount.com\",\"scope\":\"" + SCOPE + "\",\"aud\":\"https://accounts.google" +
".com/o/oauth2/token\",\"exp\":1328573381,\"iat\":1328569781}";
private static final Token TOKEN = Token.create("1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M", "Bearer", 3600);
private static final Claims CLAIMS = Claims.create(
"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com", // iss
SCOPE, // scope
"https://accounts.google.com/o/oauth2/token", // aud
1328573381, // exp
1328569781 // iat
);
public void testGenerateJWTRequest() throws Exception {
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setBody("{\n" +
" \"access_token\" : \"1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M\",\n" +
" \"token_type\" : \"Bearer\",\n" +
" \"expires_in\" : 3600\n" +
"}"));
server.play();
AuthorizationApi api = api(server.getUrl("/"));
assertEquals(api.authorize(CLAIMS), TOKEN);
RecordedRequest request = server.takeRequest();
assertEquals(request.getMethod(), "POST");
assertEquals(request.getHeader("Accept"), APPLICATION_JSON);
assertEquals(request.getHeader("Content-Type"), "application/x-www-form-urlencoded");
assertEquals(new String(request.getBody(), UTF_8), //
"grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&" +
// Base64 Encoded Header
"assertion=" +
Joiner.on('.').join(encoding.encode(header.getBytes(UTF_8)), encoding.encode(claims.getBytes(UTF_8)),
// Base64 encoded {header}.{claims} signature (using SHA256)
"W2Lesr_98AzVYiMbzxFqmwcOjpIWlwqkC6pNn1fXND9oSDNNnFhy-AAR6DKH-x9ZmxbY80" +
"R5fH-OCeWumXlVgceKN8Z2SmgQsu8ElTpypQA54j_5j8vUImJ5hsOUYPeyF1U2BUzZ3L5g" +
"03PXBA0YWwRU9E1ChH28dQBYuGiUmYw"));
}
private final BaseEncoding encoding = base64Url().omitPadding();
private AuthorizationApi api(URL url) throws IOException {
Properties overrides = new Properties();
overrides.setProperty("oauth.endpoint", url.toString());
overrides.setProperty(JWS_ALG, "RS256");
overrides.setProperty(CREDENTIAL_TYPE, P12_PRIVATE_KEY_CREDENTIALS.toString());
overrides.setProperty(AUDIENCE, "https://accounts.google.com/o/oauth2/token");
overrides.setProperty(PROPERTY_MAX_RETRIES, "1");
return ContextBuilder.newBuilder(AnonymousHttpApiMetadata.forApi(AuthorizationApi.class))
.credentials("foo", toStringAndClose(OAuthTestUtils.class.getResourceAsStream("/testpk.pem")))
.endpoint(url.toString())
.overrides(overrides)
.modules(ImmutableSet.of(new ExecutorServiceModule(sameThreadExecutor()), new OAuthModule(), new Module() {
@Override public void configure(Binder binder) {
binder.bind(OAuthScopes.class).toInstance(SingleScope.create(SCOPE));
}
}))
.buildApi(AuthorizationApi.class);
}
}

View File

@ -0,0 +1,89 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.propagate;
import static org.jclouds.oauth.v2.config.CredentialType.BEARER_TOKEN_CREDENTIALS;
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE;
import static org.jclouds.util.Strings2.toStringAndClose;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
public class OAuthTestUtils {
public static Properties defaultProperties(Properties properties) {
try {
properties = properties == null ? new Properties() : properties;
properties.put("oauth.identity", "foo");
properties.put("oauth.credential", toStringAndClose(OAuthTestUtils.class.getResourceAsStream("/testpk.pem")));
properties.put("oauth.endpoint", "http://localhost:5000/o/oauth2/token");
properties.put(AUDIENCE, "https://accounts.google.com/o/oauth2/token");
return properties;
} catch (IOException e) {
throw propagate(e);
}
}
public static Properties bearerTokenAuthProperties(Properties properties) {
properties = properties == null ? new Properties() : properties;
properties.put("oauth.identity", "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com");
properties.put("oauth.credential", "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M");
properties.put("oauth.endpoint", "http://localhost:5000/o/oauth2/token");
properties.put(AUDIENCE, "https://accounts.google.com/o/oauth2/token");
properties.put(CREDENTIAL_TYPE, BEARER_TOKEN_CREDENTIALS.toString());
return properties;
}
// TODO: move to jclouds-core
public static String setCredential(Properties overrides, String key) {
String val = null;
String credentialFromFile = null;
String testKey = "test." + key;
if (System.getProperties().containsKey(testKey)) {
val = System.getProperty(testKey);
}
checkNotNull(val, String.format("the property %s must be set (pem private key file path or private key as a string)", testKey));
if (val.startsWith("-----BEGIN")) {
return val;
}
try {
credentialFromFile = Files.toString(new File(val), Charsets.UTF_8);
} catch (IOException e) {
throw propagate(e);
}
overrides.setProperty(key, credentialFromFile);
return credentialFromFile;
}
public static String getMandatoryProperty(Properties properties, String key) {
checkNotNull(properties);
checkNotNull(key);
String value = properties.getProperty(key);
return checkNotNull(value, String.format("mandatory property %s or test.%s was not present", key, key));
}
}

View File

@ -0,0 +1,67 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2.config;
import static com.google.common.base.Suppliers.ofInstance;
import static org.testng.Assert.assertNotNull;
import java.io.File;
import java.security.PrivateKey;
import java.util.Properties;
import org.jclouds.domain.Credentials;
import org.jclouds.oauth.v2.OAuthTestUtils;
import org.jclouds.oauth.v2.config.PrivateKeySupplier.PrivateKeyForCredentials;
import org.jclouds.rest.AuthorizationException;
import org.testng.annotations.Test;
import com.google.common.base.Charsets;
import com.google.common.base.Suppliers;
import com.google.common.io.Files;
@Test(groups = "unit")
public class PrivateKeySupplierTest {
/** Test loading the credentials by extracting a pk from a PKCS12 keystore. */
public void testLoadPKString() throws Exception {
assertNotNull(loadPrivateKey());
}
@Test(expectedExceptions = AuthorizationException.class)
public void testAuthorizationExceptionIsThrownOnBadKeys() {
PrivateKeySupplier supplier = new PrivateKeySupplier(
Suppliers.ofInstance(new Credentials("MOMMA", "FileNotFoundCredential")),
new PrivateKeyForCredentials());
supplier.get();
}
public void testCredentialsAreLoadedOnRightAlgoAndCredentials() {
Properties propertied = OAuthTestUtils.defaultProperties(new Properties());
Credentials validCredentials = new Credentials(propertied.getProperty("oauth.identity"),
propertied.getProperty("oauth.credential"));
PrivateKeySupplier supplier = new PrivateKeySupplier(Suppliers.ofInstance(validCredentials),
new PrivateKeyForCredentials());
assertNotNull(supplier.get());
}
public static PrivateKey loadPrivateKey() throws Exception {
PrivateKeySupplier supplier = new PrivateKeySupplier(ofInstance(new Credentials("foo",
Files.asCharSource(new File("src/test/resources/testpk.pem"), Charsets.UTF_8).read())),
new PrivateKeyForCredentials());
return supplier.get();
}
}

View File

@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.oauth.v2.functions;
import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.io.BaseEncoding.base64Url;
import static org.jclouds.oauth.v2.config.PrivateKeySupplierTest.loadPrivateKey;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import org.testng.annotations.Test;
@Test(groups = "unit")
public class ClaimsToAssertionTest {
private static final String PAYLOAD = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.\n" +
"eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZ" +
"GV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2ds" +
"ZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2x" +
"lLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ";
private static final String SHA256withRSA_PAYLOAD_SIGNATURE_RESULT =
"bmQrCv4gjkLWDK1JNJni74_kPiSDUMF_FImgqKJMUIgkDX1m2Sg3bH1yjF-cjBN7CvfAscnageo" +
"GtL2TGbwoTjJgUO5Yy0esavUUF-mBQHQtSw-2nL-9TNyM4SNi6fHPbgr83GGKOgA86r" +
"I9-nj3oUGd1fQty2k4Lsd-Zdkz6es";
public void sha256() throws Exception {
byte[] payloadSignature = ClaimsToAssertion.sha256(loadPrivateKey(), PAYLOAD.getBytes(UTF_8));
assertNotNull(payloadSignature);
assertEquals(base64Url().omitPadding().encode(payloadSignature), SHA256withRSA_PAYLOAD_SIGNATURE_RESULT);
}
}

View File

@ -0,0 +1,56 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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.
-->
<configuration scan="false">
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>target/test-data/jclouds.log</file>
<encoder>
<Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
</encoder>
</appender>
<appender name="WIREFILE" class="ch.qos.logback.core.FileAppender">
<file>target/test-data/jclouds-wire.log</file>
<encoder>
<Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
</encoder>
</appender>
<root>
<level value="warn" />
</root>
<logger name="org.jclouds">
<level value="DEBUG" />
<appender-ref ref="FILE" />
</logger>
<logger name="jclouds.wire">
<level value="DEBUG" />
<appender-ref ref="WIREFILE" />
</logger>
<logger name="jclouds.headers">
<level value="DEBUG" />
<appender-ref ref="WIREFILE" />
</logger>
</configuration>

View File

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQCwqwzakEPP+U9vx9JCuMHebFIVQZ4Sjaj2RU9dJ6YT2s3u7dC6
/0fGM5xm4fXmSHqyGC6PC8weQSkxnSpbU+R4jMWPM8ML4TIr5wP0avbg+wy3+WWI
of0MN7YHkCfqpaaiKiMw7niK1y07YvxJN8LX1xLpE7aXgIpn6L/qtJdHnQIDAQAB
AoGBAIAHlcsW3W3smPrC7sdXqXeNPHcXFH0RmC7Qz9EMmLiuyqqqQagitFsYr/GH
M3Ltd611BNi5jfUm97ly0m1ZAKp/nkTMVhfKfRIVsBDHtjQHcUOR9tr0LslptmaN
TG0bovbUohe5KwOqMK4YOjUQbInChVBrf7VrNQtv8e0eShdpAkEA3lzLP9QYfP1i
C4iYXqS7cgMDrs3qujC7PoyB54maen+Uvgyau1ZJpKMzXYkORPcYk+b71bl9jF80
U+7LDnJjPwJBAMtksvL1V8DC5DYL43497JW4KBN4YZ3K7YWx/9gkvc3Q9VdXiUGu
6WKjmcbmsPI/jFdeO71uy934N8qEXLJcyiMCQQCTNKcxWD3l8PCJZiJI9ZFKBwjX
Hmb4X+51mBsfpw7nbbKQplOBFbynC4ujrmoN6e8RaubpNGUTGqvPrNQsejmNAkEA
lUDEAH4BczaQ+QgoXI9ceVG2NvNzzrMHMcC5Ggd8MPhR0VIvKsAMC5I6WjcXSe1Q
Mxy3gf84Ix7u8fHHhCuLOQJAQRhrlXiQUk4cJumNhjza5/+KtaV4FPbEQi+qcWE6
tGoHPEBfbXyUdcUD4ae8X1W0yri0BuyVNaOCpGCBRIhPZA==
-----END RSA PRIVATE KEY-----