Merge pull request #920 from dralves/oauth

Oauth v2
This commit is contained in:
Adrian Cole 2012-11-07 19:32:48 -08:00
commit 674e8f6c04
48 changed files with 3375 additions and 3 deletions

22
labs/oauth/README Normal file
View File

@ -0,0 +1,22 @@
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)
Optional:
- each application may expose a Map<String,String> of additional claims to be added to the token request,
these should be annotated/named with "oauth.additional-claims"
oauth.signature-or-mac-algorithm - the algorithms to use when signing the token request.
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.signature-or-mac-algorithm=RS256\
-Dtest.jclouds.oauth.scopes=https://www.googleapis.com/auth/prediction

123
labs/oauth/pom.xml Normal file
View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to jclouds, Inc. (jclouds) under one or more
contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. jclouds 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.jclouds</groupId>
<artifactId>jclouds-project</artifactId>
<version>1.6.0-SNAPSHOT</version>
<relativePath>../../project/pom.xml</relativePath>
</parent>
<groupId>org.jclouds.labs</groupId>
<artifactId>oauth</artifactId>
<name>jclouds OAuth core</name>
<description>jclouds components to access OAuth</description>
<properties>
<jclouds.version>1.6.0-SNAPSHOT</jclouds.version>
<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.signature-or-mac-algorithm>FIX_ME</test.jclouds.oauth.signature-or-mac-algorithm>
<test.jclouds.oauth.audience>FIX_ME</test.jclouds.oauth.audience>
<test.jclouds.oauth.scopes>FIX_ME</test.jclouds.oauth.scopes>
<test.oauth.api-version>2</test.oauth.api-version>
<test.oauth.build-version></test.oauth.build-version>
</properties>
<dependencies>
<dependency>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-core</artifactId>
<version>${jclouds.version}</version>
</dependency>
<dependency>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-core</artifactId>
<version>${jclouds.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jclouds.driver</groupId>
<artifactId>jclouds-slf4j</artifactId>
<version>${jclouds.version}</version>
<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>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.signature-or-mac-algorithm>${test.jclouds.oauth.signature-or-mac-algorithm>}</test.jclouds.oauth.signature-or-mac-algorithm>
<test.jclouds.oauth.audience>${test.jclouds.oauth.audience}</test.jclouds.oauth.audience>
<test.jclouds.oauth.scopes>${test.jclouds.oauth.scopes}</test.jclouds.oauth.scopes>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,49 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 org.jclouds.concurrent.Timeout;
import org.jclouds.oauth.v2.domain.Token;
import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.rest.AuthorizationException;
import java.util.concurrent.TimeUnit;
/**
* Provides synchronous access to OAuth.
* <p/>
*
* @author David Alves
* @see OAuthAsyncApi
*/
@Timeout(duration = 60, timeUnit = TimeUnit.SECONDS)
public interface OAuthApi {
/**
* Authenticates/Authorizes access to a resource defined in TokenRequest against an OAuth v2
* authentication/authorization server.
*
* @param tokenRequest specified the principal and the required permissions
* @return a Token object with the token required to access the resource along with its expiration time
* @throws AuthorizationException if the principal cannot be authenticated or has no permissions for the specifed
* resources.
*/
public Token authenticate(TokenRequest tokenRequest) throws AuthorizationException;
}

View File

@ -0,0 +1,93 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.collect.ImmutableSet;
import com.google.common.reflect.TypeToken;
import com.google.inject.Module;
import org.jclouds.apis.ApiMetadata;
import org.jclouds.oauth.v2.config.OAuthModule;
import org.jclouds.oauth.v2.config.OAuthRestClientModule;
import org.jclouds.rest.RestContext;
import org.jclouds.rest.internal.BaseRestApiMetadata;
import java.net.URI;
import java.util.Properties;
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
import static org.jclouds.oauth.v2.config.OAuthProperties.SIGNATURE_OR_MAC_ALGORITHM;
/**
* Implementation of {@link ApiMetadata} for OAuth 2 API
*
* @author David Alves
*/
public class OAuthApiMetadata extends BaseRestApiMetadata {
public static final TypeToken<RestContext<OAuthApi, OAuthAsyncApi>> CONTEXT_TOKEN = new
TypeToken<RestContext<OAuthApi, OAuthAsyncApi>>() {};
@Override
public Builder toBuilder() {
return new Builder().fromApiMetadata(this);
}
public OAuthApiMetadata() {
this(new Builder());
}
protected OAuthApiMetadata(Builder builder) {
super(builder);
}
public static Properties defaultProperties() {
Properties properties = BaseRestApiMetadata.defaultProperties();
properties.put(SIGNATURE_OR_MAC_ALGORITHM, "RS256");
properties.put(PROPERTY_SESSION_INTERVAL, 3600);
return properties;
}
public static class Builder extends BaseRestApiMetadata.Builder {
protected Builder() {
super(OAuthApi.class, OAuthAsyncApi.class);
id("oauth").name("OAuth API")
.identityName("service_account")
.credentialName("service_key")
.documentation(URI.create("TODO"))
.version("2")
.defaultProperties(OAuthApiMetadata.defaultProperties())
.defaultModules(ImmutableSet.<Class<? extends Module>>of(OAuthModule.class, OAuthRestClientModule
.class));
}
@Override
public OAuthApiMetadata build() {
return new OAuthApiMetadata(this);
}
@Override
public Builder fromApiMetadata(ApiMetadata in) {
super.fromApiMetadata(in);
return this;
}
}
}

View File

@ -0,0 +1,56 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.util.concurrent.ListenableFuture;
import org.jclouds.oauth.v2.config.Authentication;
import org.jclouds.oauth.v2.domain.Token;
import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.oauth.v2.handlers.OAuthTokenBinder;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.annotations.BinderParam;
import org.jclouds.rest.annotations.Endpoint;
import org.jclouds.rest.annotations.SkipEncoding;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.core.MediaType;
/**
* Provides asynchronous access to OAuth via REST api.
* <p/>
* Usually this is not directly used by a client, which instead specifies OAuthAuthenticator as a request filter,
* which in turn uses this class to perform token requests.
*
* @author David Alves
* @see OAuthAsyncApi
*/
@SkipEncoding({'/', '='})
@Endpoint(Authentication.class)
public interface OAuthAsyncApi {
/**
* @see OAuthApi#authenticate(TokenRequest)
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
public ListenableFuture<Token> authenticate(@BinderParam(OAuthTokenBinder.class) TokenRequest tokenRequest)
throws AuthorizationException;
}

View File

@ -0,0 +1,78 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.collect.ImmutableMap;
import java.util.Map;
/**
* The constants for OAuth \
*
* @author David Alves
*/
public class OAuthConstants {
/**
* Selected algorithm when a signature or mac isn't required.
*/
public static final String NO_ALGORITHM = "none";
/**
* Static mapping between the oauth algorithm name and the Crypto provider signature algorithm name.
*
* @see <a href="http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-06#section-3.1">doc</a>
* @see org.jclouds.oauth.v2.json.JWTTokenRequestFormat
*/
public static final Map<String, String> OAUTH_ALGORITHM_NAMES_TO_SIGNATURE_ALGORITHM_NAMES = ImmutableMap
.<String, String>builder()
.put("RS256", "SHA256withRSA")
.put("RS384", "SHA384withRSA")
.put("RS512", "SHA512withRSA")
.put("HS256", "HmacSHA256")
.put("HS384", "HmacSHA384")
.put("HS512", "HmacSHA512")
.put("ES256", "SHA256withECDSA")
.put("ES384", "SHA384withECDSA")
.put("ES512", "SHA512withECDSA")
.put(NO_ALGORITHM, NO_ALGORITHM).build();
/**
* Static mapping between the oauth algorithm name and the Crypto provider KeyFactory algorithm name.
*
* @see <a href="http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-06#section-3.1">doc</a>
*/
public static final Map<String, String> OAUTH_ALGORITHM_NAMES_TO_KEYFACTORY_ALGORITHM_NAMES = ImmutableMap
.<String, String>builder()
.put("RS256", "RSA")
.put("RS384", "RSA")
.put("RS512", "RSA")
.put("HS256", "DiffieHellman")
.put("HS384", "DiffieHellman")
.put("HS512", "DiffieHellman")
.put("ES256", "EC")
.put("ES384", "EC")
.put("ES512", "EC")
.put(NO_ALGORITHM, NO_ALGORITHM).build();
/**
* The (optional) set of additional claims to use, provided in Map<String,String> form
*/
public static final String ADDITIONAL_CLAIMS = "jclouds.oauth.additional-claims";
}

View File

@ -0,0 +1,38 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 javax.inject.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Qualifies OAuth related resources, such as Endpoint.
*
* @author David Alves
* @see org.jclouds.oauth.v2.OAuthAsyncApi
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Qualifier
public @interface Authentication {
}

View File

@ -0,0 +1,55 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import org.jclouds.oauth.v2.OAuthApi;
import org.jclouds.oauth.v2.OAuthAsyncApi;
import javax.inject.Named;
import javax.inject.Singleton;
import java.net.URI;
import static org.jclouds.rest.config.BinderUtils.bindClientAndAsyncClient;
/**
* An OAuth module to be used form other providers.
*
* @author David Alves
*/
public class OAuthAuthenticationModule extends AbstractModule {
@Override
protected void configure() {
bindClientAndAsyncClient(binder(), OAuthApi.class, OAuthAsyncApi.class);
}
/**
* When oauth is used as a module the oauth endpoint is a normal property
*/
@Provides
@Singleton
@Authentication
protected Supplier<URI> provideAuthenticationEndpoint(@Named("oauth.endpoint") String endpoint) {
return Suppliers.ofInstance(URI.create(endpoint));
}
}

View File

@ -0,0 +1,87 @@
/*
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.base.Function;
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.collect.ImmutableMap;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
import org.jclouds.oauth.v2.domain.ClaimSet;
import org.jclouds.oauth.v2.domain.Header;
import org.jclouds.oauth.v2.domain.OAuthCredentials;
import org.jclouds.oauth.v2.domain.Token;
import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.oauth.v2.functions.BuildTokenRequest;
import org.jclouds.oauth.v2.functions.FetchToken;
import org.jclouds.oauth.v2.functions.OAuthCredentialsSupplier;
import org.jclouds.oauth.v2.functions.SignOrProduceMacForToken;
import org.jclouds.oauth.v2.json.ClaimSetTypeAdapter;
import org.jclouds.oauth.v2.json.HeaderTypeAdapter;
import org.jclouds.rest.internal.GeneratedHttpRequest;
import javax.inject.Named;
import javax.inject.Singleton;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
/**
* Base OAuth module
*
* @author David Alves
*/
public class OAuthModule extends AbstractModule {
@Override
protected void configure() {
bind(new TypeLiteral<Function<byte[], byte[]>>() {}).to(SignOrProduceMacForToken.class);
bind(new TypeLiteral<Map<Type, Object>>() {}).toInstance(ImmutableMap.<Type, Object>of(
Header.class, new HeaderTypeAdapter(),
ClaimSet.class, new ClaimSetTypeAdapter()));
bind(new TypeLiteral<Supplier<OAuthCredentials>>() {}).to(OAuthCredentialsSupplier.class);
bind(new TypeLiteral<Function<GeneratedHttpRequest, TokenRequest>>() {}).to(BuildTokenRequest.class);
bind(new TypeLiteral<Function<TokenRequest, Token>>() {}).to(FetchToken.class);
}
/**
* Provides a cache for tokens. Cache is time based and expires after 59 minutes (the maximum time a token is
* valid is 60 minutes)
*/
@Provides
@Singleton
public LoadingCache<TokenRequest, Token> provideAccessCache(Function<TokenRequest, Token> getAccess,
@Named(PROPERTY_SESSION_INTERVAL) long
sessionIntervalInSeconds) {
// 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
sessionIntervalInSeconds = sessionIntervalInSeconds > 30 ? sessionIntervalInSeconds - 30 :
sessionIntervalInSeconds;
return CacheBuilder.newBuilder().expireAfterWrite(sessionIntervalInSeconds, TimeUnit.MINUTES).build(CacheLoader
.from(getAccess));
}
}

View File

@ -0,0 +1,48 @@
/*
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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;
/**
* Configurable properties for jclouds OAuth
*
* @author David Alves
*/
public class OAuthProperties {
/**
* The selected signature algorithm to use to sign the requests.
* <p/>
* This refers to the name the oauth provider expects, i.e., "RSA
*/
public static final String SIGNATURE_OR_MAC_ALGORITHM = "jclouds.oauth.signature-or-mac-algorithm";
/**
* 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="http://tools.ietf.org/html/draft-jones-json-web-token-04">doc</a>
*/
public static final String AUDIENCE = "jclouds.oauth.audience";
/**
* Optional list of comma-separated scopes to use when no OAuthScopes annotation is present.
*/
public static final String SCOPES = "jclouds.oauth.scopes";
}

View File

@ -0,0 +1,36 @@
package org.jclouds.oauth.v2.config;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.reflect.TypeToken;
import com.google.inject.Provides;
import org.jclouds.oauth.v2.OAuthApi;
import org.jclouds.oauth.v2.OAuthAsyncApi;
import org.jclouds.providers.ProviderMetadata;
import org.jclouds.rest.ConfiguresRestClient;
import org.jclouds.rest.config.RestClientModule;
import javax.inject.Singleton;
import java.net.URI;
/**
* OAuth module to when accessing OAuth stand-alone.
*
* @author David Alves
*/
@ConfiguresRestClient
public class OAuthRestClientModule extends RestClientModule<OAuthApi, OAuthAsyncApi> {
public OAuthRestClientModule() {
super(TypeToken.class.cast(TypeToken.of(OAuthApi.class)), TypeToken.class.cast(TypeToken.of(OAuthAsyncApi
.class)));
}
@Provides
@Singleton
@Authentication
protected Supplier<URI> provideAuthenticationEndpoint(ProviderMetadata providerMetadata) {
return Suppliers.ofInstance(URI.create(providerMetadata.getEndpoint()));
}
}

View File

@ -0,0 +1,45 @@
/*
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 javax.inject.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Used to annotate REST methods/ifaces that use OAuthAuthentication.
* <p/>
* Sets the scopes for the token request for that particular method.
*
* @author David Alves
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Qualifier
public @interface OAuthScopes {
/**
* @return the OAuth scopes required to access the resource.
*/
String[] value();
}

View File

@ -0,0 +1,195 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.base.Objects;
import com.google.common.base.Splitter;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Objects.ToStringHelper;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Objects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
/**
* The claimset for the token.
*
* @author David Alves
* @see <a
* href="https://developers.google.com/accounts/docs/OAuth2ServiceAccount"
* >doc</a>
*/
public class ClaimSet extends ForwardingMap<String, String> {
public static Builder builder() {
return new Builder();
}
public Builder toBuilder() {
return builder().fromClaimSet(this);
}
public static class Builder {
private Set<String> requiredClaims;
private ImmutableMap.Builder<String, String> claims = new ImmutableMap.Builder<String, String>();
private long emissionTime;
private long expirationTime;
public Builder() {
this(ImmutableSet.<String>of());
}
/**
* Constructor that allows to predefine a mandatory set of claims as a comma-separated string, e.g, "iss,iat".
*/
public Builder(String commaSeparatedRequiredClaims) {
this(ImmutableSet.copyOf(Splitter.on(",").split(checkNotNull(commaSeparatedRequiredClaims))));
}
/**
* Constructor that allows to predefine a mandatory set of claims as a set of strings.
*/
public Builder(Set<String> requiredClaims) {
this.requiredClaims = ImmutableSet.copyOf(checkNotNull(requiredClaims));
}
/**
* Adds a Claim, i.e. key/value pair, e.g., "scope":"all_permissions".
*/
public Builder addClaim(String name, String value) {
claims.put(checkNotNull(name), checkNotNull(value, "value of %s", name));
return this;
}
/**
* @see ClaimSet#getEmissionTime()
*/
public Builder emissionTime(long emmissionTime) {
this.emissionTime = emmissionTime;
return this;
}
/**
* @see ClaimSet#getExpirationTime()
*/
public Builder expirationTime(long expirationTime) {
this.expirationTime = expirationTime;
return this;
}
/**
* Adds a map containing multiple claims
*/
public Builder addAllClaims(Map<String, String> claims) {
this.claims.putAll(checkNotNull(claims));
return this;
}
public ClaimSet build() {
Map<String, String> claimsMap = claims.build();
checkState(Sets.intersection(claimsMap.keySet(), requiredClaims).size() == requiredClaims.size(),
"not all required claims were present");
if (expirationTime == 0) {
expirationTime = emissionTime + 3600;
}
return new ClaimSet(claimsMap, emissionTime, expirationTime);
}
public Builder fromClaimSet(ClaimSet claimSet) {
return new Builder().addAllClaims(claimSet.claims).expirationTime(expirationTime).emissionTime(emissionTime);
}
}
private final Map<String, String> claims;
private final long emissionTime;
private final long expirationTime;
private ClaimSet(Map<String, String> claims, long emissionTime, long expirationTime) {
this.claims = claims;
this.emissionTime = emissionTime;
this.expirationTime = expirationTime;
}
/**
* The emission time, in seconds since the epoch.
*/
public long getEmissionTime() {
return emissionTime;
}
/**
* The expiration time, in seconds since the emission time.
*/
public long getExpirationTime() {
return expirationTime;
}
/**
* @returns the claims.
*/
@Override
protected Map<String, String> delegate() {
return claims;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return Objects.hashCode(claims, emissionTime, expirationTime);
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ClaimSet other = (ClaimSet) obj;
return equal(claims, other.claims) && equal(this.emissionTime,
other.emissionTime) && equal(this.expirationTime, other.expirationTime);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return string().toString();
}
protected ToStringHelper string() {
return toStringHelper(this).omitNullValues().add("claims", claims)
.add("emissionTime", emissionTime).add("expirationTIme", expirationTime);
}
}

View File

@ -0,0 +1,131 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.base.Objects;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Objects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* The header for the OAuth token, contains the signer algorithm's name and the type of the token
*
* @author David Alves
* @see <a href="https://developers.google.com/accounts/docs/OAuth2ServiceAccount">doc</a>
*/
public class Header {
public static Builder builder() {
return new Builder();
}
public Builder toBuilder() {
return builder().fromHeader(this);
}
public static class Builder {
private String signerAlgorithm;
private String type;
/**
* @see Header#getSignerAlgorithm()
*/
public Builder signerAlgorithm(String signerAlgorithm) {
this.signerAlgorithm = checkNotNull(signerAlgorithm);
return this;
}
/**
* @see Header#getType()
*/
public Builder type(String type) {
this.type = checkNotNull(type);
return this;
}
public Header build() {
return new Header(signerAlgorithm, type);
}
public Builder fromHeader(Header header) {
return new Builder().signerAlgorithm(header.signerAlgorithm).type(header.type);
}
}
private final String signerAlgorithm;
private final String type;
protected Header(String signerAlgorithm, String type) {
this.signerAlgorithm = checkNotNull(signerAlgorithm);
this.type = checkNotNull(type);
}
/**
* The name of the algorithm used to compute the signature, e.g., "RS256"
*/
public String getSignerAlgorithm() {
return signerAlgorithm;
}
/**
* The type of the token, e.g., "JWT"
*/
public String getType() {
return type;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Header other = (Header) obj;
return equal(this.signerAlgorithm, other.signerAlgorithm) && equal(this.type,
other.type);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return Objects.hashCode(signerAlgorithm, type);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return string().toString();
}
protected Objects.ToStringHelper string() {
return toStringHelper(this).omitNullValues().add("signerAlgorithm", signerAlgorithm)
.add("type", type);
}
}

View File

@ -0,0 +1,132 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.base.Objects;
import org.jclouds.domain.Credentials;
import java.security.PrivateKey;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Objects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Special kind credentials for oauth authentication that includes {@link java.security.PrivateKey} to sign
* requests.
*/
public class OAuthCredentials extends Credentials {
public static Builder builder() {
return new Builder();
}
public Builder toBuilder() {
return builder().fromOauthCredentials(this);
}
public static class Builder extends Credentials.Builder<OAuthCredentials> {
protected PrivateKey privateKey;
/**
* @see OAuthCredentials#privateKey
*/
public Builder privateKey(PrivateKey privateKey) {
this.privateKey = checkNotNull(privateKey);
return this;
}
/**
* @see Credentials#identity
*/
public Builder identity(String identity) {
this.identity = checkNotNull(identity);
return this;
}
/**
* @see Credentials#credential
*/
public Builder credential(String credential) {
this.credential = credential;
return this;
}
@SuppressWarnings("unchecked")
public OAuthCredentials build() {
return new OAuthCredentials(checkNotNull(identity), credential, privateKey);
}
public Builder fromOauthCredentials(OAuthCredentials credentials) {
return new Builder().privateKey(credentials.privateKey).identity(credentials.identity)
.credential(credentials.credential);
}
}
/**
* The private key associated with Credentials#identity.
* Used to sign token requests.
*/
public final PrivateKey privateKey;
public OAuthCredentials(String identity, String credential, PrivateKey privateKey) {
super(identity, credential);
this.privateKey = privateKey;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
OAuthCredentials other = (OAuthCredentials) obj;
return equal(this.identity, other.identity) && equal(this.credential,
other.credential) && equal(this.privateKey,
other.privateKey);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return Objects.hashCode(identity, credential, privateKey);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return string().toString();
}
protected Objects.ToStringHelper string() {
return toStringHelper(this).omitNullValues().add("identity", identity)
.add("credential", credential != null ? credential.hashCode() : null).add("privateKey",
privateKey.hashCode());
}
}

View File

@ -0,0 +1,153 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.base.Objects;
import java.beans.ConstructorProperties;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Objects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* The oauth token, obtained upon a successful token request and ready to embed in requests.
*
* @author David Alves
*/
public class Token {
public static Builder builder() {
return new Builder();
}
public Builder toBuilder() {
return builder().fromToken(this);
}
public static class Builder {
private String accessToken;
private String tokenType;
private long expiresIn;
/**
* @see Token#getAccessToken()
*/
public Builder accessToken(String accessToken) {
this.accessToken = checkNotNull(accessToken);
return this;
}
/**
* @see Token#getTokenType()
*/
public Builder tokenType(String tokenType) {
this.tokenType = checkNotNull(tokenType);
return this;
}
/**
* @see Token#getExpiresIn()
*/
public Builder expiresIn(long expiresIn) {
this.expiresIn = expiresIn;
return this;
}
public Token build() {
return new Token(accessToken, tokenType, expiresIn);
}
public Builder fromToken(Token token) {
return new Builder().accessToken(token.accessToken).tokenType(token.tokenType).expiresIn(token.expiresIn);
}
}
private final String accessToken;
private final String tokenType;
private final long expiresIn;
@ConstructorProperties({"access_token", "token_type", "expires_in"})
protected Token(String accessToken, String tokenType, long expiresIn) {
this.accessToken = accessToken;
this.tokenType = tokenType;
this.expiresIn = expiresIn;
}
/**
* The access token obtained from the OAuth server.
*/
public String getAccessToken() {
return accessToken;
}
/**
* The type of the token, e.g., "Bearer"
*/
public String getTokenType() {
return tokenType;
}
/**
* In how many seconds this token expires.
*/
public long getExpiresIn() {
return expiresIn;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Token other = (Token) obj;
return equal(this.accessToken, other.accessToken) && equal(this.tokenType,
other.tokenType) && equal(this.expiresIn,
other.expiresIn);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return Objects.hashCode(accessToken, tokenType, expiresIn);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return string().toString();
}
protected Objects.ToStringHelper string() {
return toStringHelper(this).omitNullValues().add("accessToken", accessToken)
.add("tokenType", tokenType).add("expiresIn", expiresIn);
}
}

View File

@ -0,0 +1,135 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.base.Objects;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Objects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A complete token request.
*
* @author David Alves
*/
public class TokenRequest {
public static Builder builder() {
return new Builder();
}
public Builder toBuilder() {
return builder().fromTokenRequest(this);
}
public static class Builder {
private Header header;
private ClaimSet claimSet;
/**
* @see TokenRequest#getClaimSet()
*/
public Builder header(Header header) {
this.header = header;
return this;
}
/**
* @see TokenRequest#getHeader()
*/
public Builder claimSet(ClaimSet claimSet) {
this.claimSet = claimSet;
return this;
}
public TokenRequest build() {
return new TokenRequest(header, claimSet);
}
public Builder fromTokenRequest(TokenRequest tokeRequest) {
return new Builder().header(tokeRequest.header).claimSet(tokeRequest.claimSet);
}
}
private final Header header;
private final ClaimSet claimSet;
public TokenRequest(Header header, ClaimSet claimSet) {
this.header = checkNotNull(header);
this.claimSet = checkNotNull(claimSet);
}
/**
* The header of this token request.
*
* @see Header
*/
public Header getHeader() {
return header;
}
/**
* The claim set of this token request.
*
* @see ClaimSet
*/
public ClaimSet getClaimSet() {
return claimSet;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TokenRequest other = (TokenRequest) obj;
return equal(this.header, other.header) && equal(this.claimSet,
other.claimSet);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return Objects.hashCode(header, claimSet);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return string().toString();
}
protected Objects.ToStringHelper string() {
return toStringHelper(this).omitNullValues().add("header", header)
.add("claimSet", claimSet);
}
}

View File

@ -0,0 +1,49 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.inject.ImplementedBy;
import org.jclouds.http.HttpRequest;
import org.jclouds.oauth.v2.json.JWTTokenRequestFormat;
import java.util.Set;
/**
* Transforms a TokenRequest into a specific format (e.g. JWT token)
*
* @author David Alves
*/
@ImplementedBy(JWTTokenRequestFormat.class)
public interface TokenRequestFormat {
/**
* Transforms the provided HttpRequest into a particular token request with a specific format.
*/
public <R extends HttpRequest> R formatRequest(R httpRequest, TokenRequest tokenRequest);
/**
* The name of the type of the token request, e.g., "JWT"
*/
public String getTypeName();
/**
* The claims that must be present in the token request for it to be valid.
*/
public Set<String> requiredClaims();
}

View File

@ -0,0 +1,67 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.base.Function;
import com.google.common.cache.LoadingCache;
import org.jclouds.http.HttpException;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequestFilter;
import org.jclouds.oauth.v2.domain.Token;
import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.rest.internal.GeneratedHttpRequest;
import javax.inject.Inject;
import javax.inject.Singleton;
import static com.google.common.base.Preconditions.checkState;
/**
* To be used by client applications to embed an OAuth authentication in their REST requests.
* <p/>
* TODO when we're able to use the OAuthAuthentication an this should be used automatically
*
* @author David Alves
*/
@Singleton
public class OAuthAuthenticator implements HttpRequestFilter {
private Function<GeneratedHttpRequest, TokenRequest> tokenRequestBuilder;
private Function<TokenRequest, Token> tokenFetcher;
@Inject
OAuthAuthenticator(Function<GeneratedHttpRequest, TokenRequest> tokenRequestBuilder, LoadingCache<TokenRequest,
Token> tokenFetcher) {
this.tokenRequestBuilder = tokenRequestBuilder;
this.tokenFetcher = tokenFetcher;
}
@Override
public HttpRequest filter(HttpRequest request) throws HttpException {
checkState(request instanceof GeneratedHttpRequest, "request must be an instance of GeneratedHttpRequest");
GeneratedHttpRequest generatedHttpRequest = (GeneratedHttpRequest) request;
TokenRequest tokenRequest = tokenRequestBuilder.apply(generatedHttpRequest);
Token token = tokenFetcher.apply(tokenRequest);
return request.toBuilder().addHeader("Authorization", String.format("%s %s",
token.getTokenType(), token.getAccessToken())).build();
}
}

View File

@ -0,0 +1,131 @@
/*
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
import com.google.common.base.Ticker;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import org.jclouds.Constants;
import org.jclouds.oauth.v2.config.OAuthScopes;
import org.jclouds.oauth.v2.domain.ClaimSet;
import org.jclouds.oauth.v2.domain.Header;
import org.jclouds.oauth.v2.domain.OAuthCredentials;
import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.oauth.v2.domain.TokenRequestFormat;
import org.jclouds.rest.internal.GeneratedHttpRequest;
import javax.inject.Singleton;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkState;
import static org.jclouds.oauth.v2.OAuthConstants.ADDITIONAL_CLAIMS;
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
import static org.jclouds.oauth.v2.config.OAuthProperties.SCOPES;
import static org.jclouds.oauth.v2.config.OAuthProperties.SIGNATURE_OR_MAC_ALGORITHM;
/**
* The default authenticator.
* <p/>
* Builds the default token request with the following claims: iss,scope,aud,iat,exp.
* <p/>
* TODO scopes etc should come from the REST method and not from a global property
*
* @author David Alves
*/
@Singleton
public class BuildTokenRequest implements Function<GeneratedHttpRequest, TokenRequest> {
private final String assertionTargetDescription;
private final String signatureAlgorithm;
private final TokenRequestFormat tokenRequestFormat;
private final Supplier<OAuthCredentials> credentialsSupplier;
private final long tokenDuration;
@Inject(optional = true)
@Named(ADDITIONAL_CLAIMS)
protected Map<String, String> additionalClaims = ImmutableMap.of();
@Inject(optional = true)
@Named(SCOPES)
protected String globalScopes = null;
@Inject(optional = true)
public Ticker ticker = Ticker.systemTicker();
@Inject
public BuildTokenRequest(@Named(AUDIENCE) String assertionTargetDescription,
@Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureAlgorithm,
TokenRequestFormat tokenRequestFormat, Supplier<OAuthCredentials> credentialsSupplier,
@Named(Constants.PROPERTY_SESSION_INTERVAL) long tokenDuration) {
this.assertionTargetDescription = assertionTargetDescription;
this.signatureAlgorithm = signatureAlgorithm;
this.tokenRequestFormat = tokenRequestFormat;
this.credentialsSupplier = credentialsSupplier;
this.tokenDuration = tokenDuration;
}
@Override
public TokenRequest apply(GeneratedHttpRequest request) {
long now = TimeUnit.SECONDS.convert(ticker.read(), TimeUnit.NANOSECONDS);
// fetch the token
Header header = new Header.Builder()
.signerAlgorithm(signatureAlgorithm)
.type(tokenRequestFormat.getTypeName())
.build();
ClaimSet claimSet = new ClaimSet.Builder(this.tokenRequestFormat.requiredClaims())
.addClaim("iss", credentialsSupplier.get().identity)
.addClaim("scope", getOAuthScopes(request))
.addClaim("aud", assertionTargetDescription)
.emissionTime(now)
.expirationTime(now + tokenDuration)
.addAllClaims(additionalClaims)
.build();
return new TokenRequest.Builder()
.header(header)
.claimSet(claimSet)
.build();
}
protected String getOAuthScopes(GeneratedHttpRequest request) {
OAuthScopes classScopes = request.getDeclaring().getAnnotation(OAuthScopes.class);
OAuthScopes methodScopes = request.getJavaMethod().getAnnotation(OAuthScopes.class);
// if no annotations are present the rely on globally set scopes
if (classScopes == null && methodScopes == null) {
checkState(globalScopes != null, String.format("REST class or method should be annotated " +
"with OAuthScopes specifying required permissions. Alternatively a global property " +
"\"oauth.scopes\" may be set to define scopes globally. REST Class: %s, Method: %s",
request.getDeclaring().getName(),
request.getJavaMethod().getName()));
return globalScopes;
}
OAuthScopes scopes = methodScopes != null ? methodScopes : classScopes;
return Joiner.on(",").join(scopes.value());
}
}

View File

@ -0,0 +1,47 @@
/*
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.base.Function;
import org.jclouds.oauth.v2.OAuthApi;
import org.jclouds.oauth.v2.domain.Token;
import org.jclouds.oauth.v2.domain.TokenRequest;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* @author David Alves
*/
@Singleton
public class FetchToken implements Function<TokenRequest, Token> {
private OAuthApi oAuthApi;
@Inject
public FetchToken(OAuthApi oAuthApi) {
this.oAuthApi = oAuthApi;
}
@Override
public Token apply(TokenRequest input) {
return this.oAuthApi.authenticate(input);
}
}

View File

@ -0,0 +1,91 @@
/*
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.base.Supplier;
import org.jclouds.crypto.Pems;
import org.jclouds.io.Payloads;
import org.jclouds.oauth.v2.domain.OAuthCredentials;
import org.jclouds.rest.annotations.Credential;
import org.jclouds.rest.annotations.Identity;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.String.format;
import static org.jclouds.oauth.v2.OAuthConstants.NO_ALGORITHM;
import static org.jclouds.oauth.v2.OAuthConstants.OAUTH_ALGORITHM_NAMES_TO_KEYFACTORY_ALGORITHM_NAMES;
import static org.jclouds.oauth.v2.config.OAuthProperties.SIGNATURE_OR_MAC_ALGORITHM;
/**
* Loads {@link OAuthCredentials} from a pem private key using the KeyFactory obtained from the
* JWT Algorithm Name<->KeyFactory name mapping in OAuthConstants. The pem pk algorithm must match the KeyFactory
* algorithm.
*
* @author David Alves
* @see org.jclouds.oauth.v2.OAuthConstants#OAUTH_ALGORITHM_NAMES_TO_KEYFACTORY_ALGORITHM_NAMES
*/
@Singleton
public class OAuthCredentialsSupplier implements Supplier<OAuthCredentials> {
private final String identity;
private final String privateKeyInPemFormat;
private final String keyFactoryAlgorithm;
private OAuthCredentials credentials;
@Inject
public OAuthCredentialsSupplier(@Identity String identity,
@Credential String privateKeyInPemFormat,
@Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureOrMacAlgorithm) {
this.identity = identity;
this.privateKeyInPemFormat = privateKeyInPemFormat;
checkState(OAUTH_ALGORITHM_NAMES_TO_KEYFACTORY_ALGORITHM_NAMES.containsKey(signatureOrMacAlgorithm),
format("No mapping for key factory for algorithm: %s", signatureOrMacAlgorithm));
this.keyFactoryAlgorithm = OAUTH_ALGORITHM_NAMES_TO_KEYFACTORY_ALGORITHM_NAMES.get(signatureOrMacAlgorithm);
}
@PostConstruct
public void loadPrivateKey() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
if (keyFactoryAlgorithm.equals(NO_ALGORITHM)) {
this.credentials = new OAuthCredentials.Builder().identity(identity).credential
(privateKeyInPemFormat).build();
return;
}
KeyFactory keyFactory = KeyFactory.getInstance(keyFactoryAlgorithm);
PrivateKey privateKey = keyFactory.generatePrivate(Pems.privateKeySpec(Payloads.newStringPayload
(privateKeyInPemFormat)));
this.credentials = new OAuthCredentials.Builder().identity(identity).credential
(privateKeyInPemFormat).privateKey(privateKey).build();
}
@Override
public OAuthCredentials get() {
return this.credentials;
}
}

View File

@ -0,0 +1,123 @@
/*
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import org.jclouds.oauth.v2.domain.OAuthCredentials;
import javax.annotation.PostConstruct;
import javax.crypto.Mac;
import javax.inject.Inject;
import javax.inject.Named;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.String.format;
import static org.jclouds.oauth.v2.OAuthConstants.NO_ALGORITHM;
import static org.jclouds.oauth.v2.OAuthConstants.OAUTH_ALGORITHM_NAMES_TO_SIGNATURE_ALGORITHM_NAMES;
import static org.jclouds.oauth.v2.config.OAuthProperties.SIGNATURE_OR_MAC_ALGORITHM;
/**
* Function that signs/produces mac's for OAuth tokens, provided a {@link Signature} or a {@link Mac} algorithm and
* {@link PrivateKey}
*
* @author David Alves
*/
public class SignOrProduceMacForToken implements Function<byte[], byte[]> {
private final Supplier<OAuthCredentials> credentials;
private final String signatureOrMacAlgorithm;
private Function<byte[], byte[]> signatureOrMacFunction;
@Inject
public SignOrProduceMacForToken(@Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureOrMacAlgorithm,
Supplier<OAuthCredentials> credentials) {
checkState(OAUTH_ALGORITHM_NAMES_TO_SIGNATURE_ALGORITHM_NAMES.containsKey(signatureOrMacAlgorithm),
format("the signature algorithm %s is not supported", signatureOrMacAlgorithm));
this.signatureOrMacAlgorithm = OAUTH_ALGORITHM_NAMES_TO_SIGNATURE_ALGORITHM_NAMES.get(signatureOrMacAlgorithm);
this.credentials = credentials;
}
@PostConstruct
public void loadSignatureOrMacOrNone() throws InvalidKeyException, NoSuchAlgorithmException {
if (signatureOrMacAlgorithm.equals(NO_ALGORITHM)) {
this.signatureOrMacFunction = new Function<byte[], byte[]>() {
@Override
public byte[] apply(byte[] input) {
return null;
}
};
} else if (signatureOrMacAlgorithm.startsWith("SHA")) {
this.signatureOrMacFunction = new SignatureGenerator(signatureOrMacAlgorithm, credentials.get().privateKey);
} else {
this.signatureOrMacFunction = new MessageAuthenticationCodeGenerator(signatureOrMacAlgorithm,
credentials.get().privateKey);
}
}
@Override
public byte[] apply(byte[] input) {
return signatureOrMacFunction.apply(input);
}
private static class MessageAuthenticationCodeGenerator implements Function<byte[], byte[]> {
private Mac mac;
private MessageAuthenticationCodeGenerator(String macAlgorithm, PrivateKey privateKey) throws
NoSuchAlgorithmException, InvalidKeyException {
this.mac = Mac.getInstance(macAlgorithm);
this.mac.init(privateKey);
}
@Override
public byte[] apply(byte[] input) {
this.mac.update(input);
return this.mac.doFinal();
}
}
private static class SignatureGenerator implements Function<byte[], byte[]> {
private Signature signature;
private SignatureGenerator(String signatureAlgorithm, PrivateKey privateKey) throws NoSuchAlgorithmException,
InvalidKeyException {
this.signature = Signature.getInstance(signatureAlgorithm);
this.signature.initSign(privateKey);
}
@Override
public byte[] apply(byte[] input) {
try {
signature.update(input);
return signature.sign();
} catch (SignatureException e) {
throw Throwables.propagate(e);
}
}
}
}

View File

@ -0,0 +1,68 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.handlers;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseException;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.ResourceNotFoundException;
import javax.inject.Singleton;
import static javax.ws.rs.core.Response.Status;
import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
/**
* This will parse and set an appropriate exception on the command object.
*
* @author David Alves
*/
@Singleton
public class OAuthErrorHandler implements HttpErrorHandler {
public void handleError(HttpCommand command, HttpResponse response) {
// it is important to always read fully and close streams
byte[] data = closeClientButKeepContentStream(response);
String message = data != null ? new String(data) : null;
Exception exception = message != null ? new HttpResponseException(command, response, message)
: new HttpResponseException(command, response);
message = message != null ? message : String.format("%s -> %s", command.getCurrentRequest().getRequestLine(),
response.getStatusLine());
Status status = Status.fromStatusCode(response.getStatusCode());
switch (status) {
case BAD_REQUEST:
break;
case UNAUTHORIZED:
case FORBIDDEN:
exception = new AuthorizationException(message, exception);
break;
case NOT_FOUND:
if (!command.getCurrentRequest().getMethod().equals("DELETE")) {
exception = new ResourceNotFoundException(message, exception);
}
break;
case CONFLICT:
exception = new IllegalStateException(message, exception);
break;
}
command.setException(exception);
}
}

View File

@ -0,0 +1,49 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.handlers;
import org.jclouds.http.HttpRequest;
import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.oauth.v2.domain.TokenRequestFormat;
import org.jclouds.rest.Binder;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* Generic implementation of a token binder. Uses a provided {@link TokenRequestFormat} to actually bind tokens to
* requests.
*
* @author David Alves
*/
@Singleton
public class OAuthTokenBinder implements Binder {
private final TokenRequestFormat tokenRequestFormat;
@Inject
OAuthTokenBinder(TokenRequestFormat tokenRequestFormat) {
this.tokenRequestFormat = tokenRequestFormat;
}
@Override
public <R extends HttpRequest> R bindToRequest(R request, Object input) {
return tokenRequestFormat.formatRequest(request, (TokenRequest) input);
}
}

View File

@ -0,0 +1,63 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.json;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.jclouds.oauth.v2.domain.ClaimSet;
import java.io.IOException;
import java.util.Map;
/**
* JSON TypeAdapter for the ClaimSet type. Pull the claims maps to the root level and adds two properties for the
* expiration time and issuing time.
*
* @author David Alves
*/
public class ClaimSetTypeAdapter extends TypeAdapter<ClaimSet> {
@Override
public void write(JsonWriter out, ClaimSet value) throws IOException {
out.beginObject();
for (Map.Entry<String, String> entry : value.entrySet()) {
out.name(entry.getKey());
out.value(entry.getValue());
}
out.name("exp");
out.value(value.getExpirationTime());
out.name("iat");
out.value(value.getEmissionTime());
out.endObject();
}
@Override
public ClaimSet read(JsonReader in) throws IOException {
ClaimSet.Builder builder = new ClaimSet.Builder();
in.beginObject();
while (in.hasNext()) {
String claimName = in.nextName();
String claimValue = in.nextString();
builder.addClaim(claimName, claimValue);
}
in.endObject();
return builder.build();
}
}

View File

@ -0,0 +1,54 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.json;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.jclouds.oauth.v2.domain.Header;
import java.io.IOException;
/**
* JSON TypeAdapter for the Header type. Simply transforms the field names.
*/
public class HeaderTypeAdapter extends TypeAdapter<Header> {
@Override
public void write(JsonWriter out, Header value) throws IOException {
out.beginObject();
out.name("alg");
out.value(value.getSignerAlgorithm());
out.name("typ");
out.value(value.getType());
out.endObject();
}
@Override
public Header read(JsonReader in) throws IOException {
Header.Builder builder = new Header.Builder();
in.beginObject();
in.nextName();
builder.signerAlgorithm(in.nextString());
in.nextName();
builder.type(in.nextString());
in.endObject();
return builder.build();
}
}

View File

@ -0,0 +1,101 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.json;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import org.jclouds.crypto.CryptoStreams;
import org.jclouds.http.HttpRequest;
import org.jclouds.io.Payloads;
import org.jclouds.json.Json;
import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.oauth.v2.domain.TokenRequestFormat;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Set;
import static com.google.common.base.Joiner.on;
/**
* Formats a token request into JWT format namely:
* - transforms the token request to json
* - creates the base64 header.claimset portions of the payload.
* - uses the provided signer function to create a signature
* - creates the full url encoded payload as described in:
* https://developers.google.com/accounts/docs/OAuth2ServiceAccount
* <p/>
*
* @author David Alves
*/
@Singleton
public class JWTTokenRequestFormat implements TokenRequestFormat {
private static final String ASSERTION_FORM_PARAM = "assertion";
private static final String GRANT_TYPE_FORM_PARAM = "grant_type";
private static final String GRANT_TYPE_JWT_BEARER = "urn:ietf:params:oauth:grant-type:jwt-bearer";
private final Function<byte[], byte[]> signer;
private final Json json;
@Inject
public JWTTokenRequestFormat(Function<byte[], byte[]> signer, Json json) {
this.signer = signer;
this.json = json;
}
@Override
public <R extends HttpRequest> R formatRequest(R httpRequest, TokenRequest tokenRequest) {
HttpRequest.Builder builder = httpRequest.toBuilder();
// transform to json and encode in base 64
// use commons-codec base 64 which properly encodes urls (jclouds's Base64 does not)
String encodedHeader = json.toJson(tokenRequest.getHeader());
String encodedClaimSet = json.toJson(tokenRequest.getClaimSet());
String encodedSignature = null;
encodedHeader = CryptoStreams.base64Url(encodedHeader.getBytes(Charsets.UTF_8));
encodedClaimSet = CryptoStreams.base64Url(encodedClaimSet.getBytes(Charsets.UTF_8));
byte[] signature = signer.apply(on(".").join(encodedHeader, encodedClaimSet).getBytes(Charsets.UTF_8));
encodedSignature = signature != null ? CryptoStreams.base64Url(signature) : "";
// the final assertion in base 64 encoded {header}.{claimSet}.{signature} format
String assertion = on(".").join(encodedHeader, encodedClaimSet, encodedSignature);
builder.payload(Payloads.newUrlEncodedFormPayload(ImmutableMultimap.of(GRANT_TYPE_FORM_PARAM,
GRANT_TYPE_JWT_BEARER, ASSERTION_FORM_PARAM, assertion)));
return (R) builder.build();
}
@Override
public String getTypeName() {
return "JWT";
}
@Override
public Set<String> requiredClaims() {
// exp and ist (expiration and emission times) are assumed mandatory already
return ImmutableSet.of("iss", "scope", "aud");
}
}

View File

@ -0,0 +1 @@
org.jclouds.oauth.v2.OAuthApiMetadata

View File

@ -0,0 +1,113 @@
/*
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.base.Ticker;
import com.google.inject.Key;
import com.google.inject.name.Names;
import org.jclouds.apis.BaseContextLiveTest;
import org.jclouds.oauth.v2.domain.ClaimSet;
import org.jclouds.oauth.v2.domain.Header;
import org.jclouds.oauth.v2.domain.Token;
import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.rest.RestContext;
import org.testng.annotations.Test;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkState;
import static org.jclouds.oauth.v2.OAuthTestUtils.setCredentialFromPemFile;
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
import static org.jclouds.oauth.v2.config.OAuthProperties.SIGNATURE_OR_MAC_ALGORITHM;
import static org.testng.Assert.assertNotNull;
/**
* A base test of oauth authenticated rest providers. Providers must set the following properties:
* <p/>
* - oauth.endpoint
* - oauth.audience
* - oauth.signature-or-mac-algorithm
* <p/>
* - oauth.scopes is provided by the subclass
* <p/>
* This test asserts that a provider can authenticate with oauth for a given scope, or more simply
* that authentication/authorization is working.
*
* @author David Alves
*/
@Test(groups = "live")
public abstract class BaseOauthAuthenticatedRestContextLiveTest<S, A> extends BaseContextLiveTest<RestContext<S, A>> {
private OAuthApi oauthApi;
@Override
protected Properties setupProperties() {
Properties props = super.setupProperties();
setCredentialFromPemFile(props, provider + ".credential");
return props;
}
public void testAuthenticate() {
try {
oauthApi = context.utils().injector().getInstance(OAuthApi.class);
} catch (Exception e) {
throw new IllegalStateException("Provider has no OAuthApi bound. Was the OAuthAuthenticationModule added?");
}
// obtain the necessary properties from the context
String signatureAlgorithm = getContextPropertyOrFail(SIGNATURE_OR_MAC_ALGORITHM);
checkState(OAuthConstants.OAUTH_ALGORITHM_NAMES_TO_SIGNATURE_ALGORITHM_NAMES.containsKey(signatureAlgorithm)
, String.format("Algorithm not supported: " + signatureAlgorithm));
String audience = getContextPropertyOrFail(AUDIENCE);
// obtain the scopes from the subclass
String scopes = getScopes();
Header header = Header.builder().signerAlgorithm(signatureAlgorithm).type("JWT").build();
long now = TimeUnit.SECONDS.convert(Ticker.systemTicker().read(), TimeUnit.NANOSECONDS);
ClaimSet claimSet = ClaimSet.builder().addClaim("aud", audience).addClaim("scope", scopes).addClaim("iss",
identity).emissionTime(now).expirationTime(now + 3600).build();
TokenRequest tokenRequest = TokenRequest.builder().header(header).claimSet(claimSet).build();
Token token = oauthApi.authenticate(tokenRequest);
assertNotNull(token);
}
public abstract String getScopes();
private String getContextPropertyOrFail(String property) {
try {
return context.utils().injector().getInstance(Key.get(String.class, Names.named(property)));
} catch (Exception e) {
throw new IllegalStateException("Provider " + provider + " must have a named property: " + property + " for " +
"oauth to work");
}
}
}

View File

@ -0,0 +1,42 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.collect.ImmutableSet;
import com.google.common.reflect.TypeToken;
import org.jclouds.View;
import org.jclouds.apis.internal.BaseApiMetadataTest;
import org.testng.annotations.Test;
/**
* Tests that OAuthApiMetadata is properly registered in ServiceLoader
* <p/>
* <pre>
* META-INF/services/org.jclouds.apis.ApiMetadata
* </pre>
*
* @author Adrian Cole
*/
@Test(groups = "unit")
public class OAuthApiMetadataTest extends BaseApiMetadataTest {
public OAuthApiMetadataTest() {
super(new OAuthApiMetadata(), ImmutableSet.<TypeToken<? extends View>>of());
}
}

View File

@ -0,0 +1,81 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 com.google.common.base.Charsets;
import com.google.common.base.Throwables;
import com.google.common.io.Files;
import org.jclouds.util.Strings2;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
/**
* Utils for OAuth tests.
*
* @author David Alves
*/
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", Strings2.toStringAndClose(new FileInputStream("src/test/resources/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 Throwables.propagate(e);
}
}
public static String setCredentialFromPemFile(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 path)", testKey));
try {
credentialFromFile = Files.toString(new File(val), Charsets.UTF_8);
} catch (IOException e) {
throw Throwables.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,102 @@
/*
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.features;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.oauth.v2.OAuthApi;
import org.jclouds.oauth.v2.OAuthTestUtils;
import org.jclouds.oauth.v2.domain.ClaimSet;
import org.jclouds.oauth.v2.domain.Header;
import org.jclouds.oauth.v2.domain.Token;
import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.oauth.v2.internal.BaseOAuthApiExpectTest;
import org.testng.annotations.Test;
import javax.ws.rs.core.MediaType;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Properties;
import static org.jclouds.crypto.CryptoStreams.base64Url;
import static org.testng.Assert.assertEquals;
/**
* Tests that a token requess is well formed.
*
* @author David Alves
*/
@Test(groups = "unit")
public class OAuthApiExpectTest extends BaseOAuthApiExpectTest {
private static final String header = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";
private static final String claims = "{\"iss\":\"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer" +
".gserviceaccount.com\"," +
"\"scope\":\"https://www.googleapis.com/auth/prediction\",\"aud\":\"https://accounts.google" +
".com/o/oauth2/token\",\"exp\":1328573381,\"iat\":1328569781}";
private static final Token TOKEN = new Token.Builder().accessToken
("1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M").tokenType("Bearer").expiresIn(3600).build();
private static final ClaimSet CLAIM_SET = new ClaimSet.Builder().addClaim("iss",
"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer" +
".gserviceaccount.com")
.addClaim("scope", "https://www.googleapis.com/auth/prediction")
.addClaim("aud", "https://accounts.google.com/o/oauth2/token")
.expirationTime(1328573381)
.emissionTime(1328569781).build();
private static final Header HEADER = new Header.Builder().signerAlgorithm("RS256").type("JWT").build();
private static final String URL_ENCODED_TOKEN_REQUEST =
"grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&" +
// Base64 Encoded Header
"assertion=" + base64Url(header.getBytes(Charset.forName("UTF-8"))) + "." +
// Base64 Encoded Claims
base64Url(claims.getBytes(Charset.forName("UTF-8"))) + "." +
// Base64 encoded {header}.{claims} signature (using SHA256)
"W2Lesr_98AzVYiMbzxFqmwcOjpIWlwqkC6pNn1fXND9oSDNNnFhy-AAR6DKH-x9ZmxbY80" +
"R5fH-OCeWumXlVgceKN8Z2SmgQsu8ElTpypQA54j_5j8vUImJ5hsOUYPeyF1U2BUzZ3L5g" +
"03PXBA0YWwRU9E1ChH28dQBYuGiUmYw";
private static final HttpRequest TOKEN_REQUEST = HttpRequest.builder()
.method("POST")
.endpoint(URI.create("http://localhost:5000/o/oauth2/token"))
.addHeader("Accept", MediaType.APPLICATION_JSON)
.payload(payloadFromStringWithContentType(URL_ENCODED_TOKEN_REQUEST, "application/x-www-form-urlencoded"))
.build();
private static final HttpResponse TOKEN_RESPONSE = HttpResponse.builder().statusCode(200).payload(
payloadFromString("{\n" +
" \"access_token\" : \"1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M\",\n" +
" \"token_type\" : \"Bearer\",\n" +
" \"expires_in\" : 3600\n" +
"}")).build();
@Override
protected Properties setupProperties() {
return OAuthTestUtils.defaultProperties(super.setupProperties());
}
public void testGenerateJWTRequest() {
OAuthApi api = requestSendsResponse(TOKEN_REQUEST, TOKEN_RESPONSE);
assertEquals(api.authenticate(new TokenRequest(HEADER, CLAIM_SET)), TOKEN);
}
}

View File

@ -0,0 +1,95 @@
/*
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.features;
import com.google.common.reflect.TypeToken;
import org.jclouds.oauth.v2.OAuthApi;
import org.jclouds.oauth.v2.OAuthApiMetadata;
import org.jclouds.oauth.v2.OAuthAsyncApi;
import org.jclouds.oauth.v2.OAuthConstants;
import org.jclouds.oauth.v2.domain.ClaimSet;
import org.jclouds.oauth.v2.domain.Header;
import org.jclouds.oauth.v2.domain.Token;
import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.oauth.v2.internal.BaseOAuthApiLiveTest;
import org.jclouds.rest.RestContext;
import org.testng.annotations.Test;
import java.util.Properties;
import static com.google.common.base.Preconditions.checkState;
import static org.jclouds.oauth.v2.OAuthTestUtils.getMandatoryProperty;
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
import static org.jclouds.oauth.v2.config.OAuthProperties.SCOPES;
import static org.jclouds.oauth.v2.config.OAuthProperties.SIGNATURE_OR_MAC_ALGORITHM;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
/**
* A live test for authentication. Requires the following properties to be set:
* - test.oauth.endpoint
* - test.oauth.identity
* - test.oauth.credential
* - test.jclouds.oauth.audience
* - test.jclouds.oauth.scopes
* - test.jclouds.oauth.signature-or-mac-algorithm
*
* @author David Alves
*/
@Test(groups = "live", singleThreaded = true)
public class OAuthApiLiveTest extends BaseOAuthApiLiveTest {
private Properties properties;
@Override
protected Properties setupProperties() {
properties = super.setupProperties();
return properties;
}
@Test(groups = "live", singleThreaded = true)
public void testAuthenticateJWTToken() throws Exception {
assertTrue(properties != null, "properties were not set");
String signatureAlgorithm = getMandatoryProperty(properties, SIGNATURE_OR_MAC_ALGORITHM);
checkState(OAuthConstants.OAUTH_ALGORITHM_NAMES_TO_SIGNATURE_ALGORITHM_NAMES.containsKey(signatureAlgorithm)
, String.format("Algorithm not supported: " + signatureAlgorithm));
Header header = Header.builder().signerAlgorithm(signatureAlgorithm).type("JWT").build();
String scopes = getMandatoryProperty(properties, SCOPES);
String audience = getMandatoryProperty(properties, AUDIENCE);
long now = nowInSeconds();
ClaimSet claimSet = ClaimSet.builder().addClaim("aud", audience).addClaim("scope", scopes).addClaim("iss",
identity).emissionTime(now).expirationTime(now + 3600).build();
TokenRequest tokenRequest = TokenRequest.builder().header(header).claimSet(claimSet).build();
Token token = context.getApi().authenticate(tokenRequest);
assertNotNull(token);
}
@Override
protected TypeToken<RestContext<OAuthApi, OAuthAsyncApi>> contextType() {
return OAuthApiMetadata.CONTEXT_TOKEN;
}
}

View File

@ -0,0 +1,58 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 org.jclouds.oauth.v2.domain.OAuthCredentials;
import org.jclouds.util.Strings2;
import org.testng.annotations.Test;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
/**
* Test loading the credentials by extracting a pk from a PKCS12 keystore.
*/
@Test(groups = "unit")
public class OAuthCredentialsFromPKTest {
public static OAuthCredentials loadOAuthCredentials() throws IOException, NoSuchAlgorithmException,
CertificateException, InvalidKeySpecException {
OAuthCredentialsSupplier loader = new OAuthCredentialsSupplier("foo",
Strings2.toStringAndClose(new FileInputStream("src/test/resources/testpk.pem")), "RS256");
loader.loadPrivateKey();
return loader.get();
}
public void testLoadPKString() throws IOException, NoSuchAlgorithmException, KeyStoreException,
CertificateException, UnrecoverableKeyException, InvalidKeySpecException {
OAuthCredentials creds = loadOAuthCredentials();
assertNotNull(creds);
assertEquals(creds.identity, "foo");
assertNotNull(creds.privateKey);
}
}

View File

@ -0,0 +1,65 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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 org.jclouds.crypto.CryptoStreams;
import org.testng.annotations.Test;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import static com.google.common.base.Suppliers.ofInstance;
import static org.testng.Assert.assertNotNull;
import static org.testng.AssertJUnit.assertEquals;
/**
* Tests the SignOrProduceMacForToken
*
* @author David Alves
*/
@Test(groups = "unit")
public class SignerFunctionTest {
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 testSignPayload() throws InvalidKeyException, IOException, NoSuchAlgorithmException,
CertificateException, InvalidKeySpecException {
SignOrProduceMacForToken signer = new SignOrProduceMacForToken("RS256",
ofInstance(OAuthCredentialsFromPKTest
.loadOAuthCredentials()));
signer.loadSignatureOrMacOrNone();
byte[] payloadSignature = signer.apply(PAYLOAD.getBytes("UTF-8"));
assertNotNull(payloadSignature);
assertEquals(CryptoStreams.base64Url(payloadSignature), SHA256withRSA_PAYLOAD_SIGNATURE_RESULT);
}
}

View File

@ -0,0 +1,97 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.handlers;
import org.easymock.IArgumentMatcher;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.testng.annotations.Test;
import java.net.URI;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reportMatcher;
import static org.easymock.EasyMock.verify;
/**
* @author Adrian Cole
*/
@Test(groups = "unit")
public class OAuthErrorHandlerTest {
@Test
public void test409MakesIllegalStateException() {
assertCodeMakes(
"POST",
URI.create("http://oauth.org"),
409,
"HTTP/1.1 409 Conflict",
"\"{\"code\":\"InvalidState\",\"message\":\"An incompatible transition has already been queued for this" +
" resource\"}\"",
IllegalStateException.class);
}
private void assertCodeMakes(String method, URI uri, int statusCode, String message, String content,
Class<? extends Exception> expected) {
assertCodeMakes(method, uri, statusCode, message, "application/json", content, expected);
}
private void assertCodeMakes(String method, URI uri, int statusCode, String message, String contentType,
String content, Class<? extends Exception> expected) {
OAuthErrorHandler function = new OAuthErrorHandler();
HttpCommand command = createMock(HttpCommand.class);
HttpRequest request = HttpRequest.builder().method(method).endpoint(uri).build();
HttpResponse response = HttpResponse.builder().statusCode(statusCode).message(message).payload(content).build();
response.getPayload().getContentMetadata().setContentType(contentType);
expect(command.getCurrentRequest()).andReturn(request).atLeastOnce();
command.setException(classEq(expected));
replay(command);
function.handleError(command, response);
verify(command);
}
public static Exception classEq(final Class<? extends Exception> in) {
reportMatcher(new IArgumentMatcher() {
@Override
public void appendTo(StringBuffer buffer) {
buffer.append("classEq(");
buffer.append(in);
buffer.append(")");
}
@Override
public boolean matches(Object arg) {
return arg.getClass() == in;
}
});
return null;
}
}

View File

@ -0,0 +1,56 @@
/*
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.internal;
import com.google.common.base.Charsets;
import com.sun.jersey.core.util.Base64;
import org.jclouds.crypto.CryptoStreams;
import org.testng.annotations.Test;
import static org.testng.Assert.assertTrue;
/**
* Tests that the Base64 implementations used to Base64 encode the tokens are Url safe.
*
* @author David Alves
*/
@Test(groups = "unit")
public class Base64UrlSafeTest {
public static final String STRING_THAT_GENERATES_URL_UNSAFE_BASE64_ENCODING = "§1234567890'+±!\"#$%&/()" +
"=?*qwertyuiopº´WERTYUIOPªàsdfghjklç~ASDFGHJKLÇ^<zxcvbnm," +
".->ZXCVBNM;:_@€";
public void testJcloudsCoreBase64IsNotUrlSafe() {
String encoded = new String(Base64.encode(STRING_THAT_GENERATES_URL_UNSAFE_BASE64_ENCODING.getBytes(Charsets
.UTF_8)), Charsets.UTF_8);
assertTrue(encoded.contains("+"), encoded);
assertTrue(encoded.contains("/"), encoded);
}
public void testUsedBase64IsUrlSafe() {
String encoded = CryptoStreams.base64Url(
STRING_THAT_GENERATES_URL_UNSAFE_BASE64_ENCODING.getBytes(Charsets.UTF_8));
assertTrue(!encoded.contains("+"));
assertTrue(!encoded.contains("/"));
assertTrue(!encoded.endsWith("="));
}
}

View File

@ -0,0 +1,28 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.internal;
import org.jclouds.oauth.v2.OAuthApi;
/**
* @author Adrian Cole
*/
public class BaseOAuthApiExpectTest extends BaseOAuthExpectTest<OAuthApi> {
}

View File

@ -0,0 +1,71 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.internal;
import com.google.common.base.Ticker;
import com.google.common.reflect.TypeToken;
import org.jclouds.apis.BaseContextLiveTest;
import org.jclouds.oauth.v2.OAuthApi;
import org.jclouds.oauth.v2.OAuthApiMetadata;
import org.jclouds.oauth.v2.OAuthAsyncApi;
import org.jclouds.rest.RestContext;
import org.testng.annotations.Test;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.oauth.v2.OAuthTestUtils.setCredentialFromPemFile;
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
import static org.jclouds.oauth.v2.config.OAuthProperties.SCOPES;
import static org.jclouds.oauth.v2.config.OAuthProperties.SIGNATURE_OR_MAC_ALGORITHM;
/**
* @author David Alves
*/
@Test(groups = "live")
public class BaseOAuthApiLiveTest extends BaseContextLiveTest<RestContext<OAuthApi, OAuthAsyncApi>> {
public BaseOAuthApiLiveTest() {
provider = "oauth";
}
@Override
protected TypeToken<RestContext<OAuthApi, OAuthAsyncApi>> contextType() {
return OAuthApiMetadata.CONTEXT_TOKEN;
}
@Override
protected Properties setupProperties() {
Properties props = super.setupProperties();
setCredentialFromPemFile(props, "oauth.credential");
checkNotNull(setIfTestSystemPropertyPresent(props, "oauth.endpoint"), "test.oauth.endpoint must be set");
checkNotNull(setIfTestSystemPropertyPresent(props, AUDIENCE), "test.jclouds.oauth.audience must be set");
setIfTestSystemPropertyPresent(props, SCOPES);
setIfTestSystemPropertyPresent(props, SIGNATURE_OR_MAC_ALGORITHM);
return props;
}
protected long nowInSeconds() {
return TimeUnit.SECONDS.convert(Ticker.systemTicker().read(), TimeUnit.NANOSECONDS);
}
}

View File

@ -0,0 +1,36 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.internal;
import com.google.common.base.Function;
import com.google.inject.Module;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.oauth.v2.OAuthAsyncApi;
import java.util.Properties;
/**
* @author Adrian Cole
*/
public class BaseOAuthAsyncApiExpectTest extends BaseOAuthExpectTest<OAuthAsyncApi> {
public OAuthAsyncApi createClient(Function<HttpRequest, HttpResponse> fn, Module module, Properties props) {
return createInjector(fn, module, props).getInstance(OAuthAsyncApi.class);
}
}

View File

@ -0,0 +1,31 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.internal;
import org.jclouds.rest.internal.BaseRestApiExpectTest;
/**
* @author Adrian Cole
*/
public class BaseOAuthExpectTest<T> extends BaseRestApiExpectTest<T> {
public BaseOAuthExpectTest() {
provider = "oauth";
}
}

View File

@ -0,0 +1,73 @@
/*
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.json;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import org.jclouds.ContextBuilder;
import org.jclouds.http.HttpRequest;
import org.jclouds.oauth.v2.OAuthApiMetadata;
import org.jclouds.oauth.v2.OAuthTestUtils;
import org.jclouds.oauth.v2.domain.ClaimSet;
import org.jclouds.oauth.v2.domain.Header;
import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.oauth.v2.domain.TokenRequestFormat;
import org.jclouds.util.Strings2;
import org.testng.annotations.Test;
import java.io.IOException;
import static org.jclouds.oauth.v2.internal.Base64UrlSafeTest.STRING_THAT_GENERATES_URL_UNSAFE_BASE64_ENCODING;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertSame;
import static org.testng.Assert.assertTrue;
/**
* @author David Alves
*/
@Test(groups = "unit")
public class JWTTokenRequestFormatTest {
public void testPayloadIsUrlSafe() throws IOException {
TokenRequestFormat tokenRequestFormat = ContextBuilder.newBuilder(new OAuthApiMetadata()).overrides
(OAuthTestUtils.defaultProperties(null)).build().utils()
.injector().getInstance(TokenRequestFormat.class);
Header header = new Header.Builder().signerAlgorithm("a").type("b").build();
ClaimSet claimSet = new ClaimSet.Builder().addClaim("ist", STRING_THAT_GENERATES_URL_UNSAFE_BASE64_ENCODING)
.build();
TokenRequest tokenRequest = new TokenRequest.Builder().claimSet(claimSet).header(header).build();
HttpRequest request = tokenRequestFormat.formatRequest(HttpRequest.builder().method("GET").endpoint
("http://localhost").build(), tokenRequest);
assertNotNull(request.getPayload());
String payload = Strings2.toStringAndClose(request.getPayload().getInput());
// make sure the paylod is in the format {header}.{claims}.{signature}
Iterable<String> parts = Splitter.on(".").split(payload);
assertSame(Iterables.size(parts), 3);
assertTrue(!payload.contains("+"));
assertTrue(!payload.contains("/"));
}
}

View File

@ -0,0 +1,46 @@
/*
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.parse;
import org.jclouds.json.BaseItemParserTest;
import org.jclouds.oauth.v2.domain.Token;
import org.testng.annotations.Test;
import javax.ws.rs.Consumes;
import javax.ws.rs.core.MediaType;
/**
* @author David Alves
*/
@Test(groups = "unit")
public class ParseTokenTest extends BaseItemParserTest<Token> {
@Override
public String resource() {
return "/tokenResponse.json";
}
@Override
@Consumes(MediaType.APPLICATION_JSON)
public Token expected() {
return Token.builder().expiresIn(3600).tokenType("Bearer").accessToken
("1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M").build();
}
}

View File

@ -0,0 +1,38 @@
<?xml version="1.0"?>
<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-----

View File

@ -0,0 +1,5 @@
{
"access_token" : "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M",
"token_type" : "Bearer",
"expires_in" : 3600
}

View File

@ -18,8 +18,7 @@
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">
--><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>
<artifactId>jclouds-project</artifactId>
@ -61,5 +60,6 @@
<module>fgcp-au</module>
<module>fgcp-de</module>
<module>abiquo</module>
</modules>
<module>oauth</module>
</modules>
</project>