mirror of https://github.com/apache/jclouds.git
Decomplicate OAuth a little.
This commit is contained in:
parent
cd8aeed16d
commit
35156560dc
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
Loading…
Reference in New Issue