blob: ab4ae052be38edffb70ba9544a371d3d2113083f [file] [log] [blame]
Ash Wilson85d82652014-08-28 13:57:46 -04001package tokens
2
3import (
Ash Wilson85d82652014-08-28 13:57:46 -04004 "github.com/racker/perigee"
5 "github.com/rackspace/gophercloud"
Ash Wilson85d82652014-08-28 13:57:46 -04006)
7
Ash Wilson85d82652014-08-28 13:57:46 -04008// Scope allows a created token to be limited to a specific domain or project.
9type Scope struct {
10 ProjectID string
11 ProjectName string
12 DomainID string
13 DomainName string
14}
15
Ash Wilson6425a412014-08-29 12:30:35 -040016func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
Ash Wilsona87ee062014-09-03 11:26:06 -040017 h := c.Provider.AuthenticatedHeaders()
Ash Wilson46d913f2014-08-29 11:00:11 -040018 h["X-Subject-Token"] = subjectToken
19 return h
20}
21
Ash Wilsone5550862014-08-28 15:37:09 -040022// Create authenticates and either generates a new token, or changes the Scope of an existing token.
Ash Wilsona87ee062014-09-03 11:26:06 -040023func Create(c *gophercloud.ServiceClient, options gophercloud.AuthOptions, scope *Scope) (gophercloud.AuthResults, error) {
Ash Wilson85d82652014-08-28 13:57:46 -040024 type domainReq struct {
25 ID *string `json:"id,omitempty"`
Ash Wilson417d9222014-08-29 07:58:35 -040026 Name *string `json:"name,omitempty"`
Ash Wilson85d82652014-08-28 13:57:46 -040027 }
28
29 type projectReq struct {
30 Domain *domainReq `json:"domain,omitempty"`
31 Name *string `json:"name,omitempty"`
32 ID *string `json:"id,omitempty"`
33 }
34
35 type userReq struct {
36 ID *string `json:"id,omitempty"`
37 Name *string `json:"name,omitempty"`
38 Password string `json:"password"`
Ash Wilsoncde68122014-08-28 16:15:43 -040039 Domain *domainReq `json:"domain,omitempty"`
Ash Wilson85d82652014-08-28 13:57:46 -040040 }
41
42 type passwordReq struct {
43 User userReq `json:"user"`
44 }
45
46 type tokenReq struct {
47 ID string `json:"id"`
48 }
49
50 type identityReq struct {
51 Methods []string `json:"methods"`
Ash Wilsoncde68122014-08-28 16:15:43 -040052 Password *passwordReq `json:"password,omitempty"`
Ash Wilson85d82652014-08-28 13:57:46 -040053 Token *tokenReq `json:"token,omitempty"`
54 }
55
56 type scopeReq struct {
57 Domain *domainReq `json:"domain,omitempty"`
58 Project *projectReq `json:"project,omitempty"`
59 }
60
61 type authReq struct {
62 Identity identityReq `json:"identity"`
Ash Wilsoncde68122014-08-28 16:15:43 -040063 Scope *scopeReq `json:"scope,omitempty"`
Ash Wilson85d82652014-08-28 13:57:46 -040064 }
65
66 type request struct {
67 Auth authReq `json:"auth"`
68 }
69
Ash Wilson85d82652014-08-28 13:57:46 -040070 // Populate the request structure based on the provided arguments. Create and return an error
71 // if insufficient or incompatible information is present.
72 var req request
73
74 // Test first for unrecognized arguments.
Ash Wilsona87ee062014-09-03 11:26:06 -040075 if options.APIKey != "" {
Ash Wilson85d82652014-08-28 13:57:46 -040076 return nil, ErrAPIKeyProvided
77 }
Ash Wilsona87ee062014-09-03 11:26:06 -040078 if options.TenantID != "" {
Ash Wilson85d82652014-08-28 13:57:46 -040079 return nil, ErrTenantIDProvided
80 }
Ash Wilsona87ee062014-09-03 11:26:06 -040081 if options.TenantName != "" {
Ash Wilson85d82652014-08-28 13:57:46 -040082 return nil, ErrTenantNameProvided
83 }
84
Ash Wilsona87ee062014-09-03 11:26:06 -040085 if options.Password == "" {
86 if c.Provider.TokenID != "" {
Ash Wilson85d82652014-08-28 13:57:46 -040087 // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
88 // parameters.
Ash Wilsona87ee062014-09-03 11:26:06 -040089 if options.Username != "" {
Ash Wilson85d82652014-08-28 13:57:46 -040090 return nil, ErrUsernameWithToken
91 }
Ash Wilsona87ee062014-09-03 11:26:06 -040092 if options.UserID != "" {
Ash Wilson85d82652014-08-28 13:57:46 -040093 return nil, ErrUserIDWithToken
94 }
Ash Wilsona87ee062014-09-03 11:26:06 -040095 if options.DomainID != "" {
Ash Wilson85d82652014-08-28 13:57:46 -040096 return nil, ErrDomainIDWithToken
97 }
Ash Wilsona87ee062014-09-03 11:26:06 -040098 if options.DomainName != "" {
Ash Wilson85d82652014-08-28 13:57:46 -040099 return nil, ErrDomainNameWithToken
100 }
101
102 // Configure the request for Token authentication.
103 req.Auth.Identity.Methods = []string{"token"}
104 req.Auth.Identity.Token = &tokenReq{
Ash Wilsona87ee062014-09-03 11:26:06 -0400105 ID: c.Provider.TokenID,
Ash Wilson85d82652014-08-28 13:57:46 -0400106 }
107 } else {
108 // If no password or token ID are available, authentication can't continue.
109 return nil, ErrMissingPassword
110 }
111 } else {
112 // Password authentication.
113 req.Auth.Identity.Methods = []string{"password"}
114
115 // At least one of Username and UserID must be specified.
Ash Wilsona87ee062014-09-03 11:26:06 -0400116 if options.Username == "" && options.UserID == "" {
Ash Wilson85d82652014-08-28 13:57:46 -0400117 return nil, ErrUsernameOrUserID
118 }
119
Ash Wilsona87ee062014-09-03 11:26:06 -0400120 if options.Username != "" {
Ash Wilson85d82652014-08-28 13:57:46 -0400121 // If Username is provided, UserID may not be provided.
Ash Wilsona87ee062014-09-03 11:26:06 -0400122 if options.UserID != "" {
Ash Wilson85d82652014-08-28 13:57:46 -0400123 return nil, ErrUsernameOrUserID
124 }
125
126 // Either DomainID or DomainName must also be specified.
Ash Wilsona87ee062014-09-03 11:26:06 -0400127 if options.DomainID == "" && options.DomainName == "" {
Ash Wilson85d82652014-08-28 13:57:46 -0400128 return nil, ErrDomainIDOrDomainName
129 }
130
Ash Wilsona87ee062014-09-03 11:26:06 -0400131 if options.DomainID != "" {
132 if options.DomainName != "" {
Ash Wilson85d82652014-08-28 13:57:46 -0400133 return nil, ErrDomainIDOrDomainName
134 }
135
136 // Configure the request for Username and Password authentication with a DomainID.
137 req.Auth.Identity.Password = &passwordReq{
138 User: userReq{
Ash Wilsona87ee062014-09-03 11:26:06 -0400139 Name: &options.Username,
140 Password: options.Password,
141 Domain: &domainReq{ID: &options.DomainID},
Ash Wilson85d82652014-08-28 13:57:46 -0400142 },
143 }
144 }
145
Ash Wilsona87ee062014-09-03 11:26:06 -0400146 if options.DomainName != "" {
Ash Wilson85d82652014-08-28 13:57:46 -0400147 // Configure the request for Username and Password authentication with a DomainName.
148 req.Auth.Identity.Password = &passwordReq{
149 User: userReq{
Ash Wilsona87ee062014-09-03 11:26:06 -0400150 Name: &options.Username,
151 Password: options.Password,
152 Domain: &domainReq{Name: &options.DomainName},
Ash Wilson85d82652014-08-28 13:57:46 -0400153 },
154 }
155 }
156 }
157
Ash Wilsona87ee062014-09-03 11:26:06 -0400158 if options.UserID != "" {
Ash Wilson85d82652014-08-28 13:57:46 -0400159 // If UserID is specified, neither DomainID nor DomainName may be.
Ash Wilsona87ee062014-09-03 11:26:06 -0400160 if options.DomainID != "" {
Ash Wilson85d82652014-08-28 13:57:46 -0400161 return nil, ErrDomainIDWithUserID
162 }
Ash Wilsona87ee062014-09-03 11:26:06 -0400163 if options.DomainName != "" {
Ash Wilson85d82652014-08-28 13:57:46 -0400164 return nil, ErrDomainNameWithUserID
165 }
166
167 // Configure the request for UserID and Password authentication.
168 req.Auth.Identity.Password = &passwordReq{
Ash Wilsona87ee062014-09-03 11:26:06 -0400169 User: userReq{ID: &options.UserID, Password: options.Password},
Ash Wilson85d82652014-08-28 13:57:46 -0400170 }
171 }
172 }
173
174 // Add a "scope" element if a Scope has been provided.
175 if scope != nil {
176 if scope.ProjectName != "" {
177 // ProjectName provided: either DomainID or DomainName must also be supplied.
178 // ProjectID may not be supplied.
179 if scope.DomainID == "" && scope.DomainName == "" {
180 return nil, ErrScopeDomainIDOrDomainName
181 }
182 if scope.ProjectID != "" {
183 return nil, ErrScopeProjectIDOrProjectName
184 }
185
186 if scope.DomainID != "" {
187 // ProjectName + DomainID
188 req.Auth.Scope = &scopeReq{
189 Project: &projectReq{
190 Name: &scope.ProjectName,
191 Domain: &domainReq{ID: &scope.DomainID},
192 },
193 }
194 }
195
196 if scope.DomainName != "" {
197 // ProjectName + DomainName
198 req.Auth.Scope = &scopeReq{
199 Project: &projectReq{
200 Name: &scope.ProjectName,
201 Domain: &domainReq{Name: &scope.DomainName},
202 },
203 }
204 }
205 } else if scope.ProjectID != "" {
206 // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
Ash Wilson85d82652014-08-28 13:57:46 -0400207 if scope.DomainID != "" {
208 return nil, ErrScopeProjectIDAlone
209 }
210 if scope.DomainName != "" {
211 return nil, ErrScopeProjectIDAlone
212 }
213
214 // ProjectID
215 req.Auth.Scope = &scopeReq{
216 Project: &projectReq{ID: &scope.ProjectID},
217 }
218 } else if scope.DomainID != "" {
219 // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
220 if scope.DomainName != "" {
221 return nil, ErrScopeDomainIDOrDomainName
222 }
223
224 // DomainID
225 req.Auth.Scope = &scopeReq{
226 Domain: &domainReq{ID: &scope.DomainID},
227 }
228 } else if scope.DomainName != "" {
229 return nil, ErrScopeDomainName
230 } else {
231 return nil, ErrScopeEmpty
232 }
233 }
234
Ash Wilson4a52e2a2014-08-29 09:28:00 -0400235 var result TokenCreateResult
236 response, err := perigee.Request("POST", getTokenURL(c), perigee.Options{
Ash Wilson85d82652014-08-28 13:57:46 -0400237 ReqBody: &req,
Ash Wilson4a52e2a2014-08-29 09:28:00 -0400238 Results: &result.response,
Ash Wilson85d82652014-08-28 13:57:46 -0400239 OkCodes: []int{201},
240 })
Ash Wilson4a52e2a2014-08-29 09:28:00 -0400241 if err != nil {
242 return nil, err
243 }
244
245 // Extract the token ID from the response, if present.
246 result.tokenID = response.HttpResponse.Header.Get("X-Subject-Token")
247
248 return &result, nil
Ash Wilson85d82652014-08-28 13:57:46 -0400249}
Ash Wilson46d913f2014-08-29 11:00:11 -0400250
Ash Wilson5266e492014-09-09 15:44:30 -0400251// Get validates and retrieves information about another token.
252func Get(c *gophercloud.ServiceClient, token string) (*TokenCreateResult, error) {
Ash Wilson46d913f2014-08-29 11:00:11 -0400253 var result TokenCreateResult
254
255 response, err := perigee.Request("GET", getTokenURL(c), perigee.Options{
256 MoreHeaders: subjectTokenHeaders(c, token),
257 Results: &result.response,
258 OkCodes: []int{200, 203},
259 })
260
261 if err != nil {
262 return nil, err
263 }
264
265 // Extract the token ID from the response, if present.
266 result.tokenID = response.HttpResponse.Header.Get("X-Subject-Token")
267
268 return &result, nil
269}
270
271// Validate determines if a specified token is valid or not.
Ash Wilson6425a412014-08-29 12:30:35 -0400272func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
Ash Wilson46d913f2014-08-29 11:00:11 -0400273 response, err := perigee.Request("HEAD", getTokenURL(c), perigee.Options{
274 MoreHeaders: subjectTokenHeaders(c, token),
275 OkCodes: []int{204, 404},
276 })
277 if err != nil {
278 return false, err
279 }
280
281 return response.StatusCode == 204, nil
282}
283
284// Revoke immediately makes specified token invalid.
Ash Wilson6425a412014-08-29 12:30:35 -0400285func Revoke(c *gophercloud.ServiceClient, token string) error {
Ash Wilson46d913f2014-08-29 11:00:11 -0400286 _, err := perigee.Request("DELETE", getTokenURL(c), perigee.Options{
287 MoreHeaders: subjectTokenHeaders(c, token),
288 OkCodes: []int{204},
289 })
290 return err
291}