diff --git a/auth_aksk_options.go b/auth_aksk_options.go index 880282c43..3c374a632 100644 --- a/auth_aksk_options.go +++ b/auth_aksk_options.go @@ -24,6 +24,15 @@ type AKSKAuthOptions struct { AccessKey string //Access Key SecretKey string //Secret key + + // AgencyNmae is the name of agnecy + AgencyName string + + // AgencyDomainName is the name of domain who created the agency + AgencyDomainName string + + // DelegatedProject is the name of delegated project + DelegatedProject string } // Implements the method of AuthOptionsProvider diff --git a/auth_options.go b/auth_options.go index 243a4535a..a7a23129c 100644 --- a/auth_options.go +++ b/auth_options.go @@ -298,6 +298,10 @@ func (opts *AuthOptions) AuthTokenID() string { return "" } +func (opts *AuthOptions) AuthHeaderDomainID() string { + return "" +} + // Implements the method of AuthOptionsProvider func (opts AuthOptions) GetIdentityEndpoint() string { return opts.IdentityEndpoint @@ -381,6 +385,7 @@ func (scope *scopeInfo) BuildTokenV3ScopeMap() (map[string]interface{}, error) { type AgencyAuthOptions struct { TokenID string + DomainID string AgencyName string AgencyDomainName string DelegatedProject string @@ -394,6 +399,10 @@ func (opts *AgencyAuthOptions) AuthTokenID() string { return opts.TokenID } +func (opts *AgencyAuthOptions) AuthHeaderDomainID() string { + return opts.DomainID +} + func (opts *AgencyAuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { scope := scopeInfo{ ProjectName: opts.DelegatedProject, diff --git a/openstack/client.go b/openstack/client.go index 4ea27d804..5025da4be 100644 --- a/openstack/client.go +++ b/openstack/client.go @@ -9,6 +9,7 @@ import ( "github.com/huaweicloud/golangsdk" tokens2 "github.com/huaweicloud/golangsdk/openstack/identity/v2/tokens" + "github.com/huaweicloud/golangsdk/openstack/identity/v3/domains" "github.com/huaweicloud/golangsdk/openstack/identity/v3/endpoints" "github.com/huaweicloud/golangsdk/openstack/identity/v3/projects" "github.com/huaweicloud/golangsdk/openstack/identity/v3/services" @@ -130,7 +131,12 @@ func Authenticate(client *golangsdk.ProviderClient, options golangsdk.AuthOption akskAuthOptions, isAkSkOptions := options.(golangsdk.AKSKAuthOptions) if isAkSkOptions { - return v3AKSKAuth(client, endpoint, akskAuthOptions, golangsdk.EndpointOpts{}) + if akskAuthOptions.AgencyDomainName != "" && akskAuthOptions.AgencyName != "" { + return authWithAgencyByAKSK(client, endpoint, akskAuthOptions, golangsdk.EndpointOpts{}) + } else { + return v3AKSKAuth(client, endpoint, akskAuthOptions, golangsdk.EndpointOpts{}) + } + } else { return fmt.Errorf("Unrecognized auth options provider: %s", reflect.TypeOf(options)) } @@ -373,6 +379,124 @@ func v3AKSKAuth(client *golangsdk.ProviderClient, endpoint string, options golan return nil } +func authWithAgencyByAKSK(client *golangsdk.ProviderClient, endpoint string, opts golangsdk.AKSKAuthOptions, eo golangsdk.EndpointOpts) error { + + err := v3AKSKAuth(client, endpoint, opts, eo) + if err != nil { + return err + } + + v3Client, err := NewIdentityV3(client, eo) + if err != nil { + return err + } + + domainID, err := getDomainID(opts.Domain, v3Client) + if err != nil { + return err + } + + opts2 := golangsdk.AgencyAuthOptions{ + DomainID: domainID, + AgencyName: opts.AgencyName, + AgencyDomainName: opts.AgencyDomainName, + DelegatedProject: opts.DelegatedProject, + } + result := tokens3.Create(v3Client, &opts2) + token, err := result.ExtractToken() + if err != nil { + return err + } + + project, err := result.ExtractProject() + if err != nil { + return err + } + + catalog, err := result.ExtractServiceCatalog() + if err != nil { + return err + } + + client.TokenID = token.ID + if project != nil { + client.ProjectID = project.ID + } + + client.ReauthFunc = func() error { + client.TokenID = "" + return authWithAgencyByAKSK(client, endpoint, opts, eo) + } + + client.EndpointLocator = func(opts golangsdk.EndpointOpts) (string, error) { + return V3EndpointURL(catalog, opts) + } + + client.AKSKAuthOptions.AccessKey = "" + return nil +} + +func getDomainID(name string, client *golangsdk.ServiceClient) (string, error) { + old := client.Endpoint + defer func() { client.Endpoint = old }() + + endpoint, err := client.EndpointLocator( + golangsdk.EndpointOpts{ + Type: "identity", + Availability: golangsdk.AvailabilityPublic, + }) + if err != nil { + if v, ok := err.(ErrMultipleMatchingEndpointsV3); ok { + e := "" + for _, i := range v.Endpoints { + if i.Region == "" { + e = golangsdk.NormalizeURL(i.URL) + "auth/" + break + } + } + + if e == "" { + return "", err + } + client.Endpoint = e + } else { + return "", err + } + } else { + client.Endpoint = endpoint + "auth/" + } + + opts := domains.ListOpts{ + Name: name, + } + allPages, err := domains.List(client, &opts).AllPages() + if err != nil { + return "", fmt.Errorf("List domains failed, err=%s", err) + } + + all, err := domains.ExtractDomains(allPages) + if err != nil { + return "", fmt.Errorf("Extract domains failed, err=%s", err) + } + + count := len(all) + switch count { + case 0: + err := &golangsdk.ErrResourceNotFound{} + err.ResourceType = "iam" + err.Name = name + return "", err + case 1: + return all[0].ID, nil + default: + err := &golangsdk.ErrMultipleResourcesFound{} + err.ResourceType = "iam" + err.Name = name + err.Count = count + return "", err + } +} + // NewIdentityV2 creates a ServiceClient that may be used to interact with the // v2 identity service. func NewIdentityV2(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*golangsdk.ServiceClient, error) { diff --git a/openstack/identity/v3/tokens/requests.go b/openstack/identity/v3/tokens/requests.go index 381ec2689..ef73d0002 100644 --- a/openstack/identity/v3/tokens/requests.go +++ b/openstack/identity/v3/tokens/requests.go @@ -19,6 +19,7 @@ type AuthOptionsBuilder interface { ToTokenV3ScopeMap() (map[string]interface{}, error) CanReauth() bool AuthTokenID() string + AuthHeaderDomainID() string } // AuthOptions represents options for authenticating a user. @@ -149,6 +150,10 @@ func (opts *AuthOptions) AuthTokenID() string { return "" } +func (opts *AuthOptions) AuthHeaderDomainID() string { + return "" +} + func subjectTokenHeaders(c *golangsdk.ServiceClient, subjectToken string) map[string]string { return map[string]string{ "X-Subject-Token": subjectToken, @@ -171,7 +176,10 @@ func Create(c *golangsdk.ServiceClient, opts AuthOptionsBuilder) (r CreateResult } resp, err := c.Post(tokenURL(c), b, &r.Body, &golangsdk.RequestOpts{ - MoreHeaders: map[string]string{"X-Auth-Token": opts.AuthTokenID()}, + MoreHeaders: map[string]string{ + "X-Auth-Token": opts.AuthTokenID(), + "X-Domain-Id": opts.AuthHeaderDomainID(), + }, }) r.Err = err if resp != nil {