blob: eabf18207515e7d62b8c6b84232326117cb879f8 [file] [log] [blame]
Ash Wilson70dfe0c2014-08-28 13:57:09 -04001package gophercloud
2
Ash Wilson730a5062014-10-31 15:13:35 -04003/*
Monty Taylor9a5595b2017-03-13 13:04:29 -05004AuthOptions stores information needed to authenticate to an OpenStack Cloud.
Ash Wilson730a5062014-10-31 15:13:35 -04005You 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*/
Ash Wilson70dfe0c2014-08-28 13:57:09 -040013type AuthOptions struct {
Jamie Hannafordb280dea2014-10-24 15:14:06 +020014 // IdentityEndpoint specifies the HTTP endpoint that is required to work with
Ash Wilson730a5062014-10-31 15:13:35 -040015 // the Identity API of the appropriate version. While it's ultimately needed by
16 // all of the identity services, it will often be populated by a provider-level
17 // function.
Jon Perrittdb0ae142016-03-13 00:33:41 -060018 IdentityEndpoint string `json:"-"`
Ash Wilson70dfe0c2014-08-28 13:57:09 -040019
Jamie Hannafordb280dea2014-10-24 15:14:06 +020020 // Username is required if using Identity V2 API. Consult with your provider's
21 // control panel to discover your account's username. In Identity V3, either
Ash Wilson730a5062014-10-31 15:13:35 -040022 // UserID or a combination of Username and DomainID or DomainName are needed.
Jon Perrittdb0ae142016-03-13 00:33:41 -060023 Username string `json:"username,omitempty"`
24 UserID string `json:"id,omitempty"`
Ash Wilson70dfe0c2014-08-28 13:57:09 -040025
Jon Perrittdb0ae142016-03-13 00:33:41 -060026 Password string `json:"password,omitempty"`
Ash Wilson70dfe0c2014-08-28 13:57:09 -040027
Jamie Hannafordb280dea2014-10-24 15:14:06 +020028 // At most one of DomainID and DomainName must be provided if using Username
29 // with Identity V3. Otherwise, either are optional.
Jon Perrittdb0ae142016-03-13 00:33:41 -060030 DomainID string `json:"id,omitempty"`
31 DomainName string `json:"name,omitempty"`
Ash Wilson70dfe0c2014-08-28 13:57:09 -040032
33 // The TenantID and TenantName fields are optional for the Identity V2 API.
Monty Taylor9a5595b2017-03-13 13:04:29 -050034 // The same fields are known as project_id and project_name in the Identity
35 // V3 API, but are collected as TenantID and TenantName here in both cases.
Ash Wilson70dfe0c2014-08-28 13:57:09 -040036 // Some providers allow you to specify a TenantName instead of the TenantId.
Ash Wilson730a5062014-10-31 15:13:35 -040037 // Some require both. Your provider's authentication policies will determine
Ash Wilson70dfe0c2014-08-28 13:57:09 -040038 // how these fields influence authentication.
Monty Taylor9a5595b2017-03-13 13:04:29 -050039 // If DomainID or DomainName are provided, they will also apply to TenantName.
40 // It is not currently possible to authenticate with Username and a Domain
41 // and scope to a Project in a different Domain by using TenantName. To
42 // accomplish that, the ProjectID will need to be provided to the TenantID
43 // option.
Jon Perrittdb0ae142016-03-13 00:33:41 -060044 TenantID string `json:"tenantId,omitempty"`
45 TenantName string `json:"tenantName,omitempty"`
Ash Wilson70dfe0c2014-08-28 13:57:09 -040046
47 // AllowReauth should be set to true if you grant permission for Gophercloud to
48 // cache your credentials in memory, and to allow Gophercloud to attempt to
49 // re-authenticate automatically if/when your token expires. If you set it to
50 // false, it will not cache these settings, but re-authentication will not be
51 // possible. This setting defaults to false.
jrperritt6e2ca002016-04-16 15:37:08 -050052 //
53 // NOTE: The reauth function will try to re-authenticate endlessly if left unchecked.
jrperritt9b7b9e62016-07-11 22:30:50 -050054 // The way to limit the number of attempts is to provide a custom HTTP client to the provider client
jrperritt6e2ca002016-04-16 15:37:08 -050055 // and provide a transport that implements the RoundTripper interface and stores the number of failed retries.
56 // For an example of this, see here: https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
jrperritt0bc55782016-07-27 13:50:14 -050057 AllowReauth bool `json:"-"`
jrperritt95b74c82015-07-28 20:39:27 -060058
jrperritt1f218c82015-07-29 08:54:18 -060059 // TokenID allows users to authenticate (possibly as another user) with an
60 // authentication token ID.
jrperritt0bc55782016-07-27 13:50:14 -050061 TokenID string `json:"-"`
Ash Wilson70dfe0c2014-08-28 13:57:09 -040062}
jrperritt64d0ef02016-04-13 13:10:04 -050063
64// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
65// interface in the v2 tokens package
66func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
67 // Populate the request map.
68 authMap := make(map[string]interface{})
69
70 if opts.Username != "" {
71 if opts.Password != "" {
72 authMap["passwordCredentials"] = map[string]interface{}{
73 "username": opts.Username,
74 "password": opts.Password,
75 }
76 } else {
77 return nil, ErrMissingInput{Argument: "Password"}
78 }
79 } else if opts.TokenID != "" {
80 authMap["token"] = map[string]interface{}{
81 "id": opts.TokenID,
82 }
83 } else {
84 return nil, ErrMissingInput{Argument: "Username"}
85 }
86
87 if opts.TenantID != "" {
88 authMap["tenantId"] = opts.TenantID
89 }
90 if opts.TenantName != "" {
91 authMap["tenantName"] = opts.TenantName
92 }
93
94 return map[string]interface{}{"auth": authMap}, nil
95}
jrperritt0bc55782016-07-27 13:50:14 -050096
97func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
98 type domainReq struct {
99 ID *string `json:"id,omitempty"`
100 Name *string `json:"name,omitempty"`
101 }
102
103 type projectReq struct {
104 Domain *domainReq `json:"domain,omitempty"`
105 Name *string `json:"name,omitempty"`
106 ID *string `json:"id,omitempty"`
107 }
108
109 type userReq struct {
110 ID *string `json:"id,omitempty"`
111 Name *string `json:"name,omitempty"`
112 Password string `json:"password"`
113 Domain *domainReq `json:"domain,omitempty"`
114 }
115
116 type passwordReq struct {
117 User userReq `json:"user"`
118 }
119
120 type tokenReq struct {
121 ID string `json:"id"`
122 }
123
124 type identityReq struct {
125 Methods []string `json:"methods"`
126 Password *passwordReq `json:"password,omitempty"`
127 Token *tokenReq `json:"token,omitempty"`
128 }
129
130 type authReq struct {
131 Identity identityReq `json:"identity"`
132 }
133
134 type request struct {
135 Auth authReq `json:"auth"`
136 }
137
138 // Populate the request structure based on the provided arguments. Create and return an error
139 // if insufficient or incompatible information is present.
140 var req request
141
jrperritt0bc55782016-07-27 13:50:14 -0500142 if opts.Password == "" {
143 if opts.TokenID != "" {
144 // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
145 // parameters.
146 if opts.Username != "" {
147 return nil, ErrUsernameWithToken{}
148 }
149 if opts.UserID != "" {
150 return nil, ErrUserIDWithToken{}
151 }
152 if opts.DomainID != "" {
153 return nil, ErrDomainIDWithToken{}
154 }
155 if opts.DomainName != "" {
156 return nil, ErrDomainNameWithToken{}
157 }
158
159 // Configure the request for Token authentication.
160 req.Auth.Identity.Methods = []string{"token"}
161 req.Auth.Identity.Token = &tokenReq{
162 ID: opts.TokenID,
163 }
164 } else {
165 // If no password or token ID are available, authentication can't continue.
166 return nil, ErrMissingPassword{}
167 }
168 } else {
169 // Password authentication.
170 req.Auth.Identity.Methods = []string{"password"}
171
172 // At least one of Username and UserID must be specified.
173 if opts.Username == "" && opts.UserID == "" {
174 return nil, ErrUsernameOrUserID{}
175 }
176
177 if opts.Username != "" {
178 // If Username is provided, UserID may not be provided.
179 if opts.UserID != "" {
180 return nil, ErrUsernameOrUserID{}
181 }
182
183 // Either DomainID or DomainName must also be specified.
184 if opts.DomainID == "" && opts.DomainName == "" {
185 return nil, ErrDomainIDOrDomainName{}
186 }
187
188 if opts.DomainID != "" {
189 if opts.DomainName != "" {
190 return nil, ErrDomainIDOrDomainName{}
191 }
192
193 // Configure the request for Username and Password authentication with a DomainID.
194 req.Auth.Identity.Password = &passwordReq{
195 User: userReq{
196 Name: &opts.Username,
197 Password: opts.Password,
198 Domain: &domainReq{ID: &opts.DomainID},
199 },
200 }
201 }
202
203 if opts.DomainName != "" {
204 // Configure the request for Username and Password authentication with a DomainName.
205 req.Auth.Identity.Password = &passwordReq{
206 User: userReq{
207 Name: &opts.Username,
208 Password: opts.Password,
209 Domain: &domainReq{Name: &opts.DomainName},
210 },
211 }
212 }
213 }
214
215 if opts.UserID != "" {
216 // If UserID is specified, neither DomainID nor DomainName may be.
217 if opts.DomainID != "" {
218 return nil, ErrDomainIDWithUserID{}
219 }
220 if opts.DomainName != "" {
221 return nil, ErrDomainNameWithUserID{}
222 }
223
224 // Configure the request for UserID and Password authentication.
225 req.Auth.Identity.Password = &passwordReq{
226 User: userReq{ID: &opts.UserID, Password: opts.Password},
227 }
228 }
229 }
230
231 b, err := BuildRequestBody(req, "")
232 if err != nil {
233 return nil, err
234 }
235
236 if len(scope) != 0 {
237 b["auth"].(map[string]interface{})["scope"] = scope
238 }
239
240 return b, nil
241}
242
243func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
244
245 var scope struct {
246 ProjectID string
247 ProjectName string
248 DomainID string
249 DomainName string
250 }
251
252 if opts.TenantID != "" {
253 scope.ProjectID = opts.TenantID
jrperritt0bc55782016-07-27 13:50:14 -0500254 } else {
255 if opts.TenantName != "" {
256 scope.ProjectName = opts.TenantName
257 scope.DomainID = opts.DomainID
258 scope.DomainName = opts.DomainName
259 }
jrperritt0bc55782016-07-27 13:50:14 -0500260 }
261
262 if scope.ProjectName != "" {
263 // ProjectName provided: either DomainID or DomainName must also be supplied.
264 // ProjectID may not be supplied.
265 if scope.DomainID == "" && scope.DomainName == "" {
266 return nil, ErrScopeDomainIDOrDomainName{}
267 }
268 if scope.ProjectID != "" {
269 return nil, ErrScopeProjectIDOrProjectName{}
270 }
271
272 if scope.DomainID != "" {
273 // ProjectName + DomainID
274 return map[string]interface{}{
275 "project": map[string]interface{}{
276 "name": &scope.ProjectName,
277 "domain": map[string]interface{}{"id": &scope.DomainID},
278 },
279 }, nil
280 }
281
282 if scope.DomainName != "" {
283 // ProjectName + DomainName
284 return map[string]interface{}{
285 "project": map[string]interface{}{
286 "name": &scope.ProjectName,
287 "domain": map[string]interface{}{"name": &scope.DomainName},
288 },
289 }, nil
290 }
291 } else if scope.ProjectID != "" {
292 // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
293 if scope.DomainID != "" {
294 return nil, ErrScopeProjectIDAlone{}
295 }
296 if scope.DomainName != "" {
297 return nil, ErrScopeProjectIDAlone{}
298 }
299
300 // ProjectID
301 return map[string]interface{}{
302 "project": map[string]interface{}{
303 "id": &scope.ProjectID,
304 },
305 }, nil
306 } else if scope.DomainID != "" {
307 // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
308 if scope.DomainName != "" {
309 return nil, ErrScopeDomainIDOrDomainName{}
310 }
311
312 // DomainID
313 return map[string]interface{}{
314 "domain": map[string]interface{}{
315 "id": &scope.DomainID,
316 },
317 }, nil
318 } else if scope.DomainName != "" {
319 return nil, ErrScopeDomainName{}
320 }
321
322 return nil, nil
323}
324
325func (opts AuthOptions) CanReauth() bool {
326 return opts.AllowReauth
327}