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