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 }