github.com/huaweicloud/golangsdk@v0.0.0-20210831081626-d823fe11ceba/auth_options.go (about)

     1  package golangsdk
     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 := golangsdk.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  	// At most one of DomainID and DomainName must be provided if using Username
    49  	// with Identity V3. Otherwise, either are optional.
    50  	DomainID   string `json:"-"`
    51  	DomainName string `json:"name,omitempty"`
    52  
    53  	// The TenantID and TenantName fields are optional for the Identity V2 API.
    54  	// The same fields are known as project_id and project_name in the Identity
    55  	// V3 API, but are collected as TenantID and TenantName here in both cases.
    56  	// Some providers allow you to specify a TenantName instead of the TenantId.
    57  	// Some require both. Your provider's authentication policies will determine
    58  	// how these fields influence authentication.
    59  	// If DomainID or DomainName are provided, they will also apply to TenantName.
    60  	// It is not currently possible to authenticate with Username and a Domain
    61  	// and scope to a Project in a different Domain by using TenantName. To
    62  	// accomplish that, the ProjectID will need to be provided as the TenantID
    63  	// option.
    64  	TenantID   string `json:"tenantId,omitempty"`
    65  	TenantName string `json:"tenantName,omitempty"`
    66  
    67  	// AllowReauth should be set to true if you grant permission for Gophercloud to
    68  	// cache your credentials in memory, and to allow Gophercloud to attempt to
    69  	// re-authenticate automatically if/when your token expires.  If you set it to
    70  	// false, it will not cache these settings, but re-authentication will not be
    71  	// possible.  This setting defaults to false.
    72  	//
    73  	// NOTE: The reauth function will try to re-authenticate endlessly if left
    74  	// unchecked. The way to limit the number of attempts is to provide a custom
    75  	// HTTP client to the provider client and provide a transport that implements
    76  	// the RoundTripper interface and stores the number of failed retries. For an
    77  	// example of this, see here:
    78  	// https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
    79  	AllowReauth bool `json:"-"`
    80  
    81  	// TokenID allows users to authenticate (possibly as another user) with an
    82  	// authentication token ID.
    83  	TokenID string `json:"-"`
    84  
    85  	// AgencyNmae is the name of agnecy
    86  	AgencyName string `json:"-"`
    87  
    88  	// AgencyDomainName is the name of domain who created the agency
    89  	AgencyDomainName string `json:"-"`
    90  
    91  	// DelegatedProject is the name of delegated project
    92  	DelegatedProject string `json:"-"`
    93  }
    94  
    95  // ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
    96  // interface in the v2 tokens package
    97  func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
    98  	// Populate the request map.
    99  	authMap := make(map[string]interface{})
   100  
   101  	if opts.Username != "" {
   102  		if opts.Password != "" {
   103  			authMap["passwordCredentials"] = map[string]interface{}{
   104  				"username": opts.Username,
   105  				"password": opts.Password,
   106  			}
   107  		} else {
   108  			return nil, ErrMissingInput{Argument: "Password"}
   109  		}
   110  	} else if opts.TokenID != "" {
   111  		authMap["token"] = map[string]interface{}{
   112  			"id": opts.TokenID,
   113  		}
   114  	} else {
   115  		return nil, ErrMissingInput{Argument: "Username"}
   116  	}
   117  
   118  	if opts.TenantID != "" {
   119  		authMap["tenantId"] = opts.TenantID
   120  	}
   121  	if opts.TenantName != "" {
   122  		authMap["tenantName"] = opts.TenantName
   123  	}
   124  
   125  	return map[string]interface{}{"auth": authMap}, nil
   126  }
   127  
   128  func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
   129  	type domainReq struct {
   130  		ID   *string `json:"id,omitempty"`
   131  		Name *string `json:"name,omitempty"`
   132  	}
   133  
   134  	type projectReq struct {
   135  		Domain *domainReq `json:"domain,omitempty"`
   136  		Name   *string    `json:"name,omitempty"`
   137  		ID     *string    `json:"id,omitempty"`
   138  	}
   139  
   140  	type userReq struct {
   141  		ID       *string    `json:"id,omitempty"`
   142  		Name     *string    `json:"name,omitempty"`
   143  		Password string     `json:"password"`
   144  		Domain   *domainReq `json:"domain,omitempty"`
   145  	}
   146  
   147  	type passwordReq struct {
   148  		User userReq `json:"user"`
   149  	}
   150  
   151  	type tokenReq struct {
   152  		ID string `json:"id"`
   153  	}
   154  
   155  	type identityReq struct {
   156  		Methods  []string     `json:"methods"`
   157  		Password *passwordReq `json:"password,omitempty"`
   158  		Token    *tokenReq    `json:"token,omitempty"`
   159  	}
   160  
   161  	type authReq struct {
   162  		Identity identityReq `json:"identity"`
   163  	}
   164  
   165  	type request struct {
   166  		Auth authReq `json:"auth"`
   167  	}
   168  
   169  	// Populate the request structure based on the provided arguments. Create and return an error
   170  	// if insufficient or incompatible information is present.
   171  	var req request
   172  
   173  	if opts.Password == "" {
   174  		if opts.TokenID != "" {
   175  			// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
   176  			// parameters.
   177  			if opts.Username != "" {
   178  				return nil, ErrUsernameWithToken{}
   179  			}
   180  			if opts.UserID != "" {
   181  				return nil, ErrUserIDWithToken{}
   182  			}
   183  
   184  			// Configure the request for Token authentication.
   185  			req.Auth.Identity.Methods = []string{"token"}
   186  			req.Auth.Identity.Token = &tokenReq{
   187  				ID: opts.TokenID,
   188  			}
   189  		} else {
   190  			// If no password or token ID are available, authentication can't continue.
   191  			return nil, ErrMissingPassword{}
   192  		}
   193  	} else {
   194  		// Password authentication.
   195  		req.Auth.Identity.Methods = []string{"password"}
   196  
   197  		// At least one of Username and UserID must be specified.
   198  		if opts.Username == "" && opts.UserID == "" {
   199  			return nil, ErrUsernameOrUserID{}
   200  		}
   201  
   202  		if opts.Username != "" {
   203  			// If Username is provided, UserID may not be provided.
   204  			if opts.UserID != "" {
   205  				return nil, ErrUsernameOrUserID{}
   206  			}
   207  
   208  			// Either DomainID or DomainName must also be specified.
   209  			if opts.DomainID == "" && opts.DomainName == "" {
   210  				return nil, ErrDomainIDOrDomainName{}
   211  			}
   212  
   213  			if opts.DomainID != "" {
   214  				if opts.DomainName != "" {
   215  					return nil, ErrDomainIDOrDomainName{}
   216  				}
   217  
   218  				// Configure the request for Username and Password authentication with a DomainID.
   219  				req.Auth.Identity.Password = &passwordReq{
   220  					User: userReq{
   221  						Name:     &opts.Username,
   222  						Password: opts.Password,
   223  						Domain:   &domainReq{ID: &opts.DomainID},
   224  					},
   225  				}
   226  			}
   227  
   228  			if opts.DomainName != "" {
   229  				// Configure the request for Username and Password authentication with a DomainName.
   230  				req.Auth.Identity.Password = &passwordReq{
   231  					User: userReq{
   232  						Name:     &opts.Username,
   233  						Password: opts.Password,
   234  						Domain:   &domainReq{Name: &opts.DomainName},
   235  					},
   236  				}
   237  			}
   238  		}
   239  
   240  		if opts.UserID != "" {
   241  			// If UserID is specified, neither DomainID nor DomainName may be.
   242  			if opts.DomainID != "" {
   243  				return nil, ErrDomainIDWithUserID{}
   244  			}
   245  			if opts.DomainName != "" {
   246  				return nil, ErrDomainNameWithUserID{}
   247  			}
   248  
   249  			// Configure the request for UserID and Password authentication.
   250  			req.Auth.Identity.Password = &passwordReq{
   251  				User: userReq{ID: &opts.UserID, Password: opts.Password},
   252  			}
   253  		}
   254  	}
   255  
   256  	b, err := BuildRequestBody(req, "")
   257  	if err != nil {
   258  		return nil, err
   259  	}
   260  
   261  	if len(scope) != 0 {
   262  		b["auth"].(map[string]interface{})["scope"] = scope
   263  	}
   264  
   265  	return b, nil
   266  }
   267  
   268  func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
   269  	var scope scopeInfo
   270  
   271  	if opts.TenantID != "" {
   272  		scope.ProjectID = opts.TenantID
   273  	} else {
   274  		if opts.TenantName != "" {
   275  			scope.ProjectName = opts.TenantName
   276  			scope.DomainID = opts.DomainID
   277  			scope.DomainName = opts.DomainName
   278  		} else {
   279  			// support scoping to domain
   280  			scope.DomainID = opts.DomainID
   281  			scope.DomainName = opts.DomainName
   282  		}
   283  	}
   284  	return scope.BuildTokenV3ScopeMap()
   285  }
   286  
   287  func (opts *AuthOptions) CanReauth() bool {
   288  	return opts.AllowReauth
   289  }
   290  
   291  func (opts *AuthOptions) AuthTokenID() string {
   292  	return ""
   293  }
   294  
   295  func (opts *AuthOptions) AuthHeaderDomainID() string {
   296  	return ""
   297  }
   298  
   299  // Implements the method of AuthOptionsProvider
   300  func (opts AuthOptions) GetIdentityEndpoint() string {
   301  	return opts.IdentityEndpoint
   302  }
   303  
   304  type scopeInfo struct {
   305  	ProjectID   string
   306  	ProjectName string
   307  	DomainID    string
   308  	DomainName  string
   309  }
   310  
   311  func (scope *scopeInfo) BuildTokenV3ScopeMap() (map[string]interface{}, error) {
   312  	if scope.ProjectName != "" {
   313  		// ProjectName provided: either DomainID or DomainName must also be supplied.
   314  		// ProjectID may not be supplied.
   315  		if scope.DomainID == "" && scope.DomainName == "" {
   316  			return nil, ErrScopeDomainIDOrDomainName{}
   317  		}
   318  		if scope.ProjectID != "" {
   319  			return nil, ErrScopeProjectIDOrProjectName{}
   320  		}
   321  
   322  		if scope.DomainID != "" {
   323  			// ProjectName + DomainID
   324  			return map[string]interface{}{
   325  				"project": map[string]interface{}{
   326  					"name":   &scope.ProjectName,
   327  					"domain": map[string]interface{}{"id": &scope.DomainID},
   328  				},
   329  			}, nil
   330  		}
   331  
   332  		if scope.DomainName != "" {
   333  			// ProjectName + DomainName
   334  			return map[string]interface{}{
   335  				"project": map[string]interface{}{
   336  					"name":   &scope.ProjectName,
   337  					"domain": map[string]interface{}{"name": &scope.DomainName},
   338  				},
   339  			}, nil
   340  		}
   341  	} else if scope.ProjectID != "" {
   342  		// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
   343  		if scope.DomainID != "" {
   344  			return nil, ErrScopeProjectIDAlone{}
   345  		}
   346  		if scope.DomainName != "" {
   347  			return nil, ErrScopeProjectIDAlone{}
   348  		}
   349  
   350  		// ProjectID
   351  		return map[string]interface{}{
   352  			"project": map[string]interface{}{
   353  				"id": &scope.ProjectID,
   354  			},
   355  		}, nil
   356  	} else if scope.DomainID != "" {
   357  		// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
   358  		if scope.DomainName != "" {
   359  			return nil, ErrScopeDomainIDOrDomainName{}
   360  		}
   361  
   362  		// DomainID
   363  		return map[string]interface{}{
   364  			"domain": map[string]interface{}{
   365  				"id": &scope.DomainID,
   366  			},
   367  		}, nil
   368  	} else if scope.DomainName != "" {
   369  		// DomainName
   370  		return map[string]interface{}{
   371  			"domain": map[string]interface{}{
   372  				"name": &scope.DomainName,
   373  			},
   374  		}, nil
   375  	}
   376  
   377  	return nil, nil
   378  }
   379  
   380  type AgencyAuthOptions struct {
   381  	TokenID          string
   382  	DomainID         string
   383  	AgencyName       string
   384  	AgencyDomainName string
   385  	DelegatedProject string
   386  }
   387  
   388  func (opts *AgencyAuthOptions) CanReauth() bool {
   389  	return false
   390  }
   391  
   392  func (opts *AgencyAuthOptions) AuthTokenID() string {
   393  	return opts.TokenID
   394  }
   395  
   396  func (opts *AgencyAuthOptions) AuthHeaderDomainID() string {
   397  	return opts.DomainID
   398  }
   399  
   400  func (opts *AgencyAuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
   401  	scope := scopeInfo{
   402  		ProjectName: opts.DelegatedProject,
   403  		DomainName:  opts.AgencyDomainName,
   404  	}
   405  
   406  	return scope.BuildTokenV3ScopeMap()
   407  }
   408  
   409  func (opts *AgencyAuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
   410  	type assumeRoleReq struct {
   411  		DomainName string `json:"domain_name"`
   412  		AgencyName string `json:"xrole_name"`
   413  	}
   414  
   415  	type identityReq struct {
   416  		Methods    []string      `json:"methods"`
   417  		AssumeRole assumeRoleReq `json:"assume_role"`
   418  	}
   419  
   420  	type authReq struct {
   421  		Identity identityReq `json:"identity"`
   422  	}
   423  
   424  	var req authReq
   425  	req.Identity.Methods = []string{"assume_role"}
   426  	req.Identity.AssumeRole = assumeRoleReq{
   427  		DomainName: opts.AgencyDomainName,
   428  		AgencyName: opts.AgencyName,
   429  	}
   430  	r, err := BuildRequestBody(req, "auth")
   431  	if err != nil {
   432  		return r, err
   433  	}
   434  
   435  	if len(scope) != 0 {
   436  		r["auth"].(map[string]interface{})["scope"] = scope
   437  	}
   438  	return r, nil
   439  }