github.com/vnpaycloud-console/gophercloud/v2@v2.0.5/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(context.TODO(), 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(context.TODO(), 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 TrustID string 106 } 107 108 // ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder 109 // interface in the v2 tokens package 110 func (opts AuthOptions) ToTokenV2CreateMap() (map[string]any, error) { 111 // Populate the request map. 112 authMap := make(map[string]any) 113 114 if opts.Username != "" { 115 if opts.Password != "" { 116 authMap["passwordCredentials"] = map[string]any{ 117 "username": opts.Username, 118 "password": opts.Password, 119 } 120 } else { 121 return nil, ErrMissingInput{Argument: "Password"} 122 } 123 } else if opts.TokenID != "" { 124 authMap["token"] = map[string]any{ 125 "id": opts.TokenID, 126 } 127 } else { 128 return nil, ErrMissingInput{Argument: "Username"} 129 } 130 131 if opts.TenantID != "" { 132 authMap["tenantId"] = opts.TenantID 133 } 134 if opts.TenantName != "" { 135 authMap["tenantName"] = opts.TenantName 136 } 137 138 return map[string]any{"auth": authMap}, nil 139 } 140 141 // ToTokenV3CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder 142 // interface in the v3 tokens package 143 func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]any) (map[string]any, error) { 144 type domainReq struct { 145 ID *string `json:"id,omitempty"` 146 Name *string `json:"name,omitempty"` 147 } 148 149 type userReq struct { 150 ID *string `json:"id,omitempty"` 151 Name *string `json:"name,omitempty"` 152 Password *string `json:"password,omitempty"` 153 Passcode *string `json:"passcode,omitempty"` 154 Domain *domainReq `json:"domain,omitempty"` 155 } 156 157 type passwordReq struct { 158 User userReq `json:"user"` 159 } 160 161 type tokenReq struct { 162 ID string `json:"id"` 163 } 164 165 type applicationCredentialReq struct { 166 ID *string `json:"id,omitempty"` 167 Name *string `json:"name,omitempty"` 168 User *userReq `json:"user,omitempty"` 169 Secret *string `json:"secret,omitempty"` 170 } 171 172 type totpReq struct { 173 User *userReq `json:"user,omitempty"` 174 } 175 176 type identityReq struct { 177 Methods []string `json:"methods"` 178 Password *passwordReq `json:"password,omitempty"` 179 Token *tokenReq `json:"token,omitempty"` 180 ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"` 181 TOTP *totpReq `json:"totp,omitempty"` 182 } 183 184 type authReq struct { 185 Identity identityReq `json:"identity"` 186 } 187 188 type request struct { 189 Auth authReq `json:"auth"` 190 } 191 192 // Populate the request structure based on the provided arguments. Create and return an error 193 // if insufficient or incompatible information is present. 194 var req request 195 196 if opts.Password == "" && opts.Passcode == "" { 197 if opts.TokenID != "" { 198 // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication 199 // parameters. 200 if opts.Username != "" { 201 return nil, ErrUsernameWithToken{} 202 } 203 if opts.UserID != "" { 204 return nil, ErrUserIDWithToken{} 205 } 206 if opts.DomainID != "" { 207 return nil, ErrDomainIDWithToken{} 208 } 209 if opts.DomainName != "" { 210 return nil, ErrDomainNameWithToken{} 211 } 212 213 // Configure the request for Token authentication. 214 req.Auth.Identity.Methods = []string{"token"} 215 req.Auth.Identity.Token = &tokenReq{ 216 ID: opts.TokenID, 217 } 218 219 } else if opts.ApplicationCredentialID != "" { 220 // Configure the request for ApplicationCredentialID authentication. 221 // https://github.com/openstack/keystoneauth/blob/stable/rocky/keystoneauth1/identity/v3/application_credential.py#L48-L67 222 // There are three kinds of possible application_credential requests 223 // 1. application_credential id + secret 224 // 2. application_credential name + secret + user_id 225 // 3. application_credential name + secret + username + domain_id / domain_name 226 if opts.ApplicationCredentialSecret == "" { 227 return nil, ErrAppCredMissingSecret{} 228 } 229 req.Auth.Identity.Methods = []string{"application_credential"} 230 req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{ 231 ID: &opts.ApplicationCredentialID, 232 Secret: &opts.ApplicationCredentialSecret, 233 } 234 } else if opts.ApplicationCredentialName != "" { 235 if opts.ApplicationCredentialSecret == "" { 236 return nil, ErrAppCredMissingSecret{} 237 } 238 239 var userRequest *userReq 240 241 if opts.UserID != "" { 242 // UserID could be used without the domain information 243 userRequest = &userReq{ 244 ID: &opts.UserID, 245 } 246 } 247 248 if userRequest == nil && opts.Username == "" { 249 // Make sure that Username or UserID are provided 250 return nil, ErrUsernameOrUserID{} 251 } 252 253 if userRequest == nil && opts.DomainID != "" { 254 userRequest = &userReq{ 255 Name: &opts.Username, 256 Domain: &domainReq{ID: &opts.DomainID}, 257 } 258 } 259 260 if userRequest == nil && opts.DomainName != "" { 261 userRequest = &userReq{ 262 Name: &opts.Username, 263 Domain: &domainReq{Name: &opts.DomainName}, 264 } 265 } 266 267 // Make sure that DomainID or DomainName are provided among Username 268 if userRequest == nil { 269 return nil, ErrDomainIDOrDomainName{} 270 } 271 272 req.Auth.Identity.Methods = []string{"application_credential"} 273 req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{ 274 Name: &opts.ApplicationCredentialName, 275 User: userRequest, 276 Secret: &opts.ApplicationCredentialSecret, 277 } 278 } else { 279 // If no password or token ID or ApplicationCredential are available, authentication can't continue. 280 return nil, ErrMissingPassword{} 281 } 282 } else { 283 // Password authentication. 284 if opts.Password != "" { 285 req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "password") 286 } 287 288 // TOTP authentication. 289 if opts.Passcode != "" { 290 req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "totp") 291 } 292 293 // At least one of Username and UserID must be specified. 294 if opts.Username == "" && opts.UserID == "" { 295 return nil, ErrUsernameOrUserID{} 296 } 297 298 if opts.Username != "" { 299 // If Username is provided, UserID may not be provided. 300 if opts.UserID != "" { 301 return nil, ErrUsernameOrUserID{} 302 } 303 304 // Either DomainID or DomainName must also be specified. 305 if opts.DomainID == "" && opts.DomainName == "" { 306 return nil, ErrDomainIDOrDomainName{} 307 } 308 309 if opts.DomainID != "" { 310 if opts.DomainName != "" { 311 return nil, ErrDomainIDOrDomainName{} 312 } 313 314 // Configure the request for Username and Password authentication with a DomainID. 315 if opts.Password != "" { 316 req.Auth.Identity.Password = &passwordReq{ 317 User: userReq{ 318 Name: &opts.Username, 319 Password: &opts.Password, 320 Domain: &domainReq{ID: &opts.DomainID}, 321 }, 322 } 323 } 324 if opts.Passcode != "" { 325 req.Auth.Identity.TOTP = &totpReq{ 326 User: &userReq{ 327 Name: &opts.Username, 328 Passcode: &opts.Passcode, 329 Domain: &domainReq{ID: &opts.DomainID}, 330 }, 331 } 332 } 333 } 334 335 if opts.DomainName != "" { 336 // Configure the request for Username and Password authentication with a DomainName. 337 if opts.Password != "" { 338 req.Auth.Identity.Password = &passwordReq{ 339 User: userReq{ 340 Name: &opts.Username, 341 Password: &opts.Password, 342 Domain: &domainReq{Name: &opts.DomainName}, 343 }, 344 } 345 } 346 347 if opts.Passcode != "" { 348 req.Auth.Identity.TOTP = &totpReq{ 349 User: &userReq{ 350 Name: &opts.Username, 351 Passcode: &opts.Passcode, 352 Domain: &domainReq{Name: &opts.DomainName}, 353 }, 354 } 355 } 356 } 357 } 358 359 if opts.UserID != "" { 360 // If UserID is specified, neither DomainID nor DomainName may be. 361 if opts.DomainID != "" { 362 return nil, ErrDomainIDWithUserID{} 363 } 364 if opts.DomainName != "" { 365 return nil, ErrDomainNameWithUserID{} 366 } 367 368 // Configure the request for UserID and Password authentication. 369 if opts.Password != "" { 370 req.Auth.Identity.Password = &passwordReq{ 371 User: userReq{ 372 ID: &opts.UserID, 373 Password: &opts.Password, 374 }, 375 } 376 } 377 378 if opts.Passcode != "" { 379 req.Auth.Identity.TOTP = &totpReq{ 380 User: &userReq{ 381 ID: &opts.UserID, 382 Passcode: &opts.Passcode, 383 }, 384 } 385 } 386 } 387 } 388 389 b, err := BuildRequestBody(req, "") 390 if err != nil { 391 return nil, err 392 } 393 394 if len(scope) != 0 { 395 b["auth"].(map[string]any)["scope"] = scope 396 } 397 398 return b, nil 399 } 400 401 // ToTokenV3ScopeMap builds a scope from AuthOptions and satisfies interface in 402 // the v3 tokens package. 403 func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]any, error) { 404 // For backwards compatibility. 405 // If AuthOptions.Scope was not set, try to determine it. 406 // This works well for common scenarios. 407 if opts.Scope == nil { 408 opts.Scope = new(AuthScope) 409 if opts.TenantID != "" { 410 opts.Scope.ProjectID = opts.TenantID 411 } else { 412 if opts.TenantName != "" { 413 opts.Scope.ProjectName = opts.TenantName 414 opts.Scope.DomainID = opts.DomainID 415 opts.Scope.DomainName = opts.DomainName 416 } 417 } 418 } 419 420 if opts.Scope.System { 421 return map[string]any{ 422 "system": map[string]any{ 423 "all": true, 424 }, 425 }, nil 426 } 427 428 if opts.Scope.TrustID != "" { 429 return map[string]any{ 430 "OS-TRUST:trust": map[string]string{ 431 "id": opts.Scope.TrustID, 432 }, 433 }, nil 434 } 435 436 if opts.Scope.ProjectName != "" { 437 // ProjectName provided: either DomainID or DomainName must also be supplied. 438 // ProjectID may not be supplied. 439 if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" { 440 return nil, ErrScopeDomainIDOrDomainName{} 441 } 442 if opts.Scope.ProjectID != "" { 443 return nil, ErrScopeProjectIDOrProjectName{} 444 } 445 446 if opts.Scope.DomainID != "" { 447 // ProjectName + DomainID 448 return map[string]any{ 449 "project": map[string]any{ 450 "name": &opts.Scope.ProjectName, 451 "domain": map[string]any{"id": &opts.Scope.DomainID}, 452 }, 453 }, nil 454 } 455 456 if opts.Scope.DomainName != "" { 457 // ProjectName + DomainName 458 return map[string]any{ 459 "project": map[string]any{ 460 "name": &opts.Scope.ProjectName, 461 "domain": map[string]any{"name": &opts.Scope.DomainName}, 462 }, 463 }, nil 464 } 465 } else if opts.Scope.ProjectID != "" { 466 // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided. 467 if opts.Scope.DomainID != "" { 468 return nil, ErrScopeProjectIDAlone{} 469 } 470 if opts.Scope.DomainName != "" { 471 return nil, ErrScopeProjectIDAlone{} 472 } 473 474 // ProjectID 475 return map[string]any{ 476 "project": map[string]any{ 477 "id": &opts.Scope.ProjectID, 478 }, 479 }, nil 480 } else if opts.Scope.DomainID != "" { 481 // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided. 482 if opts.Scope.DomainName != "" { 483 return nil, ErrScopeDomainIDOrDomainName{} 484 } 485 486 // DomainID 487 return map[string]any{ 488 "domain": map[string]any{ 489 "id": &opts.Scope.DomainID, 490 }, 491 }, nil 492 } else if opts.Scope.DomainName != "" { 493 // DomainName 494 return map[string]any{ 495 "domain": map[string]any{ 496 "name": &opts.Scope.DomainName, 497 }, 498 }, nil 499 } 500 501 return nil, nil 502 } 503 504 func (opts AuthOptions) CanReauth() bool { 505 if opts.Passcode != "" { 506 // cannot reauth using TOTP passcode 507 return false 508 } 509 510 return opts.AllowReauth 511 } 512 513 // ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder 514 // interface in the v3 tokens package. 515 func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]any) (map[string]string, error) { 516 return nil, nil 517 }