mirror of https://github.com/apache/jclouds.git
Add support for Azure AD authentication using Service Principal and Password
This commit is contained in:
parent
74a2a2683f
commit
0420741690
|
@ -1,16 +1,34 @@
|
|||
In order to use oauth applications must specify the following properties:
|
||||
|
||||
Mandatory:
|
||||
Mandatory, when using non-Azure Active Directory oauth:
|
||||
<myprovider>.identity - the oauth identity (e.g., service account email in Google Api's)
|
||||
<myprovider>.credential - the private key used to sign requests, in pem format
|
||||
oauth.endpoint - the endpoint to use for authentication (e.g., "http://accounts.google.com/o/oauth2/token" in Google Api's)
|
||||
oauth.audience - the "audience" of the token request (e.g., "http://accounts.google.com/o/oauth2/token" in Google Api's)
|
||||
|
||||
Running the live test:
|
||||
Mandatory, when using oauth to authenticate against Azure Active Directory:
|
||||
<myprovider>.identity - the application GUID set up for as a Service Principal for your Azure account
|
||||
<myprovider>.credential - the password associated with the application GUID
|
||||
oauth.endpoint - the endpoint to use for Azure AD authentication (URL of the form the URL "https://login.microsoftonline.com/<Tenant ID>/oauth2/token")
|
||||
|
||||
For Azure Active Directory, setting up a service principal to work with Azure Resource Manager and Azure AD as well as finding out the <Tenant ID> is described at https://azure.microsoft.com/en-us/documentation/articles/resource-group-authenticate-service-principal/
|
||||
|
||||
Running the live test on for non client_credentials oauth grant type:
|
||||
|
||||
mvn clean install -Plive \
|
||||
-Dtest.oauth.identity=<email addr> \
|
||||
-Dtest.oauth.credential=<private key> \
|
||||
-Dtest.oauth.endpoint=https://accounts.google.com/o/oauth2/token \
|
||||
-Dtest.jclouds.oauth.audience=https://accounts.google.com/o/oauth2/token \
|
||||
-Dtest.jclouds.oauth.scope=https://www.googleapis.com/auth/prediction \
|
||||
|
||||
|
||||
To Run the live test against Azure Active Directory which uses the client_credentials grant type:
|
||||
|
||||
mvn clean install -Plive \
|
||||
-Dtest.oauth.identity=<azure app id> \
|
||||
-Dtest.oauth.credential=<azure app password> \
|
||||
-Dtest.oauth.endpoint=https://login.microsoftonline.com/<tenant id>/oauth2/token \
|
||||
-Dtest.jclouds.oauth.resource=https://management.azure.com/ \
|
||||
-Dtest.jclouds.oauth.credential-type=clientCredentialsSecret
|
||||
|
||||
mvn clean install -Plive\
|
||||
-Dtest.oauth.identity=<accout email>\
|
||||
-Dtest.oauth.credential=<accout pk in pem format>\
|
||||
-Dtest.oauth.endpoint=https://accounts.google.com/o/oauth2/token\
|
||||
-Dtest.jclouds.oauth.audience=https://accounts.google.com/o/oauth2/token\
|
||||
-Dtest.jclouds.oauth.scope=https://www.googleapis.com/auth/prediction
|
|
@ -25,6 +25,7 @@ import javax.ws.rs.Consumes;
|
|||
import javax.ws.rs.FormParam;
|
||||
import javax.ws.rs.POST;
|
||||
|
||||
import org.jclouds.javax.annotation.Nullable;
|
||||
import org.jclouds.oauth.v2.OAuthFallbacks.AuthorizationExceptionOn4xx;
|
||||
import org.jclouds.oauth.v2.config.Authorization;
|
||||
import org.jclouds.oauth.v2.domain.Claims;
|
||||
|
@ -46,4 +47,16 @@ public interface AuthorizationApi extends Closeable {
|
|||
@Consumes(APPLICATION_JSON)
|
||||
@Fallback(AuthorizationExceptionOn4xx.class)
|
||||
Token authorize(@FormParam("assertion") @ParamParser(ClaimsToAssertion.class) Claims claims);
|
||||
|
||||
@Named("oauth2:authorize_client_secret")
|
||||
@POST
|
||||
@FormParams(keys = "grant_type", values = "client_credentials")
|
||||
@Consumes(APPLICATION_JSON)
|
||||
@Fallback(AuthorizationExceptionOn4xx.class)
|
||||
Token authorizeClientSecret(
|
||||
@FormParam("client_id") String client_id,
|
||||
@FormParam("client_secret") String client_secret,
|
||||
@FormParam("resource") String resource,
|
||||
@FormParam("scope") @Nullable String scope
|
||||
);
|
||||
}
|
||||
|
|
|
@ -26,7 +26,10 @@ public enum CredentialType {
|
|||
BEARER_TOKEN_CREDENTIALS,
|
||||
|
||||
/** Contents are a PEM-encoded P12 Private Key. */
|
||||
P12_PRIVATE_KEY_CREDENTIALS;
|
||||
P12_PRIVATE_KEY_CREDENTIALS,
|
||||
|
||||
/** Contents are an ID and Secret */
|
||||
CLIENT_CREDENTIALS_SECRET;
|
||||
|
||||
@Override public String toString() {
|
||||
return UPPER_UNDERSCORE.to(LOWER_CAMEL, name());
|
||||
|
|
|
@ -28,6 +28,7 @@ import javax.inject.Singleton;
|
|||
|
||||
import org.jclouds.oauth.v2.AuthorizationApi;
|
||||
import org.jclouds.oauth.v2.filters.BearerTokenFromCredentials;
|
||||
import org.jclouds.oauth.v2.filters.ClientCredentialsSecretFlow;
|
||||
import org.jclouds.oauth.v2.filters.JWTBearerTokenFlow;
|
||||
import org.jclouds.oauth.v2.filters.OAuthFilter;
|
||||
|
||||
|
@ -69,12 +70,15 @@ public final class OAuthModule extends AbstractModule {
|
|||
@Singleton
|
||||
protected OAuthFilter authenticationFilterForCredentialType(CredentialType credentialType,
|
||||
JWTBearerTokenFlow serviceAccountAuth,
|
||||
BearerTokenFromCredentials bearerTokenAuth) {
|
||||
BearerTokenFromCredentials bearerTokenAuth,
|
||||
ClientCredentialsSecretFlow clientCredentialAuth) {
|
||||
switch (credentialType) {
|
||||
case P12_PRIVATE_KEY_CREDENTIALS:
|
||||
return serviceAccountAuth;
|
||||
case BEARER_TOKEN_CREDENTIALS:
|
||||
return bearerTokenAuth;
|
||||
case CLIENT_CREDENTIALS_SECRET:
|
||||
return clientCredentialAuth;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported credential type: " + credentialType);
|
||||
}
|
||||
|
|
|
@ -36,6 +36,15 @@ public final class OAuthProperties {
|
|||
*/
|
||||
public static final String CREDENTIAL_TYPE = "jclouds.oauth.credential-type";
|
||||
|
||||
/**
|
||||
* When using oauth with Azure Active Direction and Client Credentials, a "resource" must
|
||||
* be specified as part of the request.
|
||||
*
|
||||
* @see <a href="https://msdn.microsoft.com/en-us/library/azure/dn645543.aspx">doc</a>
|
||||
*/
|
||||
public static final String RESOURCE = "jclouds.oauth.resource";
|
||||
|
||||
private OAuthProperties() {
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jclouds.oauth.v2.domain;
|
||||
|
||||
import org.jclouds.json.SerializedNames;
|
||||
import com.google.auto.value.AutoValue;
|
||||
|
||||
/**
|
||||
* Details corresponding the a client_credential Azure AD Oauth request
|
||||
*/
|
||||
@AutoValue
|
||||
public abstract class ClientSecret {
|
||||
/** The ID of the client. **/
|
||||
public abstract String clientId();
|
||||
|
||||
/** The secret of the client. **/
|
||||
public abstract String clientSecret();
|
||||
|
||||
/** The resource to authorize against. **/
|
||||
public abstract String resource();
|
||||
|
||||
/** The scope(s) to authorize against. **/
|
||||
public abstract String scope();
|
||||
|
||||
/** When does the token expire. **/
|
||||
public abstract long expire();
|
||||
|
||||
@SerializedNames({ "client_id", "client_secret", "resource", "scope", "expire" })
|
||||
public static ClientSecret create(String clientId, String clientSecret, String resource, String scope, long expire) {
|
||||
return new AutoValue_ClientSecret(clientId, clientSecret, resource, scope, expire);
|
||||
}
|
||||
|
||||
ClientSecret() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jclouds.oauth.v2.filters;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import org.jclouds.oauth.v2.AuthorizationApi;
|
||||
import org.jclouds.oauth.v2.domain.ClientSecret;
|
||||
import org.jclouds.oauth.v2.config.OAuthScopes;
|
||||
import org.jclouds.oauth.v2.domain.Token;
|
||||
import org.jclouds.domain.Credentials;
|
||||
import org.jclouds.http.HttpException;
|
||||
import org.jclouds.http.HttpRequest;
|
||||
import org.jclouds.location.Provider;
|
||||
|
||||
import javax.inject.Named;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
|
||||
import static org.jclouds.oauth.v2.config.OAuthProperties.RESOURCE;
|
||||
|
||||
/**
|
||||
* Authorizes new Bearer Tokens at runtime by sending up for the http request.
|
||||
*
|
||||
* To retrieve the Bearer Token, a request of grant_type=client_credentials is
|
||||
* used. The credential supplied is a password.
|
||||
*
|
||||
* <h3>Cache</h3>
|
||||
* This maintains a time-based Bearer Token cache. By default expires after 59 minutes
|
||||
* (the maximum time a token is valid is 60 minutes).
|
||||
*/
|
||||
public class ClientCredentialsSecretFlow implements OAuthFilter {
|
||||
private static final Joiner ON_SPACE = Joiner.on(" ");
|
||||
|
||||
private final Supplier<Credentials> credentialsSupplier;
|
||||
private final long tokenDuration;
|
||||
private final LoadingCache<ClientSecret, Token> tokenCache;
|
||||
@Inject(optional = true) @Named(RESOURCE) private String resource;
|
||||
@Inject(optional = true) private OAuthScopes scopes;
|
||||
|
||||
@Inject
|
||||
ClientCredentialsSecretFlow(AuthorizeToken loader, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration,
|
||||
@Provider Supplier<Credentials> credentialsSupplier) {
|
||||
this.credentialsSupplier = credentialsSupplier;
|
||||
this.tokenDuration = tokenDuration;
|
||||
// since the session interval is also the token expiration time requested to the server make the token expire a
|
||||
// bit before the deadline to make sure there aren't session expiration exceptions
|
||||
long cacheExpirationSeconds = tokenDuration > 30 ? tokenDuration - 30 : tokenDuration;
|
||||
this.tokenCache = CacheBuilder.newBuilder().expireAfterWrite(cacheExpirationSeconds, SECONDS).build(loader);
|
||||
}
|
||||
|
||||
static final class AuthorizeToken extends CacheLoader<ClientSecret, Token> {
|
||||
private final AuthorizationApi api;
|
||||
|
||||
@Inject AuthorizeToken(AuthorizationApi api) {
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
@Override public Token load(ClientSecret key) throws Exception {
|
||||
return api.authorizeClientSecret(key.clientId(), key.clientSecret(), key.resource(), key.scope());
|
||||
}
|
||||
}
|
||||
|
||||
@Override public HttpRequest filter(HttpRequest request) throws HttpException {
|
||||
long now = currentTimeSeconds();
|
||||
ClientSecret client = ClientSecret.create(
|
||||
credentialsSupplier.get().identity,
|
||||
credentialsSupplier.get().credential,
|
||||
resource == null ? "" : resource,
|
||||
scopes == null ? null : ON_SPACE.join(scopes.forRequest(request)),
|
||||
now + tokenDuration
|
||||
);
|
||||
Token token = tokenCache.getUnchecked(client);
|
||||
String authorization = String.format("%s %s", token.tokenType(), token.accessToken());
|
||||
return request.toBuilder().addHeader("Authorization", authorization).build();
|
||||
}
|
||||
|
||||
long currentTimeSeconds() {
|
||||
return System.currentTimeMillis() / 1000;
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ package org.jclouds.oauth.v2.filters;
|
|||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
|
||||
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
@ -50,7 +51,7 @@ import com.google.common.cache.LoadingCache;
|
|||
public class JWTBearerTokenFlow implements OAuthFilter {
|
||||
private static final Joiner ON_COMMA = Joiner.on(",");
|
||||
|
||||
private final String audience;
|
||||
@com.google.inject.Inject(optional = true) @Named(AUDIENCE) private String audience;
|
||||
private final Supplier<Credentials> credentialsSupplier;
|
||||
private final OAuthScopes scopes;
|
||||
private final long tokenDuration;
|
||||
|
@ -59,8 +60,8 @@ public class JWTBearerTokenFlow implements OAuthFilter {
|
|||
public static class TestJWTBearerTokenFlow extends JWTBearerTokenFlow {
|
||||
|
||||
@Inject TestJWTBearerTokenFlow(AuthorizeToken loader, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration,
|
||||
@Named(AUDIENCE) String audience, @Provider Supplier<Credentials> credentialsSupplier, OAuthScopes scopes) {
|
||||
super(loader, tokenDuration, audience, credentialsSupplier, scopes);
|
||||
@Provider Supplier<Credentials> credentialsSupplier, OAuthScopes scopes) {
|
||||
super(loader, tokenDuration, credentialsSupplier, scopes);
|
||||
}
|
||||
|
||||
/** Constant time for testing. */
|
||||
|
@ -70,8 +71,7 @@ public class JWTBearerTokenFlow implements OAuthFilter {
|
|||
}
|
||||
|
||||
@Inject JWTBearerTokenFlow(AuthorizeToken loader, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration,
|
||||
@Named(AUDIENCE) String audience, @Provider Supplier<Credentials> credentialsSupplier, OAuthScopes scopes) {
|
||||
this.audience = audience;
|
||||
@Provider Supplier<Credentials> credentialsSupplier, OAuthScopes scopes) {
|
||||
this.credentialsSupplier = credentialsSupplier;
|
||||
this.scopes = scopes;
|
||||
this.tokenDuration = tokenDuration;
|
||||
|
@ -94,6 +94,7 @@ public class JWTBearerTokenFlow implements OAuthFilter {
|
|||
}
|
||||
|
||||
@Override public HttpRequest filter(HttpRequest request) throws HttpException {
|
||||
checkNotNull(audience, AUDIENCE);
|
||||
long now = currentTimeSeconds();
|
||||
Claims claims = Claims.create( //
|
||||
credentialsSupplier.get().identity, // iss
|
||||
|
|
|
@ -18,20 +18,24 @@ package org.jclouds.oauth.v2;
|
|||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static org.jclouds.oauth.v2.OAuthTestUtils.setCredential;
|
||||
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
|
||||
import static org.jclouds.oauth.v2.config.OAuthProperties.JWS_ALG;
|
||||
import static org.jclouds.oauth.v2.config.OAuthProperties.RESOURCE;
|
||||
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
|
||||
import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE;
|
||||
import static org.jclouds.providers.AnonymousProviderMetadata.forApiOnEndpoint;
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.jclouds.apis.BaseApiLiveTest;
|
||||
import org.jclouds.oauth.v2.config.CredentialType;
|
||||
import org.jclouds.oauth.v2.config.OAuthModule;
|
||||
import org.jclouds.oauth.v2.config.OAuthScopes;
|
||||
import org.jclouds.oauth.v2.config.OAuthScopes.SingleScope;
|
||||
import org.jclouds.oauth.v2.domain.Claims;
|
||||
import org.jclouds.oauth.v2.domain.Token;
|
||||
import org.jclouds.providers.ProviderMetadata;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
@ -45,19 +49,34 @@ public class AuthorizationApiLiveTest extends BaseApiLiveTest<AuthorizationApi>
|
|||
private final String jwsAlg = "RS256";
|
||||
private String scope;
|
||||
private String audience;
|
||||
private String credentialType;
|
||||
private String resource;
|
||||
|
||||
public AuthorizationApiLiveTest() {
|
||||
provider = "oauth";
|
||||
}
|
||||
|
||||
public void authenticateJWTToken() throws Exception {
|
||||
@DataProvider
|
||||
public Object[][] onlyRunForP12PrivateKeyCredentials() {
|
||||
return (CredentialType.fromValue(credentialType) == CredentialType.P12_PRIVATE_KEY_CREDENTIALS) ?
|
||||
OAuthTestUtils.SINGLE_NO_ARG_INVOCATION : OAuthTestUtils.NO_INVOCATIONS;
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public Object[][] onlyRunForClientCredentialsSecret() {
|
||||
return (CredentialType.fromValue(credentialType) == CredentialType.CLIENT_CREDENTIALS_SECRET) ?
|
||||
OAuthTestUtils.SINGLE_NO_ARG_INVOCATION : OAuthTestUtils.NO_INVOCATIONS;
|
||||
}
|
||||
|
||||
@Test(dataProvider = "onlyRunForP12PrivateKeyCredentials")
|
||||
public void authenticateP12PrivateKeyCredentialsTest() throws Exception {
|
||||
long now = System.currentTimeMillis() / 1000;
|
||||
Claims claims = Claims.create(
|
||||
identity, // iss
|
||||
scope, // scope
|
||||
audience, // aud
|
||||
now + 3600, // exp
|
||||
now // iat
|
||||
identity, // iss
|
||||
scope, // scope
|
||||
audience, // aud
|
||||
now + 3600, // exp
|
||||
now // iat
|
||||
);
|
||||
|
||||
Token token = api.authorize(claims);
|
||||
|
@ -65,6 +84,20 @@ public class AuthorizationApiLiveTest extends BaseApiLiveTest<AuthorizationApi>
|
|||
assertNotNull(token, "no token when authorizing " + claims);
|
||||
}
|
||||
|
||||
@Test(dataProvider = "onlyRunForClientCredentialsSecret")
|
||||
public void authenticateClientCredentialsSecretTest() throws Exception {
|
||||
Token token = api.authorizeClientSecret(identity, credential, resource, scope);
|
||||
|
||||
assertNotNull(token, "no token when authorizing " + identity);
|
||||
}
|
||||
|
||||
@Test(dataProvider = "onlyRunForClientCredentialsSecret")
|
||||
public void authenticateClientCredentialsSecretNullScopeTest() throws Exception {
|
||||
Token token = api.authorizeClientSecret(identity, credential, resource, null);
|
||||
|
||||
assertNotNull(token, "no token when authorizing " + identity);
|
||||
}
|
||||
|
||||
/** OAuth isn't registered as a provider intentionally, so we fake one. */
|
||||
@Override protected ProviderMetadata createProviderMetadata() {
|
||||
return forApiOnEndpoint(AuthorizationApi.class, endpoint).toBuilder().id("oauth").build();
|
||||
|
@ -73,9 +106,27 @@ public class AuthorizationApiLiveTest extends BaseApiLiveTest<AuthorizationApi>
|
|||
@Override protected Properties setupProperties() {
|
||||
Properties props = super.setupProperties();
|
||||
props.setProperty(JWS_ALG, jwsAlg);
|
||||
credential = setCredential(props, "oauth.credential");
|
||||
audience = checkNotNull(setIfTestSystemPropertyPresent(props, AUDIENCE), "test.jclouds.oauth.audience");
|
||||
scope = checkNotNull(setIfTestSystemPropertyPresent(props, "jclouds.oauth.scope"), "test.jclouds.oauth.scope");
|
||||
|
||||
// scope is required for P12_PRIVATE_KEY_CREDENTIALS, optional for CLIENT_CREDENTIALS_SECRET.
|
||||
// Moved the not-NULL check to P12_PRIVATE_KEY_CREDENTIALS specific parameters.
|
||||
scope = setIfTestSystemPropertyPresent(props, "jclouds.oauth.scope");
|
||||
|
||||
// Determine which type of Credential to use, default to P12_PRIVATE_KEY_CREDENTIALS
|
||||
credentialType = setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE);
|
||||
if (credentialType == null) {
|
||||
credentialType = CredentialType.P12_PRIVATE_KEY_CREDENTIALS.toString();
|
||||
props.setProperty(CREDENTIAL_TYPE, credentialType);
|
||||
}
|
||||
|
||||
// Set the credential specific properties.
|
||||
if (CredentialType.fromValue(credentialType) == CredentialType.CLIENT_CREDENTIALS_SECRET) {
|
||||
resource = checkNotNull(setIfTestSystemPropertyPresent(props, RESOURCE), "test." + RESOURCE);
|
||||
} else if (CredentialType.fromValue(credentialType) == CredentialType.P12_PRIVATE_KEY_CREDENTIALS) {
|
||||
audience = checkNotNull(setIfTestSystemPropertyPresent(props, AUDIENCE), "test.jclouds.oauth.audience");
|
||||
credential = setCredential(props, "oauth.credential");
|
||||
checkNotNull(scope, "test.jclouds.oauth.scope");
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,9 @@ import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor
|
|||
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
|
||||
import static org.jclouds.Constants.PROPERTY_MAX_RETRIES;
|
||||
import static org.jclouds.oauth.v2.config.CredentialType.P12_PRIVATE_KEY_CREDENTIALS;
|
||||
import static org.jclouds.oauth.v2.config.CredentialType.CLIENT_CREDENTIALS_SECRET;
|
||||
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
|
||||
import static org.jclouds.oauth.v2.config.OAuthProperties.RESOURCE;
|
||||
import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE;
|
||||
import static org.jclouds.oauth.v2.config.OAuthProperties.JWS_ALG;
|
||||
import static org.jclouds.util.Strings2.toStringAndClose;
|
||||
|
@ -35,6 +37,7 @@ import java.util.Properties;
|
|||
|
||||
import org.jclouds.ContextBuilder;
|
||||
import org.jclouds.concurrent.config.ExecutorServiceModule;
|
||||
import org.jclouds.oauth.v2.config.CredentialType;
|
||||
import org.jclouds.oauth.v2.config.OAuthModule;
|
||||
import org.jclouds.oauth.v2.config.OAuthScopes;
|
||||
import org.jclouds.oauth.v2.config.OAuthScopes.SingleScope;
|
||||
|
@ -82,7 +85,7 @@ public class AuthorizationApiMockTest {
|
|||
+ " \"token_type\" : \"Bearer\",\n" + " \"expires_in\" : 3600\n" + "}"));
|
||||
server.play();
|
||||
|
||||
AuthorizationApi api = api(server.getUrl("/"));
|
||||
AuthorizationApi api = api(server.getUrl("/"), P12_PRIVATE_KEY_CREDENTIALS);
|
||||
|
||||
assertEquals(api.authorize(CLAIMS), TOKEN);
|
||||
|
||||
|
@ -115,7 +118,7 @@ public class AuthorizationApiMockTest {
|
|||
server.enqueue(new MockResponse().setResponseCode(400));
|
||||
server.play();
|
||||
|
||||
AuthorizationApi api = api(server.getUrl("/"));
|
||||
AuthorizationApi api = api(server.getUrl("/"), P12_PRIVATE_KEY_CREDENTIALS);
|
||||
api.authorize(CLAIMS);
|
||||
fail("An AuthorizationException should have been raised");
|
||||
} catch (AuthorizationException ex) {
|
||||
|
@ -125,14 +128,48 @@ public class AuthorizationApiMockTest {
|
|||
}
|
||||
}
|
||||
|
||||
public void testGenerateClientSecretRequest() throws Exception {
|
||||
MockWebServer server = new MockWebServer();
|
||||
|
||||
String credential = "password";
|
||||
String identity = "user";
|
||||
String resource = "http://management.azure.com/";
|
||||
String encoded_resource = "http%3A//management.azure.com/";
|
||||
|
||||
try {
|
||||
server.enqueue(new MockResponse().setBody("{\n"
|
||||
+ " \"access_token\" : \"1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M\",\n"
|
||||
+ " \"token_type\" : \"Bearer\",\n" + " \"expires_in\" : 3600\n" + "}"));
|
||||
server.play();
|
||||
|
||||
AuthorizationApi api = api(server.getUrl("/"), CLIENT_CREDENTIALS_SECRET);
|
||||
|
||||
assertEquals(api.authorizeClientSecret(identity, credential, resource, null), TOKEN);
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
assertEquals(request.getMethod(), "POST");
|
||||
assertEquals(request.getHeader("Accept"), APPLICATION_JSON);
|
||||
assertEquals(request.getHeader("Content-Type"), "application/x-www-form-urlencoded");
|
||||
|
||||
assertEquals(
|
||||
new String(request.getBody(), UTF_8), //
|
||||
"grant_type=client_credentials&client_id=" + identity
|
||||
+ "&client_secret=" + credential
|
||||
+ "&resource=" + encoded_resource);
|
||||
} finally {
|
||||
server.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private final BaseEncoding encoding = base64Url().omitPadding();
|
||||
|
||||
private AuthorizationApi api(URL url) throws IOException {
|
||||
private AuthorizationApi api(URL url, CredentialType credentialType) throws IOException {
|
||||
Properties overrides = new Properties();
|
||||
overrides.setProperty("oauth.endpoint", url.toString());
|
||||
overrides.setProperty(JWS_ALG, "RS256");
|
||||
overrides.setProperty(CREDENTIAL_TYPE, P12_PRIVATE_KEY_CREDENTIALS.toString());
|
||||
overrides.setProperty(CREDENTIAL_TYPE, credentialType.toString());
|
||||
overrides.setProperty(AUDIENCE, "https://accounts.google.com/o/oauth2/token");
|
||||
overrides.setProperty(RESOURCE, "https://management.azure.com/");
|
||||
overrides.setProperty(PROPERTY_MAX_RETRIES, "1");
|
||||
|
||||
return ContextBuilder.newBuilder(AnonymousHttpApiMetadata.forApi(AuthorizationApi.class))
|
||||
|
|
|
@ -32,6 +32,9 @@ import com.google.common.io.Files;
|
|||
|
||||
public class OAuthTestUtils {
|
||||
|
||||
public static final Object[][] NO_INVOCATIONS = new Object[0][0];
|
||||
public static final Object[][] SINGLE_NO_ARG_INVOCATION = { new Object[0] };
|
||||
|
||||
public static Properties defaultProperties(Properties properties) {
|
||||
try {
|
||||
properties = properties == null ? new Properties() : properties;
|
||||
|
|
Loading…
Reference in New Issue