github.com/opentelekomcloud/gophertelekomcloud@v0.9.3/auth_options.go (about)

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