Add support for Azure AD authentication using Service Principal and Password


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/04207416
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/04207416
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/04207416

Branch: refs/heads/master
Commit: 0420741690a484c5badc9fd7eb83d714e5edf2cc
Parents: 74a2a26
Author: Jim Spring <[email protected]>
Authored: Tue Mar 29 12:35:04 2016 -0700
Committer: Ignasi Barrera <[email protected]>
Committed: Fri Apr 1 14:50:09 2016 +0200

----------------------------------------------------------------------
 apis/oauth/README                               | 34 +++++--
 .../org/jclouds/oauth/v2/AuthorizationApi.java  | 13 +++
 .../jclouds/oauth/v2/config/CredentialType.java |  5 +-
 .../jclouds/oauth/v2/config/OAuthModule.java    |  6 +-
 .../oauth/v2/config/OAuthProperties.java        |  9 ++
 .../jclouds/oauth/v2/domain/ClientSecret.java   | 49 ++++++++++
 .../v2/filters/ClientCredentialsSecretFlow.java | 99 ++++++++++++++++++++
 .../oauth/v2/filters/JWTBearerTokenFlow.java    | 11 ++-
 .../oauth/v2/AuthorizationApiLiveTest.java      | 71 ++++++++++++--
 .../oauth/v2/AuthorizationApiMockTest.java      | 45 ++++++++-
 .../org/jclouds/oauth/v2/OAuthTestUtils.java    |  3 +
 11 files changed, 316 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/04207416/apis/oauth/README
----------------------------------------------------------------------
diff --git a/apis/oauth/README b/apis/oauth/README
index 40b039d..7d039b1 100644
--- a/apis/oauth/README
+++ b/apis/oauth/README
@@ -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
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/04207416/apis/oauth/src/main/java/org/jclouds/oauth/v2/AuthorizationApi.java
----------------------------------------------------------------------
diff --git 
a/apis/oauth/src/main/java/org/jclouds/oauth/v2/AuthorizationApi.java 
b/apis/oauth/src/main/java/org/jclouds/oauth/v2/AuthorizationApi.java
index fae1761..b77a8de 100644
--- a/apis/oauth/src/main/java/org/jclouds/oauth/v2/AuthorizationApi.java
+++ b/apis/oauth/src/main/java/org/jclouds/oauth/v2/AuthorizationApi.java
@@ -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
+   );
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/04207416/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/CredentialType.java
----------------------------------------------------------------------
diff --git 
a/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/CredentialType.java 
b/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/CredentialType.java
index 141564b..b80f4c4 100644
--- a/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/CredentialType.java
+++ b/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/CredentialType.java
@@ -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());

http://git-wip-us.apache.org/repos/asf/jclouds/blob/04207416/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthModule.java
----------------------------------------------------------------------
diff --git 
a/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthModule.java 
b/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthModule.java
index 8a3568c..dc18c5d 100644
--- a/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthModule.java
+++ b/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthModule.java
@@ -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);
       }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/04207416/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthProperties.java
----------------------------------------------------------------------
diff --git 
a/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthProperties.java 
b/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthProperties.java
index 7faed0c..87b5ca9 100644
--- a/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthProperties.java
+++ b/apis/oauth/src/main/java/org/jclouds/oauth/v2/config/OAuthProperties.java
@@ -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() {
    }
 }
+

http://git-wip-us.apache.org/repos/asf/jclouds/blob/04207416/apis/oauth/src/main/java/org/jclouds/oauth/v2/domain/ClientSecret.java
----------------------------------------------------------------------
diff --git 
a/apis/oauth/src/main/java/org/jclouds/oauth/v2/domain/ClientSecret.java 
b/apis/oauth/src/main/java/org/jclouds/oauth/v2/domain/ClientSecret.java
new file mode 100644
index 0000000..d664695
--- /dev/null
+++ b/apis/oauth/src/main/java/org/jclouds/oauth/v2/domain/ClientSecret.java
@@ -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() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/04207416/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/ClientCredentialsSecretFlow.java
----------------------------------------------------------------------
diff --git 
a/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/ClientCredentialsSecretFlow.java
 
b/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/ClientCredentialsSecretFlow.java
new file mode 100644
index 0000000..562e6f0
--- /dev/null
+++ 
b/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/ClientCredentialsSecretFlow.java
@@ -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;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/04207416/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/JWTBearerTokenFlow.java
----------------------------------------------------------------------
diff --git 
a/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/JWTBearerTokenFlow.java 
b/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/JWTBearerTokenFlow.java
index b862516..05ccf2a 100644
--- 
a/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/JWTBearerTokenFlow.java
+++ 
b/apis/oauth/src/main/java/org/jclouds/oauth/v2/filters/JWTBearerTokenFlow.java
@@ -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

http://git-wip-us.apache.org/repos/asf/jclouds/blob/04207416/apis/oauth/src/test/java/org/jclouds/oauth/v2/AuthorizationApiLiveTest.java
----------------------------------------------------------------------
diff --git 
a/apis/oauth/src/test/java/org/jclouds/oauth/v2/AuthorizationApiLiveTest.java 
b/apis/oauth/src/test/java/org/jclouds/oauth/v2/AuthorizationApiLiveTest.java
index c78277f..1ded5dc 100644
--- 
a/apis/oauth/src/test/java/org/jclouds/oauth/v2/AuthorizationApiLiveTest.java
+++ 
b/apis/oauth/src/test/java/org/jclouds/oauth/v2/AuthorizationApiLiveTest.java
@@ -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;
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/04207416/apis/oauth/src/test/java/org/jclouds/oauth/v2/AuthorizationApiMockTest.java
----------------------------------------------------------------------
diff --git 
a/apis/oauth/src/test/java/org/jclouds/oauth/v2/AuthorizationApiMockTest.java 
b/apis/oauth/src/test/java/org/jclouds/oauth/v2/AuthorizationApiMockTest.java
index 7e30121..adc7585 100644
--- 
a/apis/oauth/src/test/java/org/jclouds/oauth/v2/AuthorizationApiMockTest.java
+++ 
b/apis/oauth/src/test/java/org/jclouds/oauth/v2/AuthorizationApiMockTest.java
@@ -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))

http://git-wip-us.apache.org/repos/asf/jclouds/blob/04207416/apis/oauth/src/test/java/org/jclouds/oauth/v2/OAuthTestUtils.java
----------------------------------------------------------------------
diff --git a/apis/oauth/src/test/java/org/jclouds/oauth/v2/OAuthTestUtils.java 
b/apis/oauth/src/test/java/org/jclouds/oauth/v2/OAuthTestUtils.java
index e5ec5ec..e2ac660 100644
--- a/apis/oauth/src/test/java/org/jclouds/oauth/v2/OAuthTestUtils.java
+++ b/apis/oauth/src/test/java/org/jclouds/oauth/v2/OAuthTestUtils.java
@@ -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;

Reply via email to