blob: 856c363d4fb2fa0840aa0221e5b5b75a9eaaba61 [file] [log] [blame]
Ash Wilson85d82652014-08-28 13:57:46 -04001package tokens
2
jrperritt29ae6b32016-04-13 12:59:37 -05003import "github.com/gophercloud/gophercloud"
Ash Wilson2491b4c2015-02-12 16:13:39 -05004
jrperritt29ae6b32016-04-13 12:59:37 -05005// Scope allows a created token to be limited to a specific domain or project.
6type Scope struct {
7 ProjectID string `json:"scope.project.id,omitempty" not:"ProjectName,DomainID,DomainName"`
8 ProjectName string `json:"scope.project.name,omitempty"`
9 DomainID string `json:"scope.project.id,omitempty" not:"ProjectName,ProjectID,DomainName"`
10 DomainName string `json:"scope.project.id,omitempty"`
11}
Ash Wilson85d82652014-08-28 13:57:46 -040012
Jon Perrittdb0ae142016-03-13 00:33:41 -060013// AuthOptionsBuilder describes any argument that may be passed to the Create call.
14type AuthOptionsBuilder interface {
15 // ToTokenV3CreateMap assembles the Create request body, returning an error if parameters are
16 // missing or inconsistent.
jrperritt29ae6b32016-04-13 12:59:37 -050017 ToTokenV3CreateMap(*Scope) (map[string]interface{}, error)
18}
19
20type AuthOptions struct {
21 // IdentityEndpoint specifies the HTTP endpoint that is required to work with
22 // the Identity API of the appropriate version. While it's ultimately needed by
23 // all of the identity services, it will often be populated by a provider-level
24 // function.
25 IdentityEndpoint string `json:"-"`
26
27 // Username is required if using Identity V2 API. Consult with your provider's
28 // control panel to discover your account's username. In Identity V3, either
29 // UserID or a combination of Username and DomainID or DomainName are needed.
30 Username string `json:"username,omitempty"`
31 UserID string `json:"id,omitempty"`
32
33 Password string `json:"password,omitempty"`
34
35 // At most one of DomainID and DomainName must be provided if using Username
36 // with Identity V3. Otherwise, either are optional.
37 DomainID string `json:"id,omitempty"`
38 DomainName string `json:"name,omitempty"`
39
40 // The TenantID and TenantName fields are optional for the Identity V2 API.
41 // Some providers allow you to specify a TenantName instead of the TenantId.
42 // Some require both. Your provider's authentication policies will determine
43 // how these fields influence authentication.
44 TenantID string `json:"tenantId,omitempty"`
45 TenantName string `json:"tenantName,omitempty"`
46
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.
52 AllowReauth bool `json:"-"`
53
54 // TokenID allows users to authenticate (possibly as another user) with an
55 // authentication token ID.
56 TokenID string
57}
58
59func (opts AuthOptions) ToTokenV3CreateMap(scope *Scope) (map[string]interface{}, error) {
60 type domainReq struct {
61 ID *string `json:"id,omitempty"`
62 Name *string `json:"name,omitempty"`
63 }
64
65 type projectReq struct {
66 Domain *domainReq `json:"domain,omitempty"`
67 Name *string `json:"name,omitempty"`
68 ID *string `json:"id,omitempty"`
69 }
70
71 type userReq struct {
72 ID *string `json:"id,omitempty"`
73 Name *string `json:"name,omitempty"`
74 Password string `json:"password"`
75 Domain *domainReq `json:"domain,omitempty"`
76 }
77
78 type passwordReq struct {
79 User userReq `json:"user"`
80 }
81
82 type tokenReq struct {
83 ID string `json:"id"`
84 }
85
86 type identityReq struct {
87 Methods []string `json:"methods"`
88 Password *passwordReq `json:"password,omitempty"`
89 Token *tokenReq `json:"token,omitempty"`
90 }
91
92 type scopeReq struct {
93 Domain *domainReq `json:"domain,omitempty"`
94 Project *projectReq `json:"project,omitempty"`
95 }
96
97 type authReq struct {
98 Identity identityReq `json:"identity"`
99 Scope *scopeReq `json:"scope,omitempty"`
100 }
101
102 type request struct {
103 Auth authReq `json:"auth"`
104 }
105
106 // Populate the request structure based on the provided arguments. Create and return an error
107 // if insufficient or incompatible information is present.
108 var req request
109
110 // Test first for unrecognized arguments.
111 if opts.TenantID != "" {
112 return nil, ErrTenantIDProvided{}
113 }
114 if opts.TenantName != "" {
115 return nil, ErrTenantNameProvided{}
116 }
117
118 if opts.Password == "" {
119 if opts.TokenID != "" {
120 // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
121 // parameters.
122 if opts.Username != "" {
123 return nil, ErrUsernameWithToken{}
124 }
125 if opts.UserID != "" {
126 return nil, ErrUserIDWithToken{}
127 }
128 if opts.DomainID != "" {
129 return nil, ErrDomainIDWithToken{}
130 }
131 if opts.DomainName != "" {
132 return nil, ErrDomainNameWithToken{}
133 }
134
135 // Configure the request for Token authentication.
136 req.Auth.Identity.Methods = []string{"token"}
137 req.Auth.Identity.Token = &tokenReq{
138 ID: opts.TokenID,
139 }
140 } else {
141 // If no password or token ID are available, authentication can't continue.
142 return nil, ErrMissingPassword{}
143 }
144 } else {
145 // Password authentication.
146 req.Auth.Identity.Methods = []string{"password"}
147
148 // At least one of Username and UserID must be specified.
149 if opts.Username == "" && opts.UserID == "" {
150 return nil, ErrUsernameOrUserID{}
151 }
152
153 if opts.Username != "" {
154 // If Username is provided, UserID may not be provided.
155 if opts.UserID != "" {
156 return nil, ErrUsernameOrUserID{}
157 }
158
159 // Either DomainID or DomainName must also be specified.
160 if opts.DomainID == "" && opts.DomainName == "" {
161 return nil, ErrDomainIDOrDomainName{}
162 }
163
164 if opts.DomainID != "" {
165 if opts.DomainName != "" {
166 return nil, ErrDomainIDOrDomainName{}
167 }
168
169 // Configure the request for Username and Password authentication with a DomainID.
170 req.Auth.Identity.Password = &passwordReq{
171 User: userReq{
172 Name: &opts.Username,
173 Password: opts.Password,
174 Domain: &domainReq{ID: &opts.DomainID},
175 },
176 }
177 }
178
179 if opts.DomainName != "" {
180 // Configure the request for Username and Password authentication with a DomainName.
181 req.Auth.Identity.Password = &passwordReq{
182 User: userReq{
183 Name: &opts.Username,
184 Password: opts.Password,
185 Domain: &domainReq{Name: &opts.DomainName},
186 },
187 }
188 }
189 }
190
191 if opts.UserID != "" {
192 // If UserID is specified, neither DomainID nor DomainName may be.
193 if opts.DomainID != "" {
194 return nil, ErrDomainIDWithUserID{}
195 }
196 if opts.DomainName != "" {
197 return nil, ErrDomainNameWithUserID{}
198 }
199
200 // Configure the request for UserID and Password authentication.
201 req.Auth.Identity.Password = &passwordReq{
202 User: userReq{ID: &opts.UserID, Password: opts.Password},
203 }
204 }
205 }
206
207 // Add a "scope" element if a Scope has been provided.
208 if scope != nil {
209 if scope.ProjectName != "" {
210 // ProjectName provided: either DomainID or DomainName must also be supplied.
211 // ProjectID may not be supplied.
212 if scope.DomainID == "" && scope.DomainName == "" {
213 return nil, ErrScopeDomainIDOrDomainName{}
214 }
215 if scope.ProjectID != "" {
216 return nil, ErrScopeProjectIDOrProjectName{}
217 }
218
219 if scope.DomainID != "" {
220 // ProjectName + DomainID
221 req.Auth.Scope = &scopeReq{
222 Project: &projectReq{
223 Name: &scope.ProjectName,
224 Domain: &domainReq{ID: &scope.DomainID},
225 },
226 }
227 }
228
229 if scope.DomainName != "" {
230 // ProjectName + DomainName
231 req.Auth.Scope = &scopeReq{
232 Project: &projectReq{
233 Name: &scope.ProjectName,
234 Domain: &domainReq{Name: &scope.DomainName},
235 },
236 }
237 }
238 } else if scope.ProjectID != "" {
239 // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
240 if scope.DomainID != "" {
241 return nil, ErrScopeProjectIDAlone{}
242 }
243 if scope.DomainName != "" {
244 return nil, ErrScopeProjectIDAlone{}
245 }
246
247 // ProjectID
248 req.Auth.Scope = &scopeReq{
249 Project: &projectReq{ID: &scope.ProjectID},
250 }
251 } else if scope.DomainID != "" {
252 // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
253 if scope.DomainName != "" {
254 return nil, ErrScopeDomainIDOrDomainName{}
255 }
256
257 // DomainID
258 req.Auth.Scope = &scopeReq{
259 Domain: &domainReq{ID: &scope.DomainID},
260 }
261 } else if scope.DomainName != "" {
262 return nil, ErrScopeDomainName{}
263 } else {
264 return nil, ErrScopeEmpty{}
265 }
266 }
267
268 b, err2 := gophercloud.BuildRequestBody(req, "")
269 if err2 != nil {
270 return nil, err2
271 }
272 return b, nil
Ash Wilson85d82652014-08-28 13:57:46 -0400273}
274
Ash Wilson6425a412014-08-29 12:30:35 -0400275func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
Krzysztof Kwapisiewiczbaaaf3e2016-02-03 15:18:16 +0100276 return map[string]string{
277 "X-Subject-Token": subjectToken,
278 }
Ash Wilson46d913f2014-08-29 11:00:11 -0400279}
280
Ash Wilsone5550862014-08-28 15:37:09 -0400281// Create authenticates and either generates a new token, or changes the Scope of an existing token.
jrperritt29ae6b32016-04-13 12:59:37 -0500282func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder, scopeOpts *Scope) (r CreateResult) {
Jon Perrittdb0ae142016-03-13 00:33:41 -0600283 b, err := opts.ToTokenV3CreateMap(scopeOpts)
284 if err != nil {
285 r.Err = err
Jon Perritt2be387a2016-03-31 09:31:58 -0500286 return
Ash Wilson85d82652014-08-28 13:57:46 -0400287 }
jrperritt9b7b9e62016-07-11 22:30:50 -0500288 resp, err := c.Post(tokenURL(c), b, &r.Body, &gophercloud.RequestOpts{
289 MoreHeaders: map[string]string{"X-Auth-Token": ""},
290 })
Jon Perrittdb0ae142016-03-13 00:33:41 -0600291 if resp != nil {
jrperritt29ae6b32016-04-13 12:59:37 -0500292 r.Err = err
Jon Perrittdb0ae142016-03-13 00:33:41 -0600293 r.Header = resp.Header
Ash Wilson85d82652014-08-28 13:57:46 -0400294 }
jrperritt29ae6b32016-04-13 12:59:37 -0500295 return
Ash Wilson85d82652014-08-28 13:57:46 -0400296}
Ash Wilson46d913f2014-08-29 11:00:11 -0400297
Ash Wilson5266e492014-09-09 15:44:30 -0400298// Get validates and retrieves information about another token.
Jon Perritt2be387a2016-03-31 09:31:58 -0500299func Get(c *gophercloud.ServiceClient, token string) (r GetResult) {
jrperritt29ae6b32016-04-13 12:59:37 -0500300 resp, err := c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{
Jamie Hannaford562a7d52015-03-24 16:20:16 +0100301 MoreHeaders: subjectTokenHeaders(c, token),
302 OkCodes: []int{200, 203},
Ash Wilson46d913f2014-08-29 11:00:11 -0400303 })
Jon Perrittdb0ae142016-03-13 00:33:41 -0600304 if resp != nil {
jrperritt29ae6b32016-04-13 12:59:37 -0500305 r.Err = err
Jon Perrittdb0ae142016-03-13 00:33:41 -0600306 r.Header = resp.Header
Ash Wilson46d913f2014-08-29 11:00:11 -0400307 }
jrperritt29ae6b32016-04-13 12:59:37 -0500308 return
Ash Wilson46d913f2014-08-29 11:00:11 -0400309}
310
311// Validate determines if a specified token is valid or not.
Ash Wilson6425a412014-08-29 12:30:35 -0400312func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
jrperritt29ae6b32016-04-13 12:59:37 -0500313 resp, err := c.Request("HEAD", tokenURL(c), &gophercloud.RequestOpts{
Ash Wilson46d913f2014-08-29 11:00:11 -0400314 MoreHeaders: subjectTokenHeaders(c, token),
315 OkCodes: []int{204, 404},
316 })
317 if err != nil {
318 return false, err
319 }
320
jrperritt29ae6b32016-04-13 12:59:37 -0500321 return resp.StatusCode == 204, nil
Ash Wilson46d913f2014-08-29 11:00:11 -0400322}
323
324// Revoke immediately makes specified token invalid.
Jon Perritt2be387a2016-03-31 09:31:58 -0500325func Revoke(c *gophercloud.ServiceClient, token string) (r RevokeResult) {
Jon Perrittdb0ae142016-03-13 00:33:41 -0600326 _, r.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{
Ash Wilson46d913f2014-08-29 11:00:11 -0400327 MoreHeaders: subjectTokenHeaders(c, token),
Ash Wilson46d913f2014-08-29 11:00:11 -0400328 })
jrperritt29ae6b32016-04-13 12:59:37 -0500329 return
Ash Wilson46d913f2014-08-29 11:00:11 -0400330}