github.com/gophercloud/gophercloud@v1.11.0/auth_options.go (about)

     1  package gophercloud
     2  
     3  /*
     4  AuthOptions stores information needed to authenticate to an OpenStack Cloud.
     5  You can populate one manually, or use a provider's AuthOptionsFromEnv() function
     6  to read relevant information from the standard environment variables. Pass one
     7  to a provider's AuthenticatedClient function to authenticate and obtain a
     8  ProviderClient representing an active session on that provider.
     9  
    10  Its fields are the union of those recognized by each identity implementation and
    11  provider.
    12  
    13  An example of manually providing authentication information:
    14  
    15  	opts := gophercloud.AuthOptions{
    16  	  IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
    17  	  Username: "{username}",
    18  	  Password: "{password}",
    19  	  TenantID: "{tenant_id}",
    20  	}
    21  
    22  	provider, err := openstack.AuthenticatedClient(opts)
    23  
    24  An example of using AuthOptionsFromEnv(), where the environment variables can
    25  be read from a file, such as a standard openrc file:
    26  
    27  	opts, err := openstack.AuthOptionsFromEnv()
    28  	provider, err := openstack.AuthenticatedClient(opts)
    29  */
    30  type AuthOptions struct {
    31  	// IdentityEndpoint specifies the HTTP endpoint that is required to work with
    32  	// the Identity API of the appropriate version. While it's ultimately needed by
    33  	// all of the identity services, it will often be populated by a provider-level
    34  	// function.
    35  	//
    36  	// The IdentityEndpoint is typically referred to as the "auth_url" or
    37  	// "OS_AUTH_URL" in the information provided by the cloud operator.
    38  	IdentityEndpoint string `json:"-"`
    39  
    40  	// Username is required if using Identity V2 API. Consult with your provider's
    41  	// control panel to discover your account's username. In Identity V3, either
    42  	// UserID or a combination of Username and DomainID or DomainName are needed.
    43  	Username string `json:"username,omitempty"`
    44  	UserID   string `json:"-"`
    45  
    46  	Password string `json:"password,omitempty"`
    47  
    48  	// Passcode is used in TOTP authentication method
    49  	Passcode string `json:"passcode,omitempty"`
    50  
    51  	// At most one of DomainID and DomainName must be provided if using Username
    52  	// with Identity V3. Otherwise, either are optional.
    53  	DomainID   string `json:"-"`
    54  	DomainName string `json:"name,omitempty"`
    55  
    56  	// The TenantID and TenantName fields are optional for the Identity V2 API.
    57  	// The same fields are known as project_id and project_name in the Identity
    58  	// V3 API, but are collected as TenantID and TenantName here in both cases.
    59  	// Some providers allow you to specify a TenantName instead of the TenantId.
    60  	// Some require both. Your provider's authentication policies will determine
    61  	// how these fields influence authentication.
    62  	// If DomainID or DomainName are provided, they will also apply to TenantName.
    63  	// It is not currently possible to authenticate with Username and a Domain
    64  	// and scope to a Project in a different Domain by using TenantName. To
    65  	// accomplish that, the ProjectID will need to be provided as the TenantID
    66  	// option.
    67  	TenantID   string `json:"tenantId,omitempty"`
    68  	TenantName string `json:"tenantName,omitempty"`
    69  
    70  	// AllowReauth should be set to true if you grant permission for Gophercloud to
    71  	// cache your credentials in memory, and to allow Gophercloud to attempt to
    72  	// re-authenticate automatically if/when your token expires.  If you set it to
    73  	// false, it will not cache these settings, but re-authentication will not be
    74  	// possible.  This setting defaults to false.
    75  	//
    76  	// NOTE: The reauth function will try to re-authenticate endlessly if left
    77  	// unchecked. The way to limit the number of attempts is to provide a custom
    78  	// HTTP client to the provider client and provide a transport that implements
    79  	// the RoundTripper interface and stores the number of failed retries. For an
    80  	// example of this, see here:
    81  	// https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
    82  	AllowReauth bool `json:"-"`
    83  
    84  	// TokenID allows users to authenticate (possibly as another user) with an
    85  	// authentication token ID.
    86  	TokenID string `json:"-"`
    87  
    88  	// Scope determines the scoping of the authentication request.
    89  	Scope *AuthScope `json:"-"`
    90  
    91  	// Authentication through Application Credentials requires supplying name, project and secret
    92  	// For project we can use TenantID
    93  	ApplicationCredentialID     string `json:"-"`
    94  	ApplicationCredentialName   string `json:"-"`
    95  	ApplicationCredentialSecret string `json:"-"`
    96  }
    97  
    98  // AuthScope allows a created token to be limited to a specific domain or project.
    99  type AuthScope struct {
   100  	ProjectID   string
   101  	ProjectName string
   102  	DomainID    string
   103  	DomainName  string
   104  	System      bool
   105  }
   106  
   107  // ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
   108  // interface in the v2 tokens package
   109  func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
   110  	// Populate the request map.
   111  	authMap := make(map[string]interface{})
   112  
   113  	if opts.Username != "" {
   114  		if opts.Password != "" {
   115  			authMap["passwordCredentials"] = map[string]interface{}{
   116  				"username": opts.Username,
   117  				"password": opts.Password,
   118  			}
   119  		} else {
   120  			return nil, ErrMissingInput{Argument: "Password"}
   121  		}
   122  	} else if opts.TokenID != "" {
   123  		authMap["token"] = map[string]interface{}{
   124  			"id": opts.TokenID,
   125  		}
   126  	} else {
   127  		return nil, ErrMissingInput{Argument: "Username"}
   128  	}
   129  
   130  	if opts.TenantID != "" {
   131  		authMap["tenantId"] = opts.TenantID
   132  	}
   133  	if opts.TenantName != "" {
   134  		authMap["tenantName"] = opts.TenantName
   135  	}
   136  
   137  	return map[string]interface{}{"auth": authMap}, nil
   138  }
   139  
   140  // ToTokenV3CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
   141  // interface in the v3 tokens package
   142  func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
   143  	type domainReq struct {
   144  		ID   *string `json:"id,omitempty"`
   145  		Name *string `json:"name,omitempty"`
   146  	}
   147  
   148  	type projectReq struct {
   149  		Domain *domainReq `json:"domain,omitempty"`
   150  		Name   *string    `json:"name,omitempty"`
   151  		ID     *string    `json:"id,omitempty"`
   152  	}
   153  
   154  	type userReq struct {
   155  		ID       *string    `json:"id,omitempty"`
   156  		Name     *string    `json:"name,omitempty"`
   157  		Password *string    `json:"password,omitempty"`
   158  		Passcode *string    `json:"passcode,omitempty"`
   159  		Domain   *domainReq `json:"domain,omitempty"`
   160  	}
   161  
   162  	type passwordReq struct {
   163  		User userReq `json:"user"`
   164  	}
   165  
   166  	type tokenReq struct {
   167  		ID string `json:"id"`
   168  	}
   169  
   170  	type applicationCredentialReq struct {
   171  		ID     *string  `json:"id,omitempty"`
   172  		Name   *string  `json:"name,omitempty"`
   173  		User   *userReq `json:"user,omitempty"`
   174  		Secret *string  `json:"secret,omitempty"`
   175  	}
   176  
   177  	type totpReq struct {
   178  		User *userReq `json:"user,omitempty"`
   179  	}
   180  
   181  	type identityReq struct {
   182  		Methods               []string                  `json:"methods"`
   183  		Password              *passwordReq              `json:"password,omitempty"`
   184  		Token                 *tokenReq                 `json:"token,omitempty"`
   185  		ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
   186  		TOTP                  *totpReq                  `json:"totp,omitempty"`
   187  	}
   188  
   189  	type authReq struct {
   190  		Identity identityReq `json:"identity"`
   191  	}
   192  
   193  	type request struct {
   194  		Auth authReq `json:"auth"`
   195  	}
   196  
   197  	// Populate the request structure based on the provided arguments. Create and return an error
   198  	// if insufficient or incompatible information is present.
   199  	var req request
   200  
   201  	if opts.Password == "" && opts.Passcode == "" {
   202  		if opts.TokenID != "" {
   203  			// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
   204  			// parameters.
   205  			if opts.Username != "" {
   206  				return nil, ErrUsernameWithToken{}
   207  			}
   208  			if opts.UserID != "" {
   209  				return nil, ErrUserIDWithToken{}
   210  			}
   211  			if opts.DomainID != "" {
   212  				return nil, ErrDomainIDWithToken{}
   213  			}
   214  			if opts.DomainName != "" {
   215  				return nil, ErrDomainNameWithToken{}
   216  			}
   217  
   218  			// Configure the request for Token authentication.
   219  			req.Auth.Identity.Methods = []string{"token"}
   220  			req.Auth.Identity.Token = &tokenReq{
   221  				ID: opts.TokenID,
   222  			}
   223  
   224  		} else if opts.ApplicationCredentialID != "" {
   225  			// Configure the request for ApplicationCredentialID authentication.
   226  			// https://github.com/openstack/keystoneauth/blob/stable/rocky/keystoneauth1/identity/v3/application_credential.py#L48-L67
   227  			// There are three kinds of possible application_credential requests
   228  			// 1. application_credential id + secret
   229  			// 2. application_credential name + secret + user_id
   230  			// 3. application_credential name + secret + username + domain_id / domain_name
   231  			if opts.ApplicationCredentialSecret == "" {
   232  				return nil, ErrAppCredMissingSecret{}
   233  			}
   234  			req.Auth.Identity.Methods = []string{"application_credential"}
   235  			req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
   236  				ID:     &opts.ApplicationCredentialID,
   237  				Secret: &opts.ApplicationCredentialSecret,
   238  			}
   239  		} else if opts.ApplicationCredentialName != "" {
   240  			if opts.ApplicationCredentialSecret == "" {
   241  				return nil, ErrAppCredMissingSecret{}
   242  			}
   243  
   244  			var userRequest *userReq
   245  
   246  			if opts.UserID != "" {
   247  				// UserID could be used without the domain information
   248  				userRequest = &userReq{
   249  					ID: &opts.UserID,
   250  				}
   251  			}
   252  
   253  			if userRequest == nil && opts.Username == "" {
   254  				// Make sure that Username or UserID are provided
   255  				return nil, ErrUsernameOrUserID{}
   256  			}
   257  
   258  			if userRequest == nil && opts.DomainID != "" {
   259  				userRequest = &userReq{
   260  					Name:   &opts.Username,
   261  					Domain: &domainReq{ID: &opts.DomainID},
   262  				}
   263  			}
   264  
   265  			if userRequest == nil && opts.DomainName != "" {
   266  				userRequest = &userReq{
   267  					Name:   &opts.Username,
   268  					Domain: &domainReq{Name: &opts.DomainName},
   269  				}
   270  			}
   271  
   272  			// Make sure that DomainID or DomainName are provided among Username
   273  			if userRequest == nil {
   274  				return nil, ErrDomainIDOrDomainName{}
   275  			}
   276  
   277  			req.Auth.Identity.Methods = []string{"application_credential"}
   278  			req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
   279  				Name:   &opts.ApplicationCredentialName,
   280  				User:   userRequest,
   281  				Secret: &opts.ApplicationCredentialSecret,
   282  			}
   283  		} else {
   284  			// If no password or token ID or ApplicationCredential are available, authentication can't continue.
   285  			return nil, ErrMissingPassword{}
   286  		}
   287  	} else {
   288  		// Password authentication.
   289  		if opts.Password != "" {
   290  			req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "password")
   291  		}
   292  
   293  		// TOTP authentication.
   294  		if opts.Passcode != "" {
   295  			req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "totp")
   296  		}
   297  
   298  		// At least one of Username and UserID must be specified.
   299  		if opts.Username == "" && opts.UserID == "" {
   300  			return nil, ErrUsernameOrUserID{}
   301  		}
   302  
   303  		if opts.Username != "" {
   304  			// If Username is provided, UserID may not be provided.
   305  			if opts.UserID != "" {
   306  				return nil, ErrUsernameOrUserID{}
   307  			}
   308  
   309  			// Either DomainID or DomainName must also be specified.
   310  			if opts.DomainID == "" && opts.DomainName == "" {
   311  				return nil, ErrDomainIDOrDomainName{}
   312  			}
   313  
   314  			if opts.DomainID != "" {
   315  				if opts.DomainName != "" {
   316  					return nil, ErrDomainIDOrDomainName{}
   317  				}
   318  
   319  				// Configure the request for Username and Password authentication with a DomainID.
   320  				if opts.Password != "" {
   321  					req.Auth.Identity.Password = &passwordReq{
   322  						User: userReq{
   323  							Name:     &opts.Username,
   324  							Password: &opts.Password,
   325  							Domain:   &domainReq{ID: &opts.DomainID},
   326  						},
   327  					}
   328  				}
   329  				if opts.Passcode != "" {
   330  					req.Auth.Identity.TOTP = &totpReq{
   331  						User: &userReq{
   332  							Name:     &opts.Username,
   333  							Passcode: &opts.Passcode,
   334  							Domain:   &domainReq{ID: &opts.DomainID},
   335  						},
   336  					}
   337  				}
   338  			}
   339  
   340  			if opts.DomainName != "" {
   341  				// Configure the request for Username and Password authentication with a DomainName.
   342  				if opts.Password != "" {
   343  					req.Auth.Identity.Password = &passwordReq{
   344  						User: userReq{
   345  							Name:     &opts.Username,
   346  							Password: &opts.Password,
   347  							Domain:   &domainReq{Name: &opts.DomainName},
   348  						},
   349  					}
   350  				}
   351  
   352  				if opts.Passcode != "" {
   353  					req.Auth.Identity.TOTP = &totpReq{
   354  						User: &userReq{
   355  							Name:     &opts.Username,
   356  							Passcode: &opts.Passcode,
   357  							Domain:   &domainReq{Name: &opts.DomainName},
   358  						},
   359  					}
   360  				}
   361  			}
   362  		}
   363  
   364  		if opts.UserID != "" {
   365  			// If UserID is specified, neither DomainID nor DomainName may be.
   366  			if opts.DomainID != "" {
   367  				return nil, ErrDomainIDWithUserID{}
   368  			}
   369  			if opts.DomainName != "" {
   370  				return nil, ErrDomainNameWithUserID{}
   371  			}
   372  
   373  			// Configure the request for UserID and Password authentication.
   374  			if opts.Password != "" {
   375  				req.Auth.Identity.Password = &passwordReq{
   376  					User: userReq{
   377  						ID:       &opts.UserID,
   378  						Password: &opts.Password,
   379  					},
   380  				}
   381  			}
   382  
   383  			if opts.Passcode != "" {
   384  				req.Auth.Identity.TOTP = &totpReq{
   385  					User: &userReq{
   386  						ID:       &opts.UserID,
   387  						Passcode: &opts.Passcode,
   388  					},
   389  				}
   390  			}
   391  		}
   392  	}
   393  
   394  	b, err := BuildRequestBody(req, "")
   395  	if err != nil {
   396  		return nil, err
   397  	}
   398  
   399  	if len(scope) != 0 {
   400  		b["auth"].(map[string]interface{})["scope"] = scope
   401  	}
   402  
   403  	return b, nil
   404  }
   405  
   406  // ToTokenV3ScopeMap builds a scope from AuthOptions and satisfies interface in
   407  // the v3 tokens package.
   408  func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
   409  	// For backwards compatibility.
   410  	// If AuthOptions.Scope was not set, try to determine it.
   411  	// This works well for common scenarios.
   412  	if opts.Scope == nil {
   413  		opts.Scope = new(AuthScope)
   414  		if opts.TenantID != "" {
   415  			opts.Scope.ProjectID = opts.TenantID
   416  		} else {
   417  			if opts.TenantName != "" {
   418  				opts.Scope.ProjectName = opts.TenantName
   419  				opts.Scope.DomainID = opts.DomainID
   420  				opts.Scope.DomainName = opts.DomainName
   421  			}
   422  		}
   423  	}
   424  
   425  	if opts.Scope.System {
   426  		return map[string]interface{}{
   427  			"system": map[string]interface{}{
   428  				"all": true,
   429  			},
   430  		}, nil
   431  	}
   432  
   433  	if opts.Scope.ProjectName != "" {
   434  		// ProjectName provided: either DomainID or DomainName must also be supplied.
   435  		// ProjectID may not be supplied.
   436  		if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" {
   437  			return nil, ErrScopeDomainIDOrDomainName{}
   438  		}
   439  		if opts.Scope.ProjectID != "" {
   440  			return nil, ErrScopeProjectIDOrProjectName{}
   441  		}
   442  
   443  		if opts.Scope.DomainID != "" {
   444  			// ProjectName + DomainID
   445  			return map[string]interface{}{
   446  				"project": map[string]interface{}{
   447  					"name":   &opts.Scope.ProjectName,
   448  					"domain": map[string]interface{}{"id": &opts.Scope.DomainID},
   449  				},
   450  			}, nil
   451  		}
   452  
   453  		if opts.Scope.DomainName != "" {
   454  			// ProjectName + DomainName
   455  			return map[string]interface{}{
   456  				"project": map[string]interface{}{
   457  					"name":   &opts.Scope.ProjectName,
   458  					"domain": map[string]interface{}{"name": &opts.Scope.DomainName},
   459  				},
   460  			}, nil
   461  		}
   462  	} else if opts.Scope.ProjectID != "" {
   463  		// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
   464  		if opts.Scope.DomainID != "" {
   465  			return nil, ErrScopeProjectIDAlone{}
   466  		}
   467  		if opts.Scope.DomainName != "" {
   468  			return nil, ErrScopeProjectIDAlone{}
   469  		}
   470  
   471  		// ProjectID
   472  		return map[string]interface{}{
   473  			"project": map[string]interface{}{
   474  				"id": &opts.Scope.ProjectID,
   475  			},
   476  		}, nil
   477  	} else if opts.Scope.DomainID != "" {
   478  		// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
   479  		if opts.Scope.DomainName != "" {
   480  			return nil, ErrScopeDomainIDOrDomainName{}
   481  		}
   482  
   483  		// DomainID
   484  		return map[string]interface{}{
   485  			"domain": map[string]interface{}{
   486  				"id": &opts.Scope.DomainID,
   487  			},
   488  		}, nil
   489  	} else if opts.Scope.DomainName != "" {
   490  		// DomainName
   491  		return map[string]interface{}{
   492  			"domain": map[string]interface{}{
   493  				"name": &opts.Scope.DomainName,
   494  			},
   495  		}, nil
   496  	}
   497  
   498  	return nil, nil
   499  }
   500  
   501  func (opts AuthOptions) CanReauth() bool {
   502  	if opts.Passcode != "" {
   503  		// cannot reauth using TOTP passcode
   504  		return false
   505  	}
   506  
   507  	return opts.AllowReauth
   508  }
   509  
   510  // ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder
   511  // interface in the v3 tokens package.
   512  func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error) {
   513  	return nil, nil
   514  }