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