blob: 3ee97dfb323fa90948bf9f61daccde932ac3c162 [file] [log] [blame]
Ash Wilson70dfe0c2014-08-28 13:57:09 -04001package gophercloud
2
Ash Wilson730a5062014-10-31 15:13:35 -04003/*
4AuthOptions stores information needed to authenticate to an OpenStack cluster.
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*/
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.
34 // Some providers allow you to specify a TenantName instead of the TenantId.
Ash Wilson730a5062014-10-31 15:13:35 -040035 // Some require both. Your provider's authentication policies will determine
Ash Wilson70dfe0c2014-08-28 13:57:09 -040036 // how these fields influence authentication.
Jon Perrittdb0ae142016-03-13 00:33:41 -060037 TenantID string `json:"tenantId,omitempty"`
38 TenantName string `json:"tenantName,omitempty"`
Ash Wilson70dfe0c2014-08-28 13:57:09 -040039
40 // AllowReauth should be set to true if you grant permission for Gophercloud to
41 // cache your credentials in memory, and to allow Gophercloud to attempt to
42 // re-authenticate automatically if/when your token expires. If you set it to
43 // false, it will not cache these settings, but re-authentication will not be
44 // possible. This setting defaults to false.
jrperritt6e2ca002016-04-16 15:37:08 -050045 //
46 // NOTE: The reauth function will try to re-authenticate endlessly if left unchecked.
jrperritt9b7b9e62016-07-11 22:30:50 -050047 // 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 -050048 // and provide a transport that implements the RoundTripper interface and stores the number of failed retries.
49 // 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 -050050 AllowReauth bool `json:"-"`
jrperritt95b74c82015-07-28 20:39:27 -060051
jrperritt1f218c82015-07-29 08:54:18 -060052 // TokenID allows users to authenticate (possibly as another user) with an
53 // authentication token ID.
jrperritt0bc55782016-07-27 13:50:14 -050054 TokenID string `json:"-"`
Ash Wilson70dfe0c2014-08-28 13:57:09 -040055}
jrperritt64d0ef02016-04-13 13:10:04 -050056
57// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
58// interface in the v2 tokens package
59func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
60 // Populate the request map.
61 authMap := make(map[string]interface{})
62
63 if opts.Username != "" {
64 if opts.Password != "" {
65 authMap["passwordCredentials"] = map[string]interface{}{
66 "username": opts.Username,
67 "password": opts.Password,
68 }
69 } else {
70 return nil, ErrMissingInput{Argument: "Password"}
71 }
72 } else if opts.TokenID != "" {
73 authMap["token"] = map[string]interface{}{
74 "id": opts.TokenID,
75 }
76 } else {
77 return nil, ErrMissingInput{Argument: "Username"}
78 }
79
80 if opts.TenantID != "" {
81 authMap["tenantId"] = opts.TenantID
82 }
83 if opts.TenantName != "" {
84 authMap["tenantName"] = opts.TenantName
85 }
86
87 return map[string]interface{}{"auth": authMap}, nil
88}
jrperritt0bc55782016-07-27 13:50:14 -050089
90func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
91 type domainReq struct {
92 ID *string `json:"id,omitempty"`
93 Name *string `json:"name,omitempty"`
94 }
95
96 type projectReq struct {
97 Domain *domainReq `json:"domain,omitempty"`
98 Name *string `json:"name,omitempty"`
99 ID *string `json:"id,omitempty"`
100 }
101
102 type userReq struct {
103 ID *string `json:"id,omitempty"`
104 Name *string `json:"name,omitempty"`
105 Password string `json:"password"`
106 Domain *domainReq `json:"domain,omitempty"`
107 }
108
109 type passwordReq struct {
110 User userReq `json:"user"`
111 }
112
113 type tokenReq struct {
114 ID string `json:"id"`
115 }
116
117 type identityReq struct {
118 Methods []string `json:"methods"`
119 Password *passwordReq `json:"password,omitempty"`
120 Token *tokenReq `json:"token,omitempty"`
121 }
122
123 type authReq struct {
124 Identity identityReq `json:"identity"`
125 }
126
127 type request struct {
128 Auth authReq `json:"auth"`
129 }
130
131 // Populate the request structure based on the provided arguments. Create and return an error
132 // if insufficient or incompatible information is present.
133 var req request
134
135 // Test first for unrecognized arguments.
136 if opts.TenantID != "" {
137 return nil, ErrTenantIDProvided{}
138 }
139 if opts.TenantName != "" {
140 return nil, ErrTenantNameProvided{}
141 }
142
143 if opts.Password == "" {
144 if opts.TokenID != "" {
145 // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
146 // parameters.
147 if opts.Username != "" {
148 return nil, ErrUsernameWithToken{}
149 }
150 if opts.UserID != "" {
151 return nil, ErrUserIDWithToken{}
152 }
153 if opts.DomainID != "" {
154 return nil, ErrDomainIDWithToken{}
155 }
156 if opts.DomainName != "" {
157 return nil, ErrDomainNameWithToken{}
158 }
159
160 // Configure the request for Token authentication.
161 req.Auth.Identity.Methods = []string{"token"}
162 req.Auth.Identity.Token = &tokenReq{
163 ID: opts.TokenID,
164 }
165 } else {
166 // If no password or token ID are available, authentication can't continue.
167 return nil, ErrMissingPassword{}
168 }
169 } else {
170 // Password authentication.
171 req.Auth.Identity.Methods = []string{"password"}
172
173 // At least one of Username and UserID must be specified.
174 if opts.Username == "" && opts.UserID == "" {
175 return nil, ErrUsernameOrUserID{}
176 }
177
178 if opts.Username != "" {
179 // If Username is provided, UserID may not be provided.
180 if opts.UserID != "" {
181 return nil, ErrUsernameOrUserID{}
182 }
183
184 // Either DomainID or DomainName must also be specified.
185 if opts.DomainID == "" && opts.DomainName == "" {
186 return nil, ErrDomainIDOrDomainName{}
187 }
188
189 if opts.DomainID != "" {
190 if opts.DomainName != "" {
191 return nil, ErrDomainIDOrDomainName{}
192 }
193
194 // Configure the request for Username and Password authentication with a DomainID.
195 req.Auth.Identity.Password = &passwordReq{
196 User: userReq{
197 Name: &opts.Username,
198 Password: opts.Password,
199 Domain: &domainReq{ID: &opts.DomainID},
200 },
201 }
202 }
203
204 if opts.DomainName != "" {
205 // Configure the request for Username and Password authentication with a DomainName.
206 req.Auth.Identity.Password = &passwordReq{
207 User: userReq{
208 Name: &opts.Username,
209 Password: opts.Password,
210 Domain: &domainReq{Name: &opts.DomainName},
211 },
212 }
213 }
214 }
215
216 if opts.UserID != "" {
217 // If UserID is specified, neither DomainID nor DomainName may be.
218 if opts.DomainID != "" {
219 return nil, ErrDomainIDWithUserID{}
220 }
221 if opts.DomainName != "" {
222 return nil, ErrDomainNameWithUserID{}
223 }
224
225 // Configure the request for UserID and Password authentication.
226 req.Auth.Identity.Password = &passwordReq{
227 User: userReq{ID: &opts.UserID, Password: opts.Password},
228 }
229 }
230 }
231
232 b, err := BuildRequestBody(req, "")
233 if err != nil {
234 return nil, err
235 }
236
237 if len(scope) != 0 {
238 b["auth"].(map[string]interface{})["scope"] = scope
239 }
240
241 return b, nil
242}
243
244func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
245
246 var scope struct {
247 ProjectID string
248 ProjectName string
249 DomainID string
250 DomainName string
251 }
252
253 if opts.TenantID != "" {
254 scope.ProjectID = opts.TenantID
255 opts.TenantID = ""
256 opts.TenantName = ""
257 } else {
258 if opts.TenantName != "" {
259 scope.ProjectName = opts.TenantName
260 scope.DomainID = opts.DomainID
261 scope.DomainName = opts.DomainName
262 }
263 opts.TenantName = ""
264 }
265
266 if scope.ProjectName != "" {
267 // ProjectName provided: either DomainID or DomainName must also be supplied.
268 // ProjectID may not be supplied.
269 if scope.DomainID == "" && scope.DomainName == "" {
270 return nil, ErrScopeDomainIDOrDomainName{}
271 }
272 if scope.ProjectID != "" {
273 return nil, ErrScopeProjectIDOrProjectName{}
274 }
275
276 if scope.DomainID != "" {
277 // ProjectName + DomainID
278 return map[string]interface{}{
279 "project": map[string]interface{}{
280 "name": &scope.ProjectName,
281 "domain": map[string]interface{}{"id": &scope.DomainID},
282 },
283 }, nil
284 }
285
286 if scope.DomainName != "" {
287 // ProjectName + DomainName
288 return map[string]interface{}{
289 "project": map[string]interface{}{
290 "name": &scope.ProjectName,
291 "domain": map[string]interface{}{"name": &scope.DomainName},
292 },
293 }, nil
294 }
295 } else if scope.ProjectID != "" {
296 // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
297 if scope.DomainID != "" {
298 return nil, ErrScopeProjectIDAlone{}
299 }
300 if scope.DomainName != "" {
301 return nil, ErrScopeProjectIDAlone{}
302 }
303
304 // ProjectID
305 return map[string]interface{}{
306 "project": map[string]interface{}{
307 "id": &scope.ProjectID,
308 },
309 }, nil
310 } else if scope.DomainID != "" {
311 // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
312 if scope.DomainName != "" {
313 return nil, ErrScopeDomainIDOrDomainName{}
314 }
315
316 // DomainID
317 return map[string]interface{}{
318 "domain": map[string]interface{}{
319 "id": &scope.DomainID,
320 },
321 }, nil
322 } else if scope.DomainName != "" {
323 return nil, ErrScopeDomainName{}
324 }
325
326 return nil, nil
327}
328
329func (opts AuthOptions) CanReauth() bool {
330 return opts.AllowReauth
331}