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 }