Decomplicate OAuth a little.

This commit is contained in:
Adrian Cole 2014-10-30 21:05:23 -07:00 committed by Adrian Cole
parent cd8aeed16d
commit 35156560dc
13 changed files with 129 additions and 299 deletions

View File

@ -16,17 +16,18 @@
*/ */
package org.jclouds.oauth.v2; package org.jclouds.oauth.v2;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import java.io.Closeable; import java.io.Closeable;
import javax.inject.Named; import javax.inject.Named;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.core.MediaType;
import org.jclouds.oauth.v2.binders.OAuthTokenBinder;
import org.jclouds.oauth.v2.config.Authentication; import org.jclouds.oauth.v2.config.Authentication;
import org.jclouds.oauth.v2.domain.Token; import org.jclouds.oauth.v2.domain.Token;
import org.jclouds.oauth.v2.domain.TokenRequest; import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.oauth.v2.handlers.OAuthTokenBinder;
import org.jclouds.rest.AuthorizationException; import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.annotations.BinderParam; import org.jclouds.rest.annotations.BinderParam;
import org.jclouds.rest.annotations.Endpoint; import org.jclouds.rest.annotations.Endpoint;
@ -55,6 +56,6 @@ public interface OAuthApi extends Closeable {
*/ */
@Named("authenticate") @Named("authenticate")
@POST @POST
@Consumes(MediaType.APPLICATION_JSON) @Consumes(APPLICATION_JSON)
Token authenticate(@BinderParam(OAuthTokenBinder.class) TokenRequest tokenRequest) throws AuthorizationException; Token authenticate(@BinderParam(OAuthTokenBinder.class) TokenRequest tokenRequest) throws AuthorizationException;
} }

View File

@ -14,38 +14,34 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jclouds.oauth.v2.json; package org.jclouds.oauth.v2.binders;
import static com.google.common.base.Charsets.UTF_8; import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Joiner.on; import static com.google.common.base.Joiner.on;
import static com.google.common.io.BaseEncoding.base64Url; import static com.google.common.io.BaseEncoding.base64Url;
import static org.jclouds.io.Payloads.newUrlEncodedFormPayload; import static org.jclouds.io.Payloads.newUrlEncodedFormPayload;
import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import org.jclouds.json.Json; import org.jclouds.json.Json;
import org.jclouds.oauth.v2.domain.TokenRequest; import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.oauth.v2.domain.TokenRequestFormat; import org.jclouds.rest.Binder;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
/** /**
* Formats a token request into JWT format namely: * Formats a token request into JWT format namely:
* - transforms the token request to json * <ol>
* - creates the base64 header.claimset portions of the payload. * <li>Transforms the token request to json.</li>
* - uses the provided signer function to create a signature * <li>Creates the base64 header.claimset portions of the payload.</li>
* - creates the full url encoded payload as described in: * <li>Uses the provided signer function to create a signature.</li>
* https://developers.google.com/accounts/docs/OAuth2ServiceAccount * <li>Creates the full url encoded payload as described in: <a href="https://developers.google.com/accounts/docs/OAuth2ServiceAccount">OAuth2ServiceAccount</a></li>
* <p/> * </ol>
*/ */
public class JWTTokenRequestFormat implements TokenRequestFormat { public final class OAuthTokenBinder implements Binder {
private static final String ASSERTION_FORM_PARAM = "assertion"; private static final String ASSERTION_FORM_PARAM = "assertion";
private static final String GRANT_TYPE_FORM_PARAM = "grant_type"; 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 static final String GRANT_TYPE_JWT_BEARER = "urn:ietf:params:oauth:grant-type:jwt-bearer";
@ -53,13 +49,13 @@ public class JWTTokenRequestFormat implements TokenRequestFormat {
private final Function<byte[], byte[]> signer; private final Function<byte[], byte[]> signer;
private final Json json; private final Json json;
@Inject JWTTokenRequestFormat(Function<byte[], byte[]> signer, Json json) { @Inject OAuthTokenBinder(Function<byte[], byte[]> signer, Json json) {
this.signer = signer; this.signer = signer;
this.json = json; this.json = json;
} }
@Override public <R extends HttpRequest> R formatRequest(R request, TokenRequest tokenRequest) { @Override public <R extends HttpRequest> R bindToRequest(R request, Object input) {
TokenRequest tokenRequest = (TokenRequest) input;
String encodedHeader = json.toJson(tokenRequest.header()); String encodedHeader = json.toJson(tokenRequest.header());
String encodedClaimSet = json.toJson(tokenRequest.claimSet()); String encodedClaimSet = json.toJson(tokenRequest.claimSet());
@ -77,13 +73,4 @@ public class JWTTokenRequestFormat implements TokenRequestFormat {
return (R) request.toBuilder().payload(payload).build(); return (R) request.toBuilder().payload(payload).build();
} }
@Override public String type() {
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

@ -1,45 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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)
*/
@ImplementedBy(JWTTokenRequestFormat.class)
public interface TokenRequestFormat {
/**
* Transforms the provided HttpRequest into a particular token request with a specific format.
*/
<R extends HttpRequest> R formatRequest(R httpRequest, TokenRequest tokenRequest);
/**
* The name of the type of the token request, e.g., "JWT"
*/
String type();
/**
* The claims that must be present in the token request for it to be valid.
*/
Set<String> requiredClaims();
}

View File

@ -16,26 +16,24 @@
*/ */
package org.jclouds.oauth.v2.filters; package org.jclouds.oauth.v2.filters;
import com.google.common.base.Supplier; import static java.lang.String.format;
import javax.inject.Inject;
import org.jclouds.http.HttpException; import org.jclouds.http.HttpException;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.oauth.v2.domain.OAuthCredentials; import org.jclouds.oauth.v2.domain.OAuthCredentials;
import javax.inject.Inject; import com.google.common.base.Supplier;
import javax.inject.Singleton;
@Singleton public final class BearerTokenAuthenticator implements OAuthAuthenticationFilter {
public class BearerTokenAuthenticator implements OAuthAuthenticationFilter {
private final Supplier<OAuthCredentials> creds; private final Supplier<OAuthCredentials> creds;
@Inject @Inject BearerTokenAuthenticator(Supplier<OAuthCredentials> creds) {
BearerTokenAuthenticator(final Supplier<OAuthCredentials> creds) {
this.creds = creds; this.creds = creds;
} }
@Override @Override public HttpRequest filter(HttpRequest request) throws HttpException {
public HttpRequest filter(HttpRequest request) throws HttpException { return request.toBuilder().addHeader("Authorization", format("%s %s", "Bearer ", creds.get().credential)).build();
return request.toBuilder().addHeader("Authorization", String.format("%s %s",
"Bearer ", creds.get().credential)).build();
} }
} }

View File

@ -16,47 +16,41 @@
*/ */
package org.jclouds.oauth.v2.filters; package org.jclouds.oauth.v2.filters;
import com.google.common.base.Function; import static com.google.common.base.Preconditions.checkState;
import com.google.common.cache.LoadingCache;
import javax.inject.Inject;
import org.jclouds.http.HttpException; import org.jclouds.http.HttpException;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.oauth.v2.domain.Token; import org.jclouds.oauth.v2.domain.Token;
import org.jclouds.oauth.v2.domain.TokenRequest; import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.rest.internal.GeneratedHttpRequest; import org.jclouds.rest.internal.GeneratedHttpRequest;
import javax.inject.Inject; import com.google.common.base.Function;
import javax.inject.Singleton; import com.google.common.cache.LoadingCache;
import static com.google.common.base.Preconditions.checkState;
/** /**
* To be used by client applications to embed an OAuth authentication in their REST requests. * To be used by client applications to embed an OAuth authentication in their REST requests.
* <p/> * <p/>
* TODO when we're able to use the OAuthAuthentication an this should be used automatically * TODO when we're able to use the OAuthAuthentication an this should be used automatically
*/ */
@Singleton public final class OAuthAuthenticator implements OAuthAuthenticationFilter {
public class OAuthAuthenticator implements OAuthAuthenticationFilter {
private Function<GeneratedHttpRequest, TokenRequest> tokenRequestBuilder; private Function<GeneratedHttpRequest, TokenRequest> tokenRequestBuilder;
private Function<TokenRequest, Token> tokenFetcher; private Function<TokenRequest, Token> tokenFetcher;
@Inject @Inject OAuthAuthenticator(Function<GeneratedHttpRequest, TokenRequest> tokenRequestBuilder,
OAuthAuthenticator(Function<GeneratedHttpRequest, TokenRequest> tokenRequestBuilder, LoadingCache<TokenRequest, LoadingCache<TokenRequest, Token> tokenFetcher) {
Token> tokenFetcher) {
this.tokenRequestBuilder = tokenRequestBuilder; this.tokenRequestBuilder = tokenRequestBuilder;
this.tokenFetcher = tokenFetcher; this.tokenFetcher = tokenFetcher;
} }
@Override @Override public HttpRequest filter(HttpRequest request) throws HttpException {
public HttpRequest filter(HttpRequest request) throws HttpException {
checkState(request instanceof GeneratedHttpRequest, "request must be an instance of GeneratedHttpRequest"); checkState(request instanceof GeneratedHttpRequest, "request must be an instance of GeneratedHttpRequest");
GeneratedHttpRequest generatedHttpRequest = GeneratedHttpRequest.class.cast(request); GeneratedHttpRequest generatedHttpRequest = GeneratedHttpRequest.class.cast(request);
TokenRequest tokenRequest = tokenRequestBuilder.apply(generatedHttpRequest); TokenRequest tokenRequest = tokenRequestBuilder.apply(generatedHttpRequest);
Token token = tokenFetcher.apply(tokenRequest); Token token = tokenFetcher.apply(tokenRequest);
return request.toBuilder().addHeader("Authorization", String.format("%s %s", return request.toBuilder().addHeader("Authorization", String.format("%s %s",
token.tokenType(), token.accessToken())).build(); token.tokenType(), token.accessToken())).build();
} }
} }

View File

@ -24,22 +24,21 @@ import static org.jclouds.oauth.v2.config.OAuthProperties.SIGNATURE_OR_MAC_ALGOR
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import javax.inject.Singleton;
import org.jclouds.Constants; import org.jclouds.Constants;
import org.jclouds.oauth.v2.config.OAuthScopes; import org.jclouds.oauth.v2.config.OAuthScopes;
import org.jclouds.oauth.v2.domain.ClaimSet; import org.jclouds.oauth.v2.domain.ClaimSet;
import org.jclouds.oauth.v2.domain.Header; import org.jclouds.oauth.v2.domain.Header;
import org.jclouds.oauth.v2.domain.OAuthCredentials; import org.jclouds.oauth.v2.domain.OAuthCredentials;
import org.jclouds.oauth.v2.domain.TokenRequest; import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.oauth.v2.domain.TokenRequestFormat;
import org.jclouds.rest.internal.GeneratedHttpRequest; import org.jclouds.rest.internal.GeneratedHttpRequest;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.reflect.Invokable; import com.google.common.reflect.Invokable;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.name.Named; import com.google.inject.name.Named;
@ -51,50 +50,47 @@ import com.google.inject.name.Named;
* <p/> * <p/>
* TODO scopes etc should come from the REST method and not from a global property * TODO scopes etc should come from the REST method and not from a global property
*/ */
@Singleton public final class BuildTokenRequest implements Function<GeneratedHttpRequest, TokenRequest> {
public class BuildTokenRequest implements Function<GeneratedHttpRequest, TokenRequest> { // exp and ist (expiration and emission times) are assumed mandatory already
private static final List<String> REQUIRED_CLAIMS = ImmutableList.of("iss", "scope", "aud");
private final String assertionTargetDescription; private final String assertionTargetDescription;
private final String signatureAlgorithm; private final String signatureAlgorithm;
private final TokenRequestFormat tokenRequestFormat;
private final Supplier<OAuthCredentials> credentialsSupplier; private final Supplier<OAuthCredentials> credentialsSupplier;
private final long tokenDuration; private final long tokenDuration;
@Inject(optional = true) @Inject(optional = true)
@Named(ADDITIONAL_CLAIMS) @Named(ADDITIONAL_CLAIMS)
protected Map<String, String> additionalClaims = Collections.emptyMap(); private Map<String, String> additionalClaims = Collections.emptyMap();
@Inject(optional = true) @Inject(optional = true)
@Named(SCOPES) @Named(SCOPES)
protected String globalScopes = null; private String globalScopes = null;
// injectable so expect tests can override with a predictable value // injectable so expect tests can override with a predictable value
@Inject(optional = true) @Inject(optional = true)
protected Supplier<Long> timeSourceMillisSinceEpoch = new Supplier<Long>() { private Supplier<Long> timeSourceMillisSinceEpoch = new Supplier<Long>() {
@Override @Override
public Long get() { public Long get() {
return System.currentTimeMillis(); return System.currentTimeMillis();
} }
}; };
@Inject @Inject BuildTokenRequest(@Named(AUDIENCE) String assertionTargetDescription,
public BuildTokenRequest(@Named(AUDIENCE) String assertionTargetDescription,
@Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureAlgorithm, @Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureAlgorithm,
TokenRequestFormat tokenRequestFormat, Supplier<OAuthCredentials> credentialsSupplier, Supplier<OAuthCredentials> credentialsSupplier,
@Named(Constants.PROPERTY_SESSION_INTERVAL) long tokenDuration) { @Named(Constants.PROPERTY_SESSION_INTERVAL) long tokenDuration) {
this.assertionTargetDescription = assertionTargetDescription; this.assertionTargetDescription = assertionTargetDescription;
this.signatureAlgorithm = signatureAlgorithm; this.signatureAlgorithm = signatureAlgorithm;
this.tokenRequestFormat = tokenRequestFormat;
this.credentialsSupplier = credentialsSupplier; this.credentialsSupplier = credentialsSupplier;
this.tokenDuration = tokenDuration; this.tokenDuration = tokenDuration;
} }
@Override @Override public TokenRequest apply(GeneratedHttpRequest request) {
public TokenRequest apply(GeneratedHttpRequest request) {
long now = timeSourceMillisSinceEpoch.get() / 1000; long now = timeSourceMillisSinceEpoch.get() / 1000;
// fetch the token // fetch the token
Header header = Header.create(signatureAlgorithm, tokenRequestFormat.type()); Header header = Header.create(signatureAlgorithm, "JWT");
Map<String, String> claims = new LinkedHashMap<String, String>(); Map<String, String> claims = new LinkedHashMap<String, String>();
claims.put("iss", credentialsSupplier.get().identity); claims.put("iss", credentialsSupplier.get().identity);
@ -102,7 +98,7 @@ public class BuildTokenRequest implements Function<GeneratedHttpRequest, TokenRe
claims.put("aud", assertionTargetDescription); claims.put("aud", assertionTargetDescription);
claims.putAll(additionalClaims); claims.putAll(additionalClaims);
checkState(claims.keySet().containsAll(tokenRequestFormat.requiredClaims()), checkState(claims.keySet().containsAll(REQUIRED_CLAIMS),
"not all required claims were present"); "not all required claims were present");
ClaimSet claimSet = ClaimSet.create(now, now + tokenDuration, Collections.unmodifiableMap(claims)); ClaimSet claimSet = ClaimSet.create(now, now + tokenDuration, Collections.unmodifiableMap(claims));
@ -110,7 +106,7 @@ public class BuildTokenRequest implements Function<GeneratedHttpRequest, TokenRe
return TokenRequest.create(header, claimSet); return TokenRequest.create(header, claimSet);
} }
protected String getOAuthScopes(GeneratedHttpRequest request) { private String getOAuthScopes(GeneratedHttpRequest request) {
Invokable<?, ?> invokable = request.getInvocation().getInvokable(); Invokable<?, ?> invokable = request.getInvocation().getInvokable();
OAuthScopes classScopes = invokable.getOwnerType().getRawType().getAnnotation(OAuthScopes.class); OAuthScopes classScopes = invokable.getOwnerType().getRawType().getAnnotation(OAuthScopes.class);

View File

@ -16,26 +16,23 @@
*/ */
package org.jclouds.oauth.v2.functions; package org.jclouds.oauth.v2.functions;
import com.google.common.base.Function; import javax.inject.Inject;
import org.jclouds.oauth.v2.OAuthApi; import org.jclouds.oauth.v2.OAuthApi;
import org.jclouds.oauth.v2.domain.Token; import org.jclouds.oauth.v2.domain.Token;
import org.jclouds.oauth.v2.domain.TokenRequest; import org.jclouds.oauth.v2.domain.TokenRequest;
import javax.inject.Inject; import com.google.common.base.Function;
import javax.inject.Singleton;
@Singleton public final class FetchToken implements Function<TokenRequest, Token> {
public class FetchToken implements Function<TokenRequest, Token> {
private OAuthApi oAuthApi; private final OAuthApi oAuthApi;
@Inject @Inject FetchToken(OAuthApi oAuthApi) {
public FetchToken(OAuthApi oAuthApi) {
this.oAuthApi = oAuthApi; this.oAuthApi = oAuthApi;
} }
@Override @Override public Token apply(TokenRequest input) {
public Token apply(TokenRequest input) {
return this.oAuthApi.authenticate(input); return this.oAuthApi.authenticate(input);
} }
} }

View File

@ -16,27 +16,6 @@
*/ */
package org.jclouds.oauth.v2.functions; package org.jclouds.oauth.v2.functions;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.base.Supplier;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.io.ByteSource;
import com.google.common.util.concurrent.UncheckedExecutionException;
import org.jclouds.domain.Credentials;
import org.jclouds.location.Provider;
import org.jclouds.oauth.v2.domain.OAuthCredentials;
import org.jclouds.rest.AuthorizationException;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.propagate; import static com.google.common.base.Throwables.propagate;
@ -47,20 +26,42 @@ import static org.jclouds.oauth.v2.OAuthConstants.OAUTH_ALGORITHM_NAMES_TO_KEYFA
import static org.jclouds.oauth.v2.config.OAuthProperties.SIGNATURE_OR_MAC_ALGORITHM; import static org.jclouds.oauth.v2.config.OAuthProperties.SIGNATURE_OR_MAC_ALGORITHM;
import static org.jclouds.util.Throwables2.getFirstThrowableOfType; import static org.jclouds.util.Throwables2.getFirstThrowableOfType;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.domain.Credentials;
import org.jclouds.location.Provider;
import org.jclouds.oauth.v2.domain.OAuthCredentials;
import org.jclouds.rest.AuthorizationException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.base.Supplier;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.io.ByteSource;
import com.google.common.util.concurrent.UncheckedExecutionException;
/** /**
* Loads {@link OAuthCredentials} from a pem private key using the KeyFactory obtained from the JWT 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. * Name<->KeyFactory name mapping in OAuthConstants. The pem pk algorithm must match the KeyFactory algorithm.
* *
* @see org.jclouds.oauth.v2.OAuthConstants#OAUTH_ALGORITHM_NAMES_TO_KEYFACTORY_ALGORITHM_NAMES * @see org.jclouds.oauth.v2.OAuthConstants#OAUTH_ALGORITHM_NAMES_TO_KEYFACTORY_ALGORITHM_NAMES
*/ */
@Singleton @Singleton // due to cache
public class OAuthCredentialsSupplier implements Supplier<OAuthCredentials> { public final class OAuthCredentialsSupplier implements Supplier<OAuthCredentials> {
private final Supplier<Credentials> creds; private final Supplier<Credentials> creds;
private final LoadingCache<Credentials, OAuthCredentials> keyCache; private final LoadingCache<Credentials, OAuthCredentials> keyCache;
@Inject @Inject OAuthCredentialsSupplier(@Provider Supplier<Credentials> creds, OAuthCredentialsForCredentials loader,
public OAuthCredentialsSupplier(@Provider Supplier<Credentials> creds, OAuthCredentialsForCredentials loader,
@Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureOrMacAlgorithm) { @Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureOrMacAlgorithm) {
this.creds = creds; this.creds = creds;
checkArgument(OAUTH_ALGORITHM_NAMES_TO_KEYFACTORY_ALGORITHM_NAMES.containsKey(signatureOrMacAlgorithm), checkArgument(OAUTH_ALGORITHM_NAMES_TO_KEYFACTORY_ALGORITHM_NAMES.containsKey(signatureOrMacAlgorithm),
@ -74,17 +75,15 @@ public class OAuthCredentialsSupplier implements Supplier<OAuthCredentials> {
* so that the private key is only recalculated once. * so that the private key is only recalculated once.
*/ */
@VisibleForTesting @VisibleForTesting
static class OAuthCredentialsForCredentials extends CacheLoader<Credentials, OAuthCredentials> { static final class OAuthCredentialsForCredentials extends CacheLoader<Credentials, OAuthCredentials> {
private final String keyFactoryAlgorithm; private final String keyFactoryAlgorithm;
@Inject @Inject OAuthCredentialsForCredentials(@Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureOrMacAlgorithm) {
public OAuthCredentialsForCredentials(@Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureOrMacAlgorithm) {
this.keyFactoryAlgorithm = OAUTH_ALGORITHM_NAMES_TO_KEYFACTORY_ALGORITHM_NAMES.get(checkNotNull( this.keyFactoryAlgorithm = OAUTH_ALGORITHM_NAMES_TO_KEYFACTORY_ALGORITHM_NAMES.get(checkNotNull(
signatureOrMacAlgorithm, "signatureOrMacAlgorithm")); signatureOrMacAlgorithm, "signatureOrMacAlgorithm"));
} }
@Override @Override public OAuthCredentials load(Credentials in) {
public OAuthCredentials load(Credentials in) {
try { try {
String identity = in.identity; String identity = in.identity;
String privateKeyInPemFormat = in.credential; String privateKeyInPemFormat = in.credential;
@ -108,18 +107,16 @@ public class OAuthCredentialsSupplier implements Supplier<OAuthCredentials> {
} }
} }
@Override @Override public OAuthCredentials get() {
public OAuthCredentials get() {
try { try {
// loader always throws UncheckedExecutionException so no point in using get() // loader always throws UncheckedExecutionException so no point in using get()
return keyCache.getUnchecked(checkNotNull(creds.get(), "credential supplier returned null")); return keyCache.getUnchecked(checkNotNull(creds.get(), "credential supplier returned null"));
} catch (UncheckedExecutionException e) { } catch (UncheckedExecutionException e) {
Throwable authorizationException = getFirstThrowableOfType(e, AuthorizationException.class); AuthorizationException authorizationException = getFirstThrowableOfType(e, AuthorizationException.class);
if (authorizationException != null) { if (authorizationException != null) {
throw (AuthorizationException) authorizationException; throw authorizationException;
} }
throw propagate(e); throw e;
} }
} }
} }

View File

@ -16,40 +16,42 @@
*/ */
package org.jclouds.oauth.v2.functions; package org.jclouds.oauth.v2.functions;
import com.google.common.base.Function; import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Supplier; import static com.google.common.base.Throwables.propagate;
import com.google.common.base.Throwables; import static java.lang.String.format;
import org.jclouds.oauth.v2.domain.OAuthCredentials; 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;
import javax.annotation.PostConstruct;
import javax.crypto.Mac;
import javax.inject.Inject;
import javax.inject.Named;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.Signature; import java.security.Signature;
import java.security.SignatureException; import java.security.SignatureException;
import static com.google.common.base.Preconditions.checkState; import javax.annotation.PostConstruct;
import static java.lang.String.format; import javax.crypto.Mac;
import static org.jclouds.oauth.v2.OAuthConstants.NO_ALGORITHM; import javax.inject.Inject;
import static org.jclouds.oauth.v2.OAuthConstants.OAUTH_ALGORITHM_NAMES_TO_SIGNATURE_ALGORITHM_NAMES; import javax.inject.Named;
import static org.jclouds.oauth.v2.config.OAuthProperties.SIGNATURE_OR_MAC_ALGORITHM;
import org.jclouds.oauth.v2.domain.OAuthCredentials;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.inject.Singleton;
/** /**
* Function that signs/produces mac's for OAuth tokens, provided a {@link Signature} or a {@link Mac} algorithm and * Function that signs/produces mac's for OAuth tokens, provided a {@link Signature} or a {@link Mac} algorithm and
* {@link PrivateKey} * {@link PrivateKey}
*/ */
public class SignOrProduceMacForToken implements Function<byte[], byte[]> { @Singleton // due to signatureOrMacFunction
public final class SignOrProduceMacForToken implements Function<byte[], byte[]> {
private final Supplier<OAuthCredentials> credentials; private final Supplier<OAuthCredentials> credentials;
private final String signatureOrMacAlgorithm; private final String signatureOrMacAlgorithm;
private Function<byte[], byte[]> signatureOrMacFunction; private Function<byte[], byte[]> signatureOrMacFunction;
@Inject SignOrProduceMacForToken(@Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureOrMacAlgorithm,
@Inject
public SignOrProduceMacForToken(@Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureOrMacAlgorithm,
Supplier<OAuthCredentials> credentials) { Supplier<OAuthCredentials> credentials) {
checkState(OAUTH_ALGORITHM_NAMES_TO_SIGNATURE_ALGORITHM_NAMES.containsKey(signatureOrMacAlgorithm), checkState(OAUTH_ALGORITHM_NAMES_TO_SIGNATURE_ALGORITHM_NAMES.containsKey(signatureOrMacAlgorithm),
format("the signature algorithm %s is not supported", signatureOrMacAlgorithm)); format("the signature algorithm %s is not supported", signatureOrMacAlgorithm));
@ -74,14 +76,13 @@ public class SignOrProduceMacForToken implements Function<byte[], byte[]> {
} }
} }
@Override @Override public byte[] apply(byte[] input) {
public byte[] apply(byte[] input) {
return signatureOrMacFunction.apply(input); return signatureOrMacFunction.apply(input);
} }
private static class MessageAuthenticationCodeGenerator implements Function<byte[], byte[]> { private static class MessageAuthenticationCodeGenerator implements Function<byte[], byte[]> {
private Mac mac; private final Mac mac;
private MessageAuthenticationCodeGenerator(String macAlgorithm, PrivateKey privateKey) throws private MessageAuthenticationCodeGenerator(String macAlgorithm, PrivateKey privateKey) throws
NoSuchAlgorithmException, InvalidKeyException { NoSuchAlgorithmException, InvalidKeyException {
@ -89,8 +90,7 @@ public class SignOrProduceMacForToken implements Function<byte[], byte[]> {
this.mac.init(privateKey); this.mac.init(privateKey);
} }
@Override @Override public byte[] apply(byte[] input) {
public byte[] apply(byte[] input) {
this.mac.update(input); this.mac.update(input);
return this.mac.doFinal(); return this.mac.doFinal();
} }
@ -98,7 +98,7 @@ public class SignOrProduceMacForToken implements Function<byte[], byte[]> {
private static class SignatureGenerator implements Function<byte[], byte[]> { private static class SignatureGenerator implements Function<byte[], byte[]> {
private Signature signature; private final Signature signature;
private SignatureGenerator(String signatureAlgorithm, PrivateKey privateKey) throws NoSuchAlgorithmException, private SignatureGenerator(String signatureAlgorithm, PrivateKey privateKey) throws NoSuchAlgorithmException,
InvalidKeyException { InvalidKeyException {
@ -106,13 +106,12 @@ public class SignOrProduceMacForToken implements Function<byte[], byte[]> {
this.signature.initSign(privateKey); this.signature.initSign(privateKey);
} }
@Override @Override public byte[] apply(byte[] input) {
public byte[] apply(byte[] input) {
try { try {
signature.update(input); signature.update(input);
return signature.sign(); return signature.sign();
} catch (SignatureException e) { } catch (SignatureException e) {
throw Throwables.propagate(e); throw propagate(e);
} }
} }
} }

View File

@ -16,6 +16,9 @@
*/ */
package org.jclouds.oauth.v2.handlers; package org.jclouds.oauth.v2.handlers;
import static javax.ws.rs.core.Response.Status;
import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
import org.jclouds.http.HttpCommand; import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpErrorHandler; import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
@ -23,17 +26,8 @@ import org.jclouds.http.HttpResponseException;
import org.jclouds.rest.AuthorizationException; import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.ResourceNotFoundException; import org.jclouds.rest.ResourceNotFoundException;
import javax.inject.Singleton; public final class OAuthErrorHandler implements HttpErrorHandler {
@Override public void handleError(HttpCommand command, HttpResponse response) {
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.
*/
@Singleton
public class OAuthErrorHandler implements HttpErrorHandler {
public void handleError(HttpCommand command, HttpResponse response) {
// it is important to always read fully and close streams // it is important to always read fully and close streams
byte[] data = closeClientButKeepContentStream(response); byte[] data = closeClientButKeepContentStream(response);
String message = data != null ? new String(data) : null; String message = data != null ? new String(data) : null;

View File

@ -1,45 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.
*/
@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

@ -1,42 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.oauth.v2.internal;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
/**
* Type adapter used to serialize all subtypes of a value. This can be used to force serialization for an {@link
* com.google.auto.value.AutoValue} generated class.
*/
public abstract class SubtypeAdapterFactory<T> extends TypeAdapter<T> implements TypeAdapterFactory {
private final Class<T> baseClass;
protected SubtypeAdapterFactory(Class<T> baseClass) {
this.baseClass = baseClass;
}
/** Accepts duty for any subtype. When using AutoValue properly, this will be the generated form. */
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
if (!(baseClass.isAssignableFrom(typeToken.getRawType()))) {
return null;
}
return (TypeAdapter<T>) this;
}
}

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jclouds.oauth.v2.json; package org.jclouds.oauth.v2.binders;
import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertSame; import static org.testng.Assert.assertSame;
@ -29,7 +29,6 @@ import org.jclouds.oauth.v2.OAuthTestUtils;
import org.jclouds.oauth.v2.domain.ClaimSet; import org.jclouds.oauth.v2.domain.ClaimSet;
import org.jclouds.oauth.v2.domain.Header; import org.jclouds.oauth.v2.domain.Header;
import org.jclouds.oauth.v2.domain.TokenRequest; import org.jclouds.oauth.v2.domain.TokenRequest;
import org.jclouds.oauth.v2.domain.TokenRequestFormat;
import org.jclouds.util.Strings2; import org.jclouds.util.Strings2;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -37,23 +36,23 @@ import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
@Test(groups = "unit") @Test(groups = "unit", testName = "OAuthTokenBinderTest")
public class JWTTokenRequestFormatTest { public class OAuthTokenBinderTest {
public static final String STRING_THAT_GENERATES_URL_UNSAFE_BASE64_ENCODING = "§1234567890'+±!\"#$%&/()" + public static final String STRING_THAT_GENERATES_URL_UNSAFE_BASE64_ENCODING = "§1234567890'+±!\"#$%&/()" +
"=?*qwertyuiopº´WERTYUIOPªàsdfghjklç~ASDFGHJKLÇ^<zxcvbnm," + "=?*qwertyuiopº´WERTYUIOPªàsdfghjklç~ASDFGHJKLÇ^<zxcvbnm," +
".->ZXCVBNM;:_@€"; ".->ZXCVBNM;:_@€";
public void testPayloadIsUrlSafe() throws IOException { public void testPayloadIsUrlSafe() throws IOException {
TokenRequestFormat tokenRequestFormat = ContextBuilder.newBuilder(new OAuthApiMetadata()).overrides OAuthTokenBinder tokenRequestFormat = ContextBuilder.newBuilder(new OAuthApiMetadata()).overrides
(OAuthTestUtils.defaultProperties(null)).build().utils() (OAuthTestUtils.defaultProperties(null)).build().utils()
.injector().getInstance(TokenRequestFormat.class); .injector().getInstance(OAuthTokenBinder.class);
Header header = Header.create("a", "b"); Header header = Header.create("a", "b");
ClaimSet claimSet = ClaimSet.create(0, 0, ClaimSet claimSet = ClaimSet.create(0, 0,
ImmutableMap.of("ist", STRING_THAT_GENERATES_URL_UNSAFE_BASE64_ENCODING)); ImmutableMap.of("ist", STRING_THAT_GENERATES_URL_UNSAFE_BASE64_ENCODING));
TokenRequest tokenRequest = TokenRequest.create(header, claimSet); TokenRequest tokenRequest = TokenRequest.create(header, claimSet);
HttpRequest request = tokenRequestFormat.formatRequest(HttpRequest.builder().method("GET").endpoint HttpRequest request = tokenRequestFormat.bindToRequest(
("http://localhost").build(), tokenRequest); HttpRequest.builder().method("GET").endpoint("http://localhost").build(), tokenRequest);
assertNotNull(request.getPayload()); assertNotNull(request.getPayload());