diff --git a/identity/identity.go b/identity/identity.go index 2f3249a..34783d4 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -72,6 +72,7 @@ type Credentials struct { Domain string `credentials:"optional"` // The domain for authorization (new in keystone v3) UserDomain string `credentials:"optional"` // The owning domain for this user (new in keystone v3) ProjectDomain string `credentials:"optional"` // The project domain for authorization (new in keystone v3) + TrustID string `credentials:"optional"` // The trust ID for authorization (new in keystone v3) } // Authenticator is implemented by each authentication method. @@ -153,6 +154,9 @@ var ( CredEnvDomainName = []string{ "OS_DOMAIN_NAME", } + CredEnvTrustID = []string{ + "OS_TRUST_ID", + } ) // CredentialsFromEnv creates and initializes the credentials from the @@ -168,6 +172,7 @@ func CredentialsFromEnv() (*Credentials, error) { Domain: getConfig(CredEnvDomainName), UserDomain: getConfig(CredEnvUserDomainName), ProjectDomain: getConfig(CredEnvProjectDomainName), + TrustID: getConfig(CredEnvTrustID), } defaultDomain := getConfig(CredEnvDefaultDomainName) if defaultDomain != "" { diff --git a/identity/identity_test.go b/identity/identity_test.go index d3344d6..5bf447a 100644 --- a/identity/identity_test.go +++ b/identity/identity_test.go @@ -87,6 +87,7 @@ func (s *CredentialsTestSuite) TestCompleteCredentialsFromEnvValid(c *gc.C) { "OS_DOMAIN_NAME": "domain-name", "OS_PROJECT_DOMAIN_NAME": "project-domain-name", "OS_USER_DOMAIN_NAME": "user-domain-name", + "OS_TRUST_ID": "trust-id", // ignored because user and project domains set "OS_DEFAULT_DOMAIN_NAME": "default-domain-name", } @@ -103,6 +104,7 @@ func (s *CredentialsTestSuite) TestCompleteCredentialsFromEnvValid(c *gc.C) { c.Check(creds.Domain, gc.Equals, "domain-name") c.Check(creds.ProjectDomain, gc.Equals, "project-domain-name") c.Check(creds.UserDomain, gc.Equals, "user-domain-name") + c.Check(creds.TrustID, gc.Equals, "trust-id") } func (s *CredentialsTestSuite) TestCompleteCredentialsFromEnvDefaultDomain(c *gc.C) { diff --git a/identity/v3userpass.go b/identity/v3userpass.go index 3157f3c..c6c5113 100644 --- a/identity/v3userpass.go +++ b/identity/v3userpass.go @@ -58,6 +58,7 @@ type v3AuthToken struct { type v3AuthScope struct { Domain *v3AuthDomain `json:"domain,omitempty"` Project *v3AuthProject `json:"project,omitempty"` + Trust *v3AuthTrust `json:"trust,omitempty"` } // v3AuthProject contains the project scope for the authentication @@ -68,6 +69,10 @@ type v3AuthProject struct { Name string `json:"name,omitempty"` } +type v3AuthTrust struct { + ID string `json:"id"` +} + // V3UserPass is an Authenticator that will perform username + password // authentication using the v3 protocol. type V3UserPass struct { @@ -104,7 +109,16 @@ func (u *V3UserPass) Auth(creds *Credentials) (*AuthDetails, error) { }, }, } - if creds.TenantName != "" || creds.TenantID != "" { + if creds.TrustID != "" { + if creds.TenantName != "" || creds.TenantID != "" || creds.Domain != "" { + return nil, fmt.Errorf("trust authentication cannot be scoped to a project or domain") + } + auth.Auth.Scope = &v3AuthScope{ + Trust: &v3AuthTrust{ + ID: creds.TrustID, + }, + } + } else if creds.TenantName != "" || creds.TenantID != "" { auth.Auth.Scope = &v3AuthScope{ Project: &v3AuthProject{ Domain: &v3AuthDomain{ diff --git a/identity/v3userpass_test.go b/identity/v3userpass_test.go index e57e0b7..e0247aa 100644 --- a/identity/v3userpass_test.go +++ b/identity/v3userpass_test.go @@ -124,6 +124,60 @@ func (s *V3UserPassTestSuite) TestAuthWithCatalog(c *gc.C) { c.Assert(auth.TenantId, gc.Equals, userInfo.TenantId) } +func (s *V3UserPassTestSuite) TestAuthWithTrustID(c *gc.C) { + service := identityservice.NewV3UserPass() + service.SetupHTTP(s.Mux) + userInfo := service.AddUser("joe-user", "secrets", "tenant", "default") + var l Authenticator = &V3UserPass{} + creds := Credentials{ + User: "joe-user", + URL: s.Server.URL + "/v3/auth/tokens", + Secrets: "secrets", + TrustID: "trust-id", + } + + authfunc := func(sc hook.ServiceControl, args ...interface{}) error { + v3input := args[0].(identityservice.V3UserPassRequest) + c.Assert(v3input.Auth.Identity.Methods, gc.DeepEquals, []string{"password"}) + c.Assert(v3input.Auth.Scope.Trust.ID, gc.Equals, "trust-id") + c.Assert(v3input.Auth.Scope.Project.ID, gc.Equals, "") + c.Assert(v3input.Auth.Scope.Project.Name, gc.Equals, "") + c.Assert(v3input.Auth.Scope.Domain.Name, gc.Equals, "") + return nil + } + + cleanup := service.RegisterControlPoint("preauthentication", authfunc) + defer cleanup() + + auth, err := l.Auth(&creds) + c.Assert(err, gc.IsNil) + c.Assert(auth.Token, gc.Equals, userInfo.Token) +} + +func (s *V3UserPassTestSuite) TestAuthWithTrustIDAndOtherScope(c *gc.C) { + var l Authenticator = &V3UserPass{} + scenarios := []Credentials{{ + User: "joe-user", + Secrets: "secrets", + TrustID: "trust-id", + TenantName: "tenant", + }, { + User: "joe-user", + Secrets: "secrets", + TrustID: "trust-id", + TenantID: "tenant-id", + }, { + User: "joe-user", + Secrets: "secrets", + TrustID: "trust-id", + Domain: "domain", + }} + for _, creds := range scenarios { + _, err := l.Auth(&creds) + c.Assert(err, gc.ErrorMatches, "trust authentication cannot be scoped to a project or domain") + } +} + func (s *V3UserPassTestSuite) TestAuthToDomainwithTenantNameAndTenantID(c *gc.C) { service := identityservice.NewV3UserPass() service.SetupHTTP(s.Mux) diff --git a/testservices/identityservice/v3userpass.go b/testservices/identityservice/v3userpass.go index 169860a..032137f 100644 --- a/testservices/identityservice/v3userpass.go +++ b/testservices/identityservice/v3userpass.go @@ -36,6 +36,9 @@ type V3UserPassRequest struct { Domain struct { Name string `json:"name,omitempty"` } `json:"domain"` + Trust struct { + ID string `json:"id"` + } `json:"trust"` } `json:"scope"` } `json:"auth"` }