Repository: jclouds
Updated Branches:
  refs/heads/2.1.x b144d9f47 -> 93a805ca5


JCLOUDS-1414: OpenStack Keystone V3 - different auth "domains" support

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

Branch: refs/heads/2.1.x
Commit: 93a805ca57cb29f4df534c9c1a073bad6125bafa
Parents: b144d9f
Author: Alix Lourme <[email protected]>
Authored: Mon May 7 18:06:31 2018 +0200
Committer: Ignasi Barrera <[email protected]>
Committed: Thu May 10 10:43:35 2018 +0200

----------------------------------------------------------------------
 .../domain/TenantOrDomainAndCredentials.java    |   6 +-
 .../auth/functions/BaseAuthenticator.java       |  36 +++--
 .../keystone/config/KeystoneProperties.java     |  32 ++++-
 .../v3/binders/BindAuthToJsonPayload.java       |  29 +++-
 .../openstack/keystone/v3/domain/Auth.java      |  73 +++++++---
 .../v3/auth/V3AuthenticationApiMockTest.java    | 144 +++++++++++++------
 ...th-password-project-scoped-id-domain-id.json |  26 ++++
 ...-password-project-scoped-id-domain-name.json |  26 ++++
 ...ect-scoped-name-domain-backwards-compat.json |  26 ++++
 ...-password-project-scoped-name-domain-id.json |  26 ++++
 ...assword-project-scoped-name-domain-name.json |  26 ++++
 11 files changed, 368 insertions(+), 82 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/93a805ca/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/auth/domain/TenantOrDomainAndCredentials.java
----------------------------------------------------------------------
diff --git 
a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/auth/domain/TenantOrDomainAndCredentials.java
 
b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/auth/domain/TenantOrDomainAndCredentials.java
index b30f3fc..f6ce1de 100644
--- 
a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/auth/domain/TenantOrDomainAndCredentials.java
+++ 
b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/auth/domain/TenantOrDomainAndCredentials.java
@@ -30,7 +30,9 @@ public abstract class TenantOrDomainAndCredentials<T> {
    @Nullable public abstract String tenantOrDomainId();
    @Nullable public abstract String tenantOrDomainName();
    @Nullable public abstract String scope();
-   public abstract T credentials();
+   @Nullable public abstract String projectDomainName();
+   @Nullable public abstract String projectDomainId();
+   public abstract T credentials(); 
 
    TenantOrDomainAndCredentials() {
 
@@ -45,6 +47,8 @@ public abstract class TenantOrDomainAndCredentials<T> {
       public abstract Builder<T> tenantOrDomainId(String tenantId);
       public abstract Builder<T> tenantOrDomainName(String tenantName);
       public abstract Builder<T> scope(String scope);
+      public abstract Builder<T> projectDomainName(String projectDomainName);
+      public abstract Builder<T> projectDomainId(String projectDomainId); 
       public abstract Builder<T> credentials(T credentials);
 
       public abstract TenantOrDomainAndCredentials<T> build();

http://git-wip-us.apache.org/repos/asf/jclouds/blob/93a805ca/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/auth/functions/BaseAuthenticator.java
----------------------------------------------------------------------
diff --git 
a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/auth/functions/BaseAuthenticator.java
 
b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/auth/functions/BaseAuthenticator.java
index d49204c..015f470 100644
--- 
a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/auth/functions/BaseAuthenticator.java
+++ 
b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/auth/functions/BaseAuthenticator.java
@@ -17,6 +17,8 @@
 package org.jclouds.openstack.keystone.auth.functions;
 
 import static com.google.common.base.Preconditions.checkState;
+import static 
org.jclouds.openstack.keystone.config.KeystoneProperties.PROJECT_DOMAIN_ID;
+import static 
org.jclouds.openstack.keystone.config.KeystoneProperties.PROJECT_DOMAIN_NAME;
 import static 
org.jclouds.openstack.keystone.config.KeystoneProperties.REQUIRES_TENANT;
 import static org.jclouds.openstack.keystone.config.KeystoneProperties.SCOPE;
 import static 
org.jclouds.openstack.keystone.config.KeystoneProperties.TENANT_ID;
@@ -51,15 +53,25 @@ public abstract class BaseAuthenticator<C> implements 
Function<Credentials, Auth
    @Inject(optional = true)
    @Named(REQUIRES_TENANT)
    protected boolean requiresTenant;
-   
+
    @Inject(optional = true)
    @Named(SCOPE)
    protected String scope = Scope.UNSCOPED;
 
+   @Inject(optional = true)
+   @Named(PROJECT_DOMAIN_NAME)
+   protected String projectDomainName;
+
+   @Inject(optional = true)
+   @Named(PROJECT_DOMAIN_ID)
+   protected String projectDomainId;
+
    @PostConstruct
    public void checkPropertiesAreCompatible() {
-      checkState(defaultTenantName == null || defaultTenantId == null, "you 
cannot specify both %s and %s",
-            TENANT_NAME, TENANT_ID);
+      checkState(defaultTenantName == null || defaultTenantId == null, "you 
cannot specify both %s and %s", TENANT_NAME,
+            TENANT_ID);
+      checkState(projectDomainName == null || projectDomainId == null, "you 
cannot specify both %s and %s",
+            PROJECT_DOMAIN_NAME, PROJECT_DOMAIN_ID);
    }
 
    @Override
@@ -74,21 +86,21 @@ public abstract class BaseAuthenticator<C> implements 
Function<Credentials, Auth
       }
 
       if (defaultTenantId == null && tenantName == null && requiresTenant) {
-         throw new IllegalArgumentException(
-               String.format(
-                     "current configuration is set to [%s]. Unless you set 
[%s] or [%s], you must prefix your identity with 'tenantName:'",
-                     REQUIRES_TENANT, TENANT_NAME, TENANT_ID));
+         throw new IllegalArgumentException(String.format(
+               "current configuration is set to [%s]. Unless you set [%s] or 
[%s], you must prefix your identity with 'tenantName:'",
+               REQUIRES_TENANT, TENANT_NAME, TENANT_ID));
       }
-      
+
       C creds = createCredentials(usernameOrAccessKey, 
passwordOrSecretKeyOrToken);
-      TenantOrDomainAndCredentials<C> credsWithTenant = 
TenantOrDomainAndCredentials.<C> builder().tenantOrDomainId(defaultTenantId)
-            
.tenantOrDomainName(tenantName).scope(scope).credentials(creds).build();
-      
+      TenantOrDomainAndCredentials<C> credsWithTenant = 
TenantOrDomainAndCredentials.<C> builder()
+            
.tenantOrDomainId(defaultTenantId).tenantOrDomainName(tenantName).scope(scope)
+            
.projectDomainName(projectDomainName).projectDomainId(projectDomainId).credentials(creds).build();
+
       return authenticate(credsWithTenant);
    }
 
    public abstract C createCredentials(String identity, String credential);
-   
+
    public abstract AuthInfo authenticate(TenantOrDomainAndCredentials<C> 
credentials);
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/93a805ca/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/config/KeystoneProperties.java
----------------------------------------------------------------------
diff --git 
a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/config/KeystoneProperties.java
 
b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/config/KeystoneProperties.java
index bab41a4..e97ff9c 100644
--- 
a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/config/KeystoneProperties.java
+++ 
b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/config/KeystoneProperties.java
@@ -65,7 +65,7 @@ public final class KeystoneProperties {
     * @see <a href="http://wiki.openstack.org/CLIAuth";>openstack docs</a>
     */
    public static final String REQUIRES_TENANT = 
"jclouds.keystone.requires-tenant";
-   
+
    /**
     * set this property to specify for scoped authentication.
     * <p>
@@ -81,12 +81,40 @@ public final class KeystoneProperties {
    public static final String SCOPE = "jclouds.keystone.scope";
 
    /**
+    * Set this property to specify the domain name of project (tenant)
+    * scope.<br/>
+    * Required property when authentication {@link #SCOPE} is 'project:' and
+    * project (tenant) domain is different than the user domain (Otherwise, the
+    * domain used is the same as the user). <br/>
+    * Cannot be used simultaneously with {@link #PROJECT_DOMAIN_ID}
+    *
+    * @see <a href=
+    *      
"https://docs.openstack.org/keystone/latest/api_curl_examples.html#project-scoped";>openstack
+    *      docs : Identity service (Keystone)</a>
+    */
+   public static final String PROJECT_DOMAIN_NAME = 
"jclouds.keystone.project-domain-name";
+
+   /**
+    * Set this property to specify the domain id of project (tenant) 
scope.<br/>
+    * Required property when authentication {@link #SCOPE} is 'project:' and
+    * project (tenant) domain is different than the user domain (Otherwise, the
+    * domain used is the same as the user). <br/>
+    * Cannot be used simultaneously with {@link #PROJECT_DOMAIN_NAME}
+    *
+    * 
+    * @see <a href=
+    *      
"https://docs.openstack.org/keystone/latest/api_curl_examples.html#project-scoped";>openstack
+    *      docs : Identity service (Keystone)</a>
+    */
+   public static final String PROJECT_DOMAIN_ID = 
"jclouds.keystone.project-domain-id";
+
+   /**
     * type of the keystone service. ex. {@code compute}
     *
     * @see ServiceType
     */
    public static final String SERVICE_TYPE = "jclouds.keystone.service-type";
-   
+
    /**
     * Version of keystone to be used by services. Default: 3.
     */

http://git-wip-us.apache.org/repos/asf/jclouds/blob/93a805ca/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/binders/BindAuthToJsonPayload.java
----------------------------------------------------------------------
diff --git 
a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/binders/BindAuthToJsonPayload.java
 
b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/binders/BindAuthToJsonPayload.java
index a27ae12..be0ba60 100644
--- 
a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/binders/BindAuthToJsonPayload.java
+++ 
b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/binders/BindAuthToJsonPayload.java
@@ -38,6 +38,7 @@ import 
org.jclouds.openstack.keystone.v3.domain.Auth.DomainScope;
 import org.jclouds.openstack.keystone.v3.domain.Auth.Id;
 import org.jclouds.openstack.keystone.v3.domain.Auth.Name;
 import org.jclouds.openstack.keystone.v3.domain.Auth.ProjectIdScope;
+import org.jclouds.openstack.keystone.v3.domain.Auth.ProjectIdScope.ProjectId;
 import org.jclouds.openstack.keystone.v3.domain.Auth.ProjectScope;
 import org.jclouds.openstack.keystone.v3.domain.Auth.ProjectScope.ProjectName;
 import org.jclouds.rest.MapBinder;
@@ -50,8 +51,7 @@ import com.google.common.collect.ImmutableSet;
 
 public abstract class BindAuthToJsonPayload<T> extends BindToJsonPayload 
implements MapBinder {
 
-   private static final Set<String> SCOPE_PREFIXES = ImmutableSet
-         .of(PROJECT, PROJECT_ID, DOMAIN, DOMAIN_ID);
+   private static final Set<String> SCOPE_PREFIXES = ImmutableSet.of(PROJECT, 
PROJECT_ID, DOMAIN, DOMAIN_ID);
 
    protected BindAuthToJsonPayload(Json jsonBinder) {
       super(jsonBinder);
@@ -93,15 +93,32 @@ public abstract class BindAuthToJsonPayload<T> extends 
BindToJsonPayload impleme
       checkArgument(SCOPE_PREFIXES.contains(parts[0]), "Scope prefix should 
be: %s", SCOPE_PREFIXES);
 
       if (PROJECT.equals(parts[0])) {
-         Object domainScope = credentials.tenantOrDomainId() != null ? 
Id.create(credentials.tenantOrDomainId()) : Name
-               .create(credentials.tenantOrDomainName());
-         return ProjectScope.create(ProjectName.create(parts[1], domainScope));
+         return ProjectScope.create(ProjectName.create(parts[1], 
parseProjectDomain(credentials, true)));
       } else if (PROJECT_ID.equals(parts[0])) {
-         return ProjectIdScope.create(Id.create(parts[1]));
+         // tenant (name/id) was never used as domain for project-id; so try to
+         // keep backward compatibility
+         return ProjectIdScope.create(ProjectId.create(parts[1], 
parseProjectDomain(credentials, false)));
       } else if (DOMAIN.equals(parts[0])) {
          return DomainScope.create(Name.create(parts[1]));
       } else {
          return DomainIdScope.create(Id.create(parts[1]));
       }
    }
+
+   private Object parseProjectDomain(TenantOrDomainAndCredentials<T> 
credentials, boolean useTenantAsDefaultDomain) {
+      // Before 'projectDomainName'/'projectDomainId' support,
+      // 'tenantOrDomainId' was used as domain (id) for project-scoped by name,
+      // but not by id, so 'useTenantAsDefaultDomain' flag allows to manage 
that
+      Object domainScope = null;
+      if (useTenantAsDefaultDomain && credentials.tenantOrDomainId() != null) {
+         domainScope = Id.create(credentials.tenantOrDomainId());
+      } else if (credentials.projectDomainName() != null) {
+         domainScope = Name.create(credentials.projectDomainName());
+      } else if (credentials.projectDomainId() != null) {
+         domainScope = Id.create(credentials.projectDomainId());
+      } else if (useTenantAsDefaultDomain) {
+         domainScope = Name.create(credentials.tenantOrDomainName());
+      }
+      return domainScope;
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/93a805ca/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Auth.java
----------------------------------------------------------------------
diff --git 
a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Auth.java
 
b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Auth.java
index 58c9ee6..fcfe4fa 100644
--- 
a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Auth.java
+++ 
b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Auth.java
@@ -26,18 +26,24 @@ import com.google.auto.value.AutoValue;
 @AutoValue
 public abstract class Auth {
    public abstract Identity identity();
-   @Nullable public abstract Object scope();
+
+   @Nullable
+   public abstract Object scope();
 
    @SerializedNames({ "identity", "scope" })
    public static Auth create(Identity identity, Object scope) {
       return new AutoValue_Auth(identity, scope);
    }
-   
+
    @AutoValue
    public abstract static class Identity {
       public abstract List<String> methods();
-      @Nullable public abstract Id token();
-      @Nullable public abstract PasswordAuth password();
+
+      @Nullable
+      public abstract Id token();
+
+      @Nullable
+      public abstract PasswordAuth password();
 
       @SerializedNames({ "methods", "token", "password" })
       public static Identity create(List<String> methods, Id token, 
PasswordAuth password) {
@@ -56,7 +62,9 @@ public abstract class Auth {
          @AutoValue
          public abstract static class UserAuth {
             public abstract String name();
+
             public abstract DomainAuth domain();
+
             public abstract String password();
 
             @SerializedNames({ "name", "domain", "password" })
@@ -66,7 +74,8 @@ public abstract class Auth {
 
             @AutoValue
             public abstract static class DomainAuth {
-               @Nullable public abstract String name();
+               @Nullable
+               public abstract String name();
 
                @SerializedNames({ "name" })
                public static DomainAuth create(String name) {
@@ -76,7 +85,7 @@ public abstract class Auth {
          }
       }
    }
-   
+
    @AutoValue
    public abstract static class Id {
       public abstract String id();
@@ -86,17 +95,18 @@ public abstract class Auth {
          return new AutoValue_Auth_Id(id);
       }
    }
-   
+
    @AutoValue
    public abstract static class Name {
-      @Nullable public abstract String name();
+      @Nullable
+      public abstract String name();
 
       @SerializedNames({ "name" })
       public static Name create(String name) {
          return new AutoValue_Auth_Name(name);
       }
    }
-   
+
    public static class Scope {
       public static final String PROJECT = "project";
       public static final String PROJECT_ID = "projectId";
@@ -108,22 +118,24 @@ public abstract class Auth {
    @AutoValue
    public abstract static class ProjectScope {
       public abstract ProjectName project();
-      
+
       @SerializedNames({ Scope.PROJECT })
       public static ProjectScope create(ProjectName project) {
          return new AutoValue_Auth_ProjectScope(project);
       }
-      
+
       @AutoValue
       public abstract static class ProjectName {
          public abstract String name();
-         @Nullable public abstract Object domain();
-         
+
+         @Nullable
+         public abstract Object domain();
+
          @SerializedNames({ "name", Scope.DOMAIN })
          public static ProjectName create(String name, Object domain) {
             return new AutoValue_Auth_ProjectScope_ProjectName(name, domain);
          }
-         
+
          public static ProjectName create(String name, Name domain) {
             return new AutoValue_Auth_ProjectScope_ProjectName(name, domain);
          }
@@ -133,17 +145,38 @@ public abstract class Auth {
          }
       }
    }
-   
+
    @AutoValue
    public abstract static class ProjectIdScope {
-      public abstract Id project();
+      public abstract ProjectId project();
 
       @SerializedNames({ Scope.PROJECT })
-      public static ProjectIdScope create(Id id) {
-         return new AutoValue_Auth_ProjectIdScope(id);
+      public static ProjectIdScope create(ProjectId project) {
+         return new AutoValue_Auth_ProjectIdScope(project);
+      }
+
+      @AutoValue
+      public abstract static class ProjectId {
+         public abstract String id();
+
+         @Nullable
+         public abstract Object domain();
+
+         @SerializedNames({ "id", Scope.DOMAIN })
+         public static ProjectId create(String id, Object domain) {
+            return new AutoValue_Auth_ProjectIdScope_ProjectId(id, domain);
+         }
+
+         public static ProjectId create(String id, Name domain) {
+            return new AutoValue_Auth_ProjectIdScope_ProjectId(id, domain);
+         }
+
+         public static ProjectId create(String id, Id domain) {
+            return new AutoValue_Auth_ProjectIdScope_ProjectId(id, domain);
+         }
       }
    }
-   
+
    @AutoValue
    public abstract static class DomainIdScope {
       public abstract Id domain();
@@ -153,7 +186,7 @@ public abstract class Auth {
          return new AutoValue_Auth_DomainIdScope(id);
       }
    }
-   
+
    @AutoValue
    public abstract static class DomainScope {
       public abstract Name domain();

http://git-wip-us.apache.org/repos/asf/jclouds/blob/93a805ca/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/auth/V3AuthenticationApiMockTest.java
----------------------------------------------------------------------
diff --git 
a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/auth/V3AuthenticationApiMockTest.java
 
b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/auth/V3AuthenticationApiMockTest.java
index d601913..fac5d52 100644
--- 
a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/auth/V3AuthenticationApiMockTest.java
+++ 
b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/auth/V3AuthenticationApiMockTest.java
@@ -19,6 +19,7 @@ package org.jclouds.openstack.keystone.v3.auth;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
+import org.jclouds.openstack.keystone.auth.domain.ApiAccessKeyCredentials;
 import org.jclouds.openstack.keystone.auth.domain.AuthInfo;
 import org.jclouds.openstack.keystone.auth.domain.PasswordCredentials;
 import org.jclouds.openstack.keystone.auth.domain.TenantOrDomainAndCredentials;
@@ -31,71 +32,132 @@ import org.testng.annotations.Test;
 public class V3AuthenticationApiMockTest extends BaseV3KeystoneApiMockTest {
 
    public void testAuthenticatePassword() throws InterruptedException {
-      server.enqueue(jsonResponse("/v3/token.json"));
 
-      TenantOrDomainAndCredentials<PasswordCredentials> credentials = 
TenantOrDomainAndCredentials.<PasswordCredentials> builder()
-            .tenantOrDomainName("domain")
-            .scope("unscoped")
+      TenantOrDomainAndCredentials<PasswordCredentials> credentials = 
TenantOrDomainAndCredentials
+            .<PasswordCredentials> 
builder().tenantOrDomainName("domain").scope("unscoped")
             
.credentials(PasswordCredentials.builder().username("identity").password("credential").build()).build();
-      
-      AuthInfo authInfo = authenticationApi.authenticatePassword(credentials);
-
-      assertTrue(authInfo instanceof Token);
-      assertEquals(authInfo, tokenFromResource("/v3/token.json"));
 
-      assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "POST", "/auth/tokens", 
stringFromResource("/v3/auth-password.json"));
+      checkTokenResult(credentials, "/v3/auth-password.json");
    }
-   
+
    public void testAuthenticatePasswordScoped() throws InterruptedException {
-      server.enqueue(jsonResponse("/v3/token.json"));
 
-      TenantOrDomainAndCredentials<PasswordCredentials> credentials = 
TenantOrDomainAndCredentials.<PasswordCredentials> builder()
-            .tenantOrDomainName("domain")
-            .scope("projectId:1234567890")
+      TenantOrDomainAndCredentials<PasswordCredentials> credentials = 
TenantOrDomainAndCredentials
+            .<PasswordCredentials> 
builder().tenantOrDomainName("domain").scope("projectId:1234567890")
             
.credentials(PasswordCredentials.builder().username("identity").password("credential").build()).build();
-      
-      AuthInfo authInfo = authenticationApi.authenticatePassword(credentials);
 
-      assertTrue(authInfo instanceof Token);
-      assertEquals(authInfo, tokenFromResource("/v3/token.json"));
+      checkTokenResult(credentials, "/v3/auth-password-scoped.json");
+   }
 
-      assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "POST", "/auth/tokens", 
stringFromResource("/v3/auth-password-scoped.json"));
+   public void testAuthenticatePasswordProjectScopedIdDomainBackwardsCompat() 
throws InterruptedException {
+      // See JCLOUDS-1414, before add of KeystoneProperties.PROJECT_DOMAIN,
+      // TENANT_ID was not used as domain for project-scoped with id
+      // => Unit test only for backward compatibility (is the same as
+      // 'testAuthenticatePasswordScoped' with TENANT-ID in addition)
+
+      TenantOrDomainAndCredentials<PasswordCredentials> credentials = 
TenantOrDomainAndCredentials
+            .<PasswordCredentials> 
builder().tenantOrDomainName("domain").scope("projectId:1234567890")
+            .tenantOrDomainId("somethingShouldNotBeUsed")
+            
.credentials(PasswordCredentials.builder().username("identity").password("credential").build()).build();
+
+      checkTokenResult(credentials, "/v3/auth-password-scoped.json");
+   }
+
+   public void 
testAuthenticatePasswordProjectScopedNameDomainBackwardsCompat() throws 
InterruptedException {
+      // See JCLOUDS-1414, before add of KeystoneProperties.PROJECT_DOMAIN,
+      // domain-id of project-scoped could be filled with TENANT_ID
+      // => Unit test only for backward compatibility
+
+      TenantOrDomainAndCredentials<PasswordCredentials> credentials = 
TenantOrDomainAndCredentials
+            .<PasswordCredentials> 
builder().tenantOrDomainName("domain").scope("project:my-project")
+            .tenantOrDomainId("default")
+            
.credentials(PasswordCredentials.builder().username("identity").password("credential").build()).build();
+
+      checkTokenResult(credentials, 
"/v3/auth-password-project-scoped-name-domain-backwards-compat.json");
+   }
+
+   public void testAuthenticatePasswordProjectScopedIdDomainId() throws 
InterruptedException {
+
+      TenantOrDomainAndCredentials<PasswordCredentials> credentials = 
TenantOrDomainAndCredentials
+            .<PasswordCredentials> 
builder().tenantOrDomainName("domain").scope("projectId:42-project-42")
+            .projectDomainId("42-domain-42")
+            
.credentials(PasswordCredentials.builder().username("identity").password("credential").build()).build();
+
+      checkTokenResult(credentials, 
"/v3/auth-password-project-scoped-id-domain-id.json");
+   }
+
+   public void testAuthenticatePasswordProjectScopedIdDomainName() throws 
InterruptedException {
+
+      TenantOrDomainAndCredentials<PasswordCredentials> credentials = 
TenantOrDomainAndCredentials
+            .<PasswordCredentials> 
builder().tenantOrDomainName("domain").scope("projectId:42")
+            .projectDomainName("default")
+            
.credentials(PasswordCredentials.builder().username("identity").password("credential").build()).build();
+
+      checkTokenResult(credentials, 
"/v3/auth-password-project-scoped-id-domain-name.json");
+   }
+
+   public void testAuthenticatePasswordProjectScopedNameDomainId() throws 
InterruptedException {
+
+      TenantOrDomainAndCredentials<PasswordCredentials> credentials = 
TenantOrDomainAndCredentials
+            .<PasswordCredentials> 
builder().tenantOrDomainName("domain").scope("project:my-project")
+            .projectDomainId("42")
+            
.credentials(PasswordCredentials.builder().username("identity").password("credential").build()).build();
+
+      checkTokenResult(credentials, 
"/v3/auth-password-project-scoped-name-domain-id.json");
+   }
+
+   public void testAuthenticatePasswordProjectScopedNameDomainName() throws 
InterruptedException {
+
+      TenantOrDomainAndCredentials<PasswordCredentials> credentials = 
TenantOrDomainAndCredentials
+            .<PasswordCredentials> 
builder().tenantOrDomainName("domain").scope("project:my-project")
+            .projectDomainName("default")
+            
.credentials(PasswordCredentials.builder().username("identity").password("credential").build()).build();
+
+      checkTokenResult(credentials, 
"/v3/auth-password-project-scoped-name-domain-name.json");
    }
 
    public void testAuthenticateToken() throws InterruptedException {
-      server.enqueue(jsonResponse("/v3/token.json"));
 
-      TenantOrDomainAndCredentials<TokenCredentials> credentials = 
TenantOrDomainAndCredentials.<TokenCredentials> builder()
-            .tenantOrDomainName("domain")
-            .scope("unscoped")
+      TenantOrDomainAndCredentials<TokenCredentials> credentials = 
TenantOrDomainAndCredentials
+            .<TokenCredentials> 
builder().tenantOrDomainName("domain").scope("unscoped")
             
.credentials(TokenCredentials.builder().id("token").build()).build();
-      
-      AuthInfo authInfo = authenticationApi.authenticateToken(credentials);
-
-      assertTrue(authInfo instanceof Token);
-      assertEquals(authInfo, tokenFromResource("/v3/token.json"));
 
-      assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "POST", "/auth/tokens", 
stringFromResource("/v3/auth-token.json"));
+      checkTokenResult(credentials, "/v3/auth-token.json");
    }
-   
+
    public void testAuthenticateTokenScoped() throws InterruptedException {
-      server.enqueue(jsonResponse("/v3/token.json"));
 
-      TenantOrDomainAndCredentials<TokenCredentials> credentials = 
TenantOrDomainAndCredentials.<TokenCredentials> builder()
-            .tenantOrDomainName("domain")
-            .scope("domain:mydomain")
+      TenantOrDomainAndCredentials<TokenCredentials> credentials = 
TenantOrDomainAndCredentials
+            .<TokenCredentials> 
builder().tenantOrDomainName("domain").scope("domain:mydomain")
             
.credentials(TokenCredentials.builder().id("token").build()).build();
-      
-      AuthInfo authInfo = authenticationApi.authenticateToken(credentials);
+
+      checkTokenResult(credentials, "/v3/auth-token-scoped.json");
+   }
+
+   @SuppressWarnings("unchecked")
+   private void checkTokenResult(TenantOrDomainAndCredentials<?> credentials, 
String json) throws InterruptedException {
+      server.enqueue(jsonResponse("/v3/token.json"));
+
+      AuthInfo authInfo = null;
+
+      if (credentials.credentials() instanceof PasswordCredentials) {
+         authInfo = authenticationApi
+               
.authenticatePassword((TenantOrDomainAndCredentials<PasswordCredentials>) 
credentials);
+      } else if (credentials.credentials() instanceof TokenCredentials) {
+         authInfo = 
authenticationApi.authenticateToken((TenantOrDomainAndCredentials<TokenCredentials>)
 credentials);
+      } else if (credentials.credentials() instanceof ApiAccessKeyCredentials) 
{
+         authInfo = authenticationApi
+               
.authenticateAccessKey((TenantOrDomainAndCredentials<ApiAccessKeyCredentials>) 
credentials);
+      } else {
+         throw new IllegalArgumentException(String.format("Unsupported 
authentication method with class: %s",
+               credentials.credentials().getClass().getName()));
+      }
 
       assertTrue(authInfo instanceof Token);
       assertEquals(authInfo, tokenFromResource("/v3/token.json"));
 
       assertEquals(server.getRequestCount(), 1);
-      assertSent(server, "POST", "/auth/tokens", 
stringFromResource("/v3/auth-token-scoped.json"));
+      assertSent(server, "POST", "/auth/tokens", stringFromResource(json));
    }
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/93a805ca/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-id-domain-id.json
----------------------------------------------------------------------
diff --git 
a/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-id-domain-id.json
 
b/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-id-domain-id.json
new file mode 100644
index 0000000..49b76e4
--- /dev/null
+++ 
b/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-id-domain-id.json
@@ -0,0 +1,26 @@
+{
+    "auth": {
+        "identity": {
+            "methods": [
+                "password"
+            ],
+            "password": {
+                "user": {
+                    "name": "identity",
+                    "domain": {
+                        "name": "domain"
+                    },
+                    "password": "credential"
+                }
+            }
+        },
+        "scope": {
+            "project": {
+                "id": "42-project-42",
+                "domain": {
+                    "id": "42-domain-42"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/93a805ca/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-id-domain-name.json
----------------------------------------------------------------------
diff --git 
a/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-id-domain-name.json
 
b/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-id-domain-name.json
new file mode 100644
index 0000000..b910abe
--- /dev/null
+++ 
b/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-id-domain-name.json
@@ -0,0 +1,26 @@
+{
+    "auth": {
+        "identity": {
+            "methods": [
+                "password"
+            ],
+            "password": {
+                "user": {
+                    "name": "identity",
+                    "domain": {
+                        "name": "domain"
+                    },
+                    "password": "credential"
+                }
+            }
+        },
+        "scope": {
+            "project": {
+                "id": "42",
+                "domain": {
+                    "name": "default"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/93a805ca/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-name-domain-backwards-compat.json
----------------------------------------------------------------------
diff --git 
a/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-name-domain-backwards-compat.json
 
b/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-name-domain-backwards-compat.json
new file mode 100644
index 0000000..69ea30e
--- /dev/null
+++ 
b/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-name-domain-backwards-compat.json
@@ -0,0 +1,26 @@
+{
+    "auth": {
+        "identity": {
+            "methods": [
+                "password"
+            ],
+            "password": {
+                "user": {
+                    "name": "identity",
+                    "domain": {
+                        "name": "domain"
+                    },
+                    "password": "credential"
+                }
+            }
+        },
+        "scope": {
+            "project": {
+                "name": "my-project",
+                "domain": {
+                    "id": "default"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/93a805ca/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-name-domain-id.json
----------------------------------------------------------------------
diff --git 
a/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-name-domain-id.json
 
b/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-name-domain-id.json
new file mode 100644
index 0000000..e99656f
--- /dev/null
+++ 
b/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-name-domain-id.json
@@ -0,0 +1,26 @@
+{
+    "auth": {
+        "identity": {
+            "methods": [
+                "password"
+            ],
+            "password": {
+                "user": {
+                    "name": "identity",
+                    "domain": {
+                        "name": "domain"
+                    },
+                    "password": "credential"
+                }
+            }
+        },
+        "scope": {
+            "project": {
+                "name": "my-project",
+                "domain": {
+                    "id": "42"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/93a805ca/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-name-domain-name.json
----------------------------------------------------------------------
diff --git 
a/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-name-domain-name.json
 
b/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-name-domain-name.json
new file mode 100644
index 0000000..054393c
--- /dev/null
+++ 
b/apis/openstack-keystone/src/test/resources/v3/auth-password-project-scoped-name-domain-name.json
@@ -0,0 +1,26 @@
+{
+    "auth": {
+        "identity": {
+            "methods": [
+                "password"
+            ],
+            "password": {
+                "user": {
+                    "name": "identity",
+                    "domain": {
+                        "name": "domain"
+                    },
+                    "password": "credential"
+                }
+            }
+        },
+        "scope": {
+            "project": {
+                "name": "my-project",
+                "domain": {
+                    "name": "default"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file

Reply via email to