package gophercloud /* AuthOptions stores information needed to authenticate to an OpenStack Cloud. You can populate one manually, or use a provider's AuthOptionsFromEnv() function to read relevant information from the standard environment variables. Pass one to a provider's AuthenticatedClient function to authenticate and obtain a ProviderClient representing an active session on that provider. Its fields are the union of those recognized by each identity implementation and provider. An example of manually providing authentication information: opts := gophercloud.AuthOptions{ IdentityEndpoint: "https://openstack.example.com:5000/v2.0", Username: "{username}", Password: "{password}", TenantID: "{tenant_id}", } provider, err := openstack.AuthenticatedClient(opts) An example of using AuthOptionsFromEnv(), where the environment variables can be read from a file, such as a standard openrc file: opts, err := openstack.AuthOptionsFromEnv() provider, err := openstack.AuthenticatedClient(opts) */ type AuthOptions struct { // IdentityEndpoint specifies the HTTP endpoint that is required to work with // the Identity API of the appropriate version. While it's ultimately needed by // all of the identity services, it will often be populated by a provider-level // function. // // The IdentityEndpoint is typically referred to as the "auth_url" or // "OS_AUTH_URL" in the information provided by the cloud operator. IdentityEndpoint string `json:"-"` // Username is required if using Identity V2 API. Consult with your provider's // control panel to discover your account's username. In Identity V3, either // UserID or a combination of Username and DomainID or DomainName are needed. Username string `json:"username,omitempty"` UserID string `json:"-"` Password string `json:"password,omitempty"` // At most one of DomainID and DomainName must be provided if using Username // with Identity V3. Otherwise, either are optional. DomainID string `json:"-"` DomainName string `json:"name,omitempty"` // The TenantID and TenantName fields are optional for the Identity V2 API. // The same fields are known as project_id and project_name in the Identity // V3 API, but are collected as TenantID and TenantName here in both cases. // Some providers allow you to specify a TenantName instead of the TenantId. // Some require both. Your provider's authentication policies will determine // how these fields influence authentication. // If DomainID or DomainName are provided, they will also apply to TenantName. // It is not currently possible to authenticate with Username and a Domain // and scope to a Project in a different Domain by using TenantName. To // accomplish that, the ProjectID will need to be provided as the TenantID // option. TenantID string `json:"tenantId,omitempty"` TenantName string `json:"tenantName,omitempty"` // AllowReauth should be set to true if you grant permission for Gophercloud to // cache your credentials in memory, and to allow Gophercloud to attempt to // re-authenticate automatically if/when your token expires. If you set it to // false, it will not cache these settings, but re-authentication will not be // possible. This setting defaults to false. // // NOTE: The reauth function will try to re-authenticate endlessly if left // unchecked. The way to limit the number of attempts is to provide a custom // HTTP client to the provider client and provide a transport that implements // the RoundTripper interface and stores the number of failed retries. For an // example of this, see here: // https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311 AllowReauth bool `json:"-"` // TokenID allows users to authenticate (possibly as another user) with an // authentication token ID. TokenID string `json:"-"` // Scope determines the scoping of the authentication request. Scope *AuthScope `json:"-"` // Authentication through Application Credentials requires supplying name, project and secret // For project we can use TenantID ApplicationCredentialID string `json:"-"` ApplicationCredentialName string `json:"-"` ApplicationCredentialSecret string `json:"-"` } // AuthScope allows a created token to be limited to a specific domain or project. type AuthScope struct { ProjectID string ProjectName string DomainID string DomainName string } // ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder // interface in the v2 tokens package func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) { // Populate the request map. authMap := make(map[string]interface{}) if opts.Username != "" { if opts.Password != "" { authMap["passwordCredentials"] = map[string]interface{}{ "username": opts.Username, "password": opts.Password, } } else { return nil, ErrMissingInput{Argument: "Password"} } } else if opts.TokenID != "" { authMap["token"] = map[string]interface{}{ "id": opts.TokenID, } } else { return nil, ErrMissingInput{Argument: "Username"} } if opts.TenantID != "" { authMap["tenantId"] = opts.TenantID } if opts.TenantName != "" { authMap["tenantName"] = opts.TenantName } return map[string]interface{}{"auth": authMap}, nil } func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) { type domainReq struct { ID *string `json:"id,omitempty"` Name *string `json:"name,omitempty"` } type projectReq struct { Domain *domainReq `json:"domain,omitempty"` Name *string `json:"name,omitempty"` ID *string `json:"id,omitempty"` } type userReq struct { ID *string `json:"id,omitempty"` Name *string `json:"name,omitempty"` Password string `json:"password,omitempty"` Domain *domainReq `json:"domain,omitempty"` } type passwordReq struct { User userReq `json:"user"` } type tokenReq struct { ID string `json:"id"` } type applicationCredentialReq struct { ID *string `json:"id,omitempty"` Name *string `json:"name,omitempty"` User *userReq `json:"user,omitempty"` Secret *string `json:"secret,omitempty"` } type identityReq struct { Methods []string `json:"methods"` Password *passwordReq `json:"password,omitempty"` Token *tokenReq `json:"token,omitempty"` ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"` } type authReq struct { Identity identityReq `json:"identity"` } type request struct { Auth authReq `json:"auth"` } // Populate the request structure based on the provided arguments. Create and return an error // if insufficient or incompatible information is present. var req request if opts.Password == "" { if opts.TokenID != "" { // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication // parameters. if opts.Username != "" { return nil, ErrUsernameWithToken{} } if opts.UserID != "" { return nil, ErrUserIDWithToken{} } if opts.DomainID != "" { return nil, ErrDomainIDWithToken{} } if opts.DomainName != "" { return nil, ErrDomainNameWithToken{} } // Configure the request for Token authentication. req.Auth.Identity.Methods = []string{"token"} req.Auth.Identity.Token = &tokenReq{ ID: opts.TokenID, } } else if opts.ApplicationCredentialID != "" { // Configure the request for ApplicationCredentialID authentication. // https://github.com/openstack/keystoneauth/blob/stable/rocky/keystoneauth1/identity/v3/application_credential.py#L48-L67 // There are three kinds of possible application_credential requests // 1. application_credential id + secret // 2. application_credential name + secret + user_id // 3. application_credential name + secret + username + domain_id / domain_name if opts.ApplicationCredentialSecret == "" { return nil, ErrAppCredMissingSecret{} } req.Auth.Identity.Methods = []string{"application_credential"} req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{ ID: &opts.ApplicationCredentialID, Secret: &opts.ApplicationCredentialSecret, } } else if opts.ApplicationCredentialName != "" { if opts.ApplicationCredentialSecret == "" { return nil, ErrAppCredMissingSecret{} } var userRequest *userReq if opts.UserID != "" { // UserID could be used without the domain information userRequest = &userReq{ ID: &opts.UserID, } } if userRequest == nil && opts.Username == "" { // Make sure that Username or UserID are provided return nil, ErrUsernameOrUserID{} } if userRequest == nil && opts.DomainID != "" { userRequest = &userReq{ Name: &opts.Username, Domain: &domainReq{ID: &opts.DomainID}, } } if userRequest == nil && opts.DomainName != "" { userRequest = &userReq{ Name: &opts.Username, Domain: &domainReq{Name: &opts.DomainName}, } } // Make sure that DomainID or DomainName are provided among Username if userRequest == nil { return nil, ErrDomainIDOrDomainName{} } req.Auth.Identity.Methods = []string{"application_credential"} req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{ Name: &opts.ApplicationCredentialName, User: userRequest, Secret: &opts.ApplicationCredentialSecret, } } else { // If no password or token ID or ApplicationCredential are available, authentication can't continue. return nil, ErrMissingPassword{} } } else { // Password authentication. req.Auth.Identity.Methods = []string{"password"} // At least one of Username and UserID must be specified. if opts.Username == "" && opts.UserID == "" { return nil, ErrUsernameOrUserID{} } if opts.Username != "" { // If Username is provided, UserID may not be provided. if opts.UserID != "" { return nil, ErrUsernameOrUserID{} } // Either DomainID or DomainName must also be specified. if opts.DomainID == "" && opts.DomainName == "" { return nil, ErrDomainIDOrDomainName{} } if opts.DomainID != "" { if opts.DomainName != "" { return nil, ErrDomainIDOrDomainName{} } // Configure the request for Username and Password authentication with a DomainID. req.Auth.Identity.Password = &passwordReq{ User: userReq{ Name: &opts.Username, Password: opts.Password, Domain: &domainReq{ID: &opts.DomainID}, }, } } if opts.DomainName != "" { // Configure the request for Username and Password authentication with a DomainName. req.Auth.Identity.Password = &passwordReq{ User: userReq{ Name: &opts.Username, Password: opts.Password, Domain: &domainReq{Name: &opts.DomainName}, }, } } } if opts.UserID != "" { // If UserID is specified, neither DomainID nor DomainName may be. if opts.DomainID != "" { return nil, ErrDomainIDWithUserID{} } if opts.DomainName != "" { return nil, ErrDomainNameWithUserID{} } // Configure the request for UserID and Password authentication. req.Auth.Identity.Password = &passwordReq{ User: userReq{ID: &opts.UserID, Password: opts.Password}, } } } b, err := BuildRequestBody(req, "") if err != nil { return nil, err } if len(scope) != 0 { b["auth"].(map[string]interface{})["scope"] = scope } return b, nil } func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { // For backwards compatibility. // If AuthOptions.Scope was not set, try to determine it. // This works well for common scenarios. if opts.Scope == nil { opts.Scope = new(AuthScope) if opts.TenantID != "" { opts.Scope.ProjectID = opts.TenantID } else { if opts.TenantName != "" { opts.Scope.ProjectName = opts.TenantName opts.Scope.DomainID = opts.DomainID opts.Scope.DomainName = opts.DomainName } } } if opts.Scope.ProjectName != "" { // ProjectName provided: either DomainID or DomainName must also be supplied. // ProjectID may not be supplied. if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" { return nil, ErrScopeDomainIDOrDomainName{} } if opts.Scope.ProjectID != "" { return nil, ErrScopeProjectIDOrProjectName{} } if opts.Scope.DomainID != "" { // ProjectName + DomainID return map[string]interface{}{ "project": map[string]interface{}{ "name": &opts.Scope.ProjectName, "domain": map[string]interface{}{"id": &opts.Scope.DomainID}, }, }, nil } if opts.Scope.DomainName != "" { // ProjectName + DomainName return map[string]interface{}{ "project": map[string]interface{}{ "name": &opts.Scope.ProjectName, "domain": map[string]interface{}{"name": &opts.Scope.DomainName}, }, }, nil } } else if opts.Scope.ProjectID != "" { // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided. if opts.Scope.DomainID != "" { return nil, ErrScopeProjectIDAlone{} } if opts.Scope.DomainName != "" { return nil, ErrScopeProjectIDAlone{} } // ProjectID return map[string]interface{}{ "project": map[string]interface{}{ "id": &opts.Scope.ProjectID, }, }, nil } else if opts.Scope.DomainID != "" { // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided. if opts.Scope.DomainName != "" { return nil, ErrScopeDomainIDOrDomainName{} } // DomainID return map[string]interface{}{ "domain": map[string]interface{}{ "id": &opts.Scope.DomainID, }, }, nil } else if opts.Scope.DomainName != "" { // DomainName return map[string]interface{}{ "domain": map[string]interface{}{ "name": &opts.Scope.DomainName, }, }, nil } return nil, nil } func (opts AuthOptions) CanReauth() bool { return opts.AllowReauth }