blob: 7a16131093f66a2e90d6891ef73981ecae04e9a3 [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
142 // Test first for unrecognized arguments.
143 if opts.TenantID != "" {
144 return nil, ErrTenantIDProvided{}
145 }
146 if opts.TenantName != "" {
147 return nil, ErrTenantNameProvided{}
148 }
149
150 if opts.Password == "" {
151 if opts.TokenID != "" {
152 // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
153 // parameters.
154 if opts.Username != "" {
155 return nil, ErrUsernameWithToken{}
156 }
157 if opts.UserID != "" {
158 return nil, ErrUserIDWithToken{}
159 }
160 if opts.DomainID != "" {
161 return nil, ErrDomainIDWithToken{}
162 }
163 if opts.DomainName != "" {
164 return nil, ErrDomainNameWithToken{}
165 }
166
167 // Configure the request for Token authentication.
168 req.Auth.Identity.Methods = []string{"token"}
169 req.Auth.Identity.Token = &tokenReq{
170 ID: opts.TokenID,
171 }
172 } else {
173 // If no password or token ID are available, authentication can't continue.
174 return nil, ErrMissingPassword{}
175 }
176 } else {
177 // Password authentication.
178 req.Auth.Identity.Methods = []string{"password"}
179
180 // At least one of Username and UserID must be specified.
181 if opts.Username == "" && opts.UserID == "" {
182 return nil, ErrUsernameOrUserID{}
183 }
184
185 if opts.Username != "" {
186 // If Username is provided, UserID may not be provided.
187 if opts.UserID != "" {
188 return nil, ErrUsernameOrUserID{}
189 }
190
191 // Either DomainID or DomainName must also be specified.
192 if opts.DomainID == "" && opts.DomainName == "" {
193 return nil, ErrDomainIDOrDomainName{}
194 }
195
196 if opts.DomainID != "" {
197 if opts.DomainName != "" {
198 return nil, ErrDomainIDOrDomainName{}
199 }
200
201 // Configure the request for Username and Password authentication with a DomainID.
202 req.Auth.Identity.Password = &passwordReq{
203 User: userReq{
204 Name: &opts.Username,
205 Password: opts.Password,
206 Domain: &domainReq{ID: &opts.DomainID},
207 },
208 }
209 }
210
211 if opts.DomainName != "" {
212 // Configure the request for Username and Password authentication with a DomainName.
213 req.Auth.Identity.Password = &passwordReq{
214 User: userReq{
215 Name: &opts.Username,
216 Password: opts.Password,
217 Domain: &domainReq{Name: &opts.DomainName},
218 },
219 }
220 }
221 }
222
223 if opts.UserID != "" {
224 // If UserID is specified, neither DomainID nor DomainName may be.
225 if opts.DomainID != "" {
226 return nil, ErrDomainIDWithUserID{}
227 }
228 if opts.DomainName != "" {
229 return nil, ErrDomainNameWithUserID{}
230 }
231
232 // Configure the request for UserID and Password authentication.
233 req.Auth.Identity.Password = &passwordReq{
234 User: userReq{ID: &opts.UserID, Password: opts.Password},
235 }
236 }
237 }
238
239 b, err := BuildRequestBody(req, "")
240 if err != nil {
241 return nil, err
242 }
243
244 if len(scope) != 0 {
245 b["auth"].(map[string]interface{})["scope"] = scope
246 }
247
248 return b, nil
249}
250
251func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
252
253 var scope struct {
254 ProjectID string
255 ProjectName string
256 DomainID string
257 DomainName string
258 }
259
260 if opts.TenantID != "" {
261 scope.ProjectID = opts.TenantID
262 opts.TenantID = ""
263 opts.TenantName = ""
264 } else {
265 if opts.TenantName != "" {
266 scope.ProjectName = opts.TenantName
267 scope.DomainID = opts.DomainID
268 scope.DomainName = opts.DomainName
269 }
270 opts.TenantName = ""
271 }
272
273 if scope.ProjectName != "" {
274 // ProjectName provided: either DomainID or DomainName must also be supplied.
275 // ProjectID may not be supplied.
276 if scope.DomainID == "" && scope.DomainName == "" {
277 return nil, ErrScopeDomainIDOrDomainName{}
278 }
279 if scope.ProjectID != "" {
280 return nil, ErrScopeProjectIDOrProjectName{}
281 }
282
283 if scope.DomainID != "" {
284 // ProjectName + DomainID
285 return map[string]interface{}{
286 "project": map[string]interface{}{
287 "name": &scope.ProjectName,
288 "domain": map[string]interface{}{"id": &scope.DomainID},
289 },
290 }, nil
291 }
292
293 if scope.DomainName != "" {
294 // ProjectName + DomainName
295 return map[string]interface{}{
296 "project": map[string]interface{}{
297 "name": &scope.ProjectName,
298 "domain": map[string]interface{}{"name": &scope.DomainName},
299 },
300 }, nil
301 }
302 } else if scope.ProjectID != "" {
303 // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
304 if scope.DomainID != "" {
305 return nil, ErrScopeProjectIDAlone{}
306 }
307 if scope.DomainName != "" {
308 return nil, ErrScopeProjectIDAlone{}
309 }
310
311 // ProjectID
312 return map[string]interface{}{
313 "project": map[string]interface{}{
314 "id": &scope.ProjectID,
315 },
316 }, nil
317 } else if scope.DomainID != "" {
318 // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
319 if scope.DomainName != "" {
320 return nil, ErrScopeDomainIDOrDomainName{}
321 }
322
323 // DomainID
324 return map[string]interface{}{
325 "domain": map[string]interface{}{
326 "id": &scope.DomainID,
327 },
328 }, nil
329 } else if scope.DomainName != "" {
330 return nil, ErrScopeDomainName{}
331 }
332
333 return nil, nil
334}
335
336func (opts AuthOptions) CanReauth() bool {
337 return opts.AllowReauth
338}