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;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import java.io.Closeable;
import javax.inject.Named;
import javax.ws.rs.Consumes;
import javax.ws.rs.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.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;
@ -55,6 +56,6 @@ public interface OAuthApi extends Closeable {
*/
@Named("authenticate")
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
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
* 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.Joiner.on;
import static com.google.common.io.BaseEncoding.base64Url;
import static org.jclouds.io.Payloads.newUrlEncodedFormPayload;
import java.util.Set;
import javax.inject.Inject;
import org.jclouds.http.HttpRequest;
import org.jclouds.io.Payload;
import org.jclouds.json.Json;
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.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
/**
* 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/>
* <ol>
* <li>Transforms the token request to json.</li>
* <li>Creates the base64 header.claimset portions of the payload.</li>
* <li>Uses the provided signer function to create a signature.</li>
* <li>Creates the full url encoded payload as described in: <a href="https://developers.google.com/accounts/docs/OAuth2ServiceAccount">OAuth2ServiceAccount</a></li>
* </ol>
*/
public class JWTTokenRequestFormat implements TokenRequestFormat {
public final class OAuthTokenBinder implements Binder {
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";
@ -53,13 +49,13 @@ public class JWTTokenRequestFormat implements TokenRequestFormat {
private final Function<byte[], byte[]> signer;
private final Json json;
@Inject JWTTokenRequestFormat(Function<byte[], byte[]> signer, Json json) {
@Inject OAuthTokenBinder(Function<byte[], byte[]> signer, Json json) {
this.signer = signer;
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 encodedClaimSet = json.toJson(tokenRequest.claimSet());
@ -72,18 +68,9 @@ public class JWTTokenRequestFormat implements TokenRequestFormat {
// the final assertion in base 64 encoded {header}.{claimSet}.{signature} format
String assertion = on(".").join(encodedHeader, encodedClaimSet, encodedSignature);
Payload payload = newUrlEncodedFormPayload(ImmutableMultimap.<String, String> builder()
.put(GRANT_TYPE_FORM_PARAM, GRANT_TYPE_JWT_BEARER)
.put(ASSERTION_FORM_PARAM, assertion).build());
.put(GRANT_TYPE_FORM_PARAM, GRANT_TYPE_JWT_BEARER)
.put(ASSERTION_FORM_PARAM, assertion).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;
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.HttpRequest;
import org.jclouds.oauth.v2.domain.OAuthCredentials;
import javax.inject.Inject;
import javax.inject.Singleton;
import com.google.common.base.Supplier;
@Singleton
public class BearerTokenAuthenticator implements OAuthAuthenticationFilter {
public final class BearerTokenAuthenticator implements OAuthAuthenticationFilter {
private final Supplier<OAuthCredentials> creds;
@Inject
BearerTokenAuthenticator(final Supplier<OAuthCredentials> creds) {
@Inject BearerTokenAuthenticator(Supplier<OAuthCredentials> creds) {
this.creds = creds;
}
@Override
public HttpRequest filter(HttpRequest request) throws HttpException {
return request.toBuilder().addHeader("Authorization", String.format("%s %s",
"Bearer ", creds.get().credential)).build();
@Override public HttpRequest filter(HttpRequest request) throws HttpException {
return request.toBuilder().addHeader("Authorization", format("%s %s", "Bearer ", creds.get().credential)).build();
}
}

View File

@ -16,47 +16,41 @@
*/
package org.jclouds.oauth.v2.filters;
import com.google.common.base.Function;
import com.google.common.cache.LoadingCache;
import static com.google.common.base.Preconditions.checkState;
import javax.inject.Inject;
import org.jclouds.http.HttpException;
import org.jclouds.http.HttpRequest;
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;
import com.google.common.base.Function;
import com.google.common.cache.LoadingCache;
/**
* 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
*/
@Singleton
public class OAuthAuthenticator implements OAuthAuthenticationFilter {
public final class OAuthAuthenticator implements OAuthAuthenticationFilter {
private Function<GeneratedHttpRequest, TokenRequest> tokenRequestBuilder;
private Function<TokenRequest, Token> tokenFetcher;
@Inject
OAuthAuthenticator(Function<GeneratedHttpRequest, TokenRequest> tokenRequestBuilder, LoadingCache<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 {
@Override public HttpRequest filter(HttpRequest request) throws HttpException {
checkState(request instanceof GeneratedHttpRequest, "request must be an instance of GeneratedHttpRequest");
GeneratedHttpRequest generatedHttpRequest = GeneratedHttpRequest.class.cast(request);
TokenRequest tokenRequest = tokenRequestBuilder.apply(generatedHttpRequest);
Token token = tokenFetcher.apply(tokenRequest);
return request.toBuilder().addHeader("Authorization", String.format("%s %s",
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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Singleton;
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 com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.reflect.Invokable;
import com.google.inject.Inject;
import com.google.inject.name.Named;
@ -51,50 +50,47 @@ import com.google.inject.name.Named;
* <p/>
* TODO scopes etc should come from the REST method and not from a global property
*/
@Singleton
public class BuildTokenRequest implements Function<GeneratedHttpRequest, TokenRequest> {
public final 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 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 = Collections.emptyMap();
private Map<String, String> additionalClaims = Collections.emptyMap();
@Inject(optional = true)
@Named(SCOPES)
protected String globalScopes = null;
private String globalScopes = null;
// injectable so expect tests can override with a predictable value
@Inject(optional = true)
protected Supplier<Long> timeSourceMillisSinceEpoch = new Supplier<Long>() {
private Supplier<Long> timeSourceMillisSinceEpoch = new Supplier<Long>() {
@Override
public Long get() {
return System.currentTimeMillis();
}
};
@Inject
public BuildTokenRequest(@Named(AUDIENCE) String assertionTargetDescription,
@Inject BuildTokenRequest(@Named(AUDIENCE) String assertionTargetDescription,
@Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureAlgorithm,
TokenRequestFormat tokenRequestFormat, Supplier<OAuthCredentials> credentialsSupplier,
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) {
@Override public TokenRequest apply(GeneratedHttpRequest request) {
long now = timeSourceMillisSinceEpoch.get() / 1000;
// fetch the token
Header header = Header.create(signatureAlgorithm, tokenRequestFormat.type());
Header header = Header.create(signatureAlgorithm, "JWT");
Map<String, String> claims = new LinkedHashMap<String, String>();
claims.put("iss", credentialsSupplier.get().identity);
@ -102,7 +98,7 @@ public class BuildTokenRequest implements Function<GeneratedHttpRequest, TokenRe
claims.put("aud", assertionTargetDescription);
claims.putAll(additionalClaims);
checkState(claims.keySet().containsAll(tokenRequestFormat.requiredClaims()),
checkState(claims.keySet().containsAll(REQUIRED_CLAIMS),
"not all required claims were present");
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);
}
protected String getOAuthScopes(GeneratedHttpRequest request) {
private String getOAuthScopes(GeneratedHttpRequest request) {
Invokable<?, ?> invokable = request.getInvocation().getInvokable();
OAuthScopes classScopes = invokable.getOwnerType().getRawType().getAnnotation(OAuthScopes.class);

View File

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

View File

@ -16,27 +16,6 @@
*/
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.checkNotNull;
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.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
* 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
*/
@Singleton
public class OAuthCredentialsSupplier implements Supplier<OAuthCredentials> {
@Singleton // due to cache
public final class OAuthCredentialsSupplier implements Supplier<OAuthCredentials> {
private final Supplier<Credentials> creds;
private final LoadingCache<Credentials, OAuthCredentials> keyCache;
@Inject
public OAuthCredentialsSupplier(@Provider Supplier<Credentials> creds, OAuthCredentialsForCredentials loader,
@Inject OAuthCredentialsSupplier(@Provider Supplier<Credentials> creds, OAuthCredentialsForCredentials loader,
@Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureOrMacAlgorithm) {
this.creds = creds;
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.
*/
@VisibleForTesting
static class OAuthCredentialsForCredentials extends CacheLoader<Credentials, OAuthCredentials> {
static final class OAuthCredentialsForCredentials extends CacheLoader<Credentials, OAuthCredentials> {
private final String keyFactoryAlgorithm;
@Inject
public OAuthCredentialsForCredentials(@Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureOrMacAlgorithm) {
@Inject OAuthCredentialsForCredentials(@Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureOrMacAlgorithm) {
this.keyFactoryAlgorithm = OAUTH_ALGORITHM_NAMES_TO_KEYFACTORY_ALGORITHM_NAMES.get(checkNotNull(
signatureOrMacAlgorithm, "signatureOrMacAlgorithm"));
}
@Override
public OAuthCredentials load(Credentials in) {
@Override public OAuthCredentials load(Credentials in) {
try {
String identity = in.identity;
String privateKeyInPemFormat = in.credential;
@ -108,18 +107,16 @@ public class OAuthCredentialsSupplier implements Supplier<OAuthCredentials> {
}
}
@Override
public OAuthCredentials get() {
@Override public OAuthCredentials get() {
try {
// loader always throws UncheckedExecutionException so no point in using get()
return keyCache.getUnchecked(checkNotNull(creds.get(), "credential supplier returned null"));
} catch (UncheckedExecutionException e) {
Throwable authorizationException = getFirstThrowableOfType(e, AuthorizationException.class);
AuthorizationException authorizationException = getFirstThrowableOfType(e, AuthorizationException.class);
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;
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 static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Throwables.propagate;
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;
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;
import javax.annotation.PostConstruct;
import javax.crypto.Mac;
import javax.inject.Inject;
import javax.inject.Named;
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
* {@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 String signatureOrMacAlgorithm;
private Function<byte[], byte[]> signatureOrMacFunction;
@Inject
public SignOrProduceMacForToken(@Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureOrMacAlgorithm,
@Inject 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));
@ -74,14 +76,13 @@ public class SignOrProduceMacForToken implements Function<byte[], byte[]> {
}
}
@Override
public byte[] apply(byte[] input) {
@Override public byte[] apply(byte[] input) {
return signatureOrMacFunction.apply(input);
}
private static class MessageAuthenticationCodeGenerator implements Function<byte[], byte[]> {
private Mac mac;
private final Mac mac;
private MessageAuthenticationCodeGenerator(String macAlgorithm, PrivateKey privateKey) throws
NoSuchAlgorithmException, InvalidKeyException {
@ -89,8 +90,7 @@ public class SignOrProduceMacForToken implements Function<byte[], byte[]> {
this.mac.init(privateKey);
}
@Override
public byte[] apply(byte[] input) {
@Override public byte[] apply(byte[] input) {
this.mac.update(input);
return this.mac.doFinal();
}
@ -98,7 +98,7 @@ public class SignOrProduceMacForToken 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,
InvalidKeyException {
@ -106,13 +106,12 @@ public class SignOrProduceMacForToken implements Function<byte[], byte[]> {
this.signature.initSign(privateKey);
}
@Override
public byte[] apply(byte[] input) {
@Override public byte[] apply(byte[] input) {
try {
signature.update(input);
return signature.sign();
} catch (SignatureException e) {
throw Throwables.propagate(e);
throw propagate(e);
}
}
}

View File

@ -16,6 +16,9 @@
*/
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.HttpErrorHandler;
import org.jclouds.http.HttpResponse;
@ -23,17 +26,8 @@ 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.
*/
@Singleton
public class OAuthErrorHandler implements HttpErrorHandler {
public void handleError(HttpCommand command, HttpResponse response) {
public final class OAuthErrorHandler implements HttpErrorHandler {
@Override 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;

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
* 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.assertSame;
@ -29,7 +29,6 @@ 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;
@ -37,23 +36,23 @@ import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
@Test(groups = "unit")
public class JWTTokenRequestFormatTest {
@Test(groups = "unit", testName = "OAuthTokenBinderTest")
public class OAuthTokenBinderTest {
public static final String STRING_THAT_GENERATES_URL_UNSAFE_BASE64_ENCODING = "§1234567890'+±!\"#$%&/()" +
"=?*qwertyuiopº´WERTYUIOPªàsdfghjklç~ASDFGHJKLÇ^<zxcvbnm," +
".->ZXCVBNM;:_@€";
public void testPayloadIsUrlSafe() throws IOException {
TokenRequestFormat tokenRequestFormat = ContextBuilder.newBuilder(new OAuthApiMetadata()).overrides
OAuthTokenBinder tokenRequestFormat = ContextBuilder.newBuilder(new OAuthApiMetadata()).overrides
(OAuthTestUtils.defaultProperties(null)).build().utils()
.injector().getInstance(TokenRequestFormat.class);
.injector().getInstance(OAuthTokenBinder.class);
Header header = Header.create("a", "b");
ClaimSet claimSet = ClaimSet.create(0, 0,
ImmutableMap.of("ist", STRING_THAT_GENERATES_URL_UNSAFE_BASE64_ENCODING));
TokenRequest tokenRequest = TokenRequest.create(header, claimSet);
HttpRequest request = tokenRequestFormat.formatRequest(HttpRequest.builder().method("GET").endpoint
("http://localhost").build(), tokenRequest);
HttpRequest request = tokenRequestFormat.bindToRequest(
HttpRequest.builder().method("GET").endpoint("http://localhost").build(), tokenRequest);
assertNotNull(request.getPayload());