blob: ab84d81a633176ee5958bc7d317d8c099ed53430 [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 Wilson46d913f2014-08-29 11:00:11 -040017 h := c.AuthenticatedHeaders()
18 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 Wilson6425a412014-08-29 12:30:35 -040023func Create(c *gophercloud.ServiceClient, 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 Wilsone5550862014-08-28 15:37:09 -040070 ao := c.Options
71
Ash Wilson85d82652014-08-28 13:57:46 -040072 // Populate the request structure based on the provided arguments. Create and return an error
73 // if insufficient or incompatible information is present.
74 var req request
75
76 // Test first for unrecognized arguments.
77 if ao.APIKey != "" {
78 return nil, ErrAPIKeyProvided
79 }
80 if ao.TenantID != "" {
81 return nil, ErrTenantIDProvided
82 }
83 if ao.TenantName != "" {
84 return nil, ErrTenantNameProvided
85 }
86
87 if ao.Password == "" {
88 if c.TokenID != "" {
89 // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
90 // parameters.
91 if ao.Username != "" {
92 return nil, ErrUsernameWithToken
93 }
94 if ao.UserID != "" {
95 return nil, ErrUserIDWithToken
96 }
97 if ao.DomainID != "" {
98 return nil, ErrDomainIDWithToken
99 }
100 if ao.DomainName != "" {
101 return nil, ErrDomainNameWithToken
102 }
103
104 // Configure the request for Token authentication.
105 req.Auth.Identity.Methods = []string{"token"}
106 req.Auth.Identity.Token = &tokenReq{
107 ID: c.TokenID,
108 }
109 } else {
110 // If no password or token ID are available, authentication can't continue.
111 return nil, ErrMissingPassword
112 }
113 } else {
114 // Password authentication.
115 req.Auth.Identity.Methods = []string{"password"}
116
117 // At least one of Username and UserID must be specified.
118 if ao.Username == "" && ao.UserID == "" {
119 return nil, ErrUsernameOrUserID
120 }
121
122 if ao.Username != "" {
123 // If Username is provided, UserID may not be provided.
124 if ao.UserID != "" {
125 return nil, ErrUsernameOrUserID
126 }
127
128 // Either DomainID or DomainName must also be specified.
129 if ao.DomainID == "" && ao.DomainName == "" {
130 return nil, ErrDomainIDOrDomainName
131 }
132
133 if ao.DomainID != "" {
134 if ao.DomainName != "" {
135 return nil, ErrDomainIDOrDomainName
136 }
137
138 // Configure the request for Username and Password authentication with a DomainID.
139 req.Auth.Identity.Password = &passwordReq{
140 User: userReq{
141 Name: &ao.Username,
142 Password: ao.Password,
143 Domain: &domainReq{ID: &ao.DomainID},
144 },
145 }
146 }
147
148 if ao.DomainName != "" {
149 // Configure the request for Username and Password authentication with a DomainName.
150 req.Auth.Identity.Password = &passwordReq{
151 User: userReq{
152 Name: &ao.Username,
153 Password: ao.Password,
154 Domain: &domainReq{Name: &ao.DomainName},
155 },
156 }
157 }
158 }
159
160 if ao.UserID != "" {
161 // If UserID is specified, neither DomainID nor DomainName may be.
162 if ao.DomainID != "" {
163 return nil, ErrDomainIDWithUserID
164 }
165 if ao.DomainName != "" {
166 return nil, ErrDomainNameWithUserID
167 }
168
169 // Configure the request for UserID and Password authentication.
170 req.Auth.Identity.Password = &passwordReq{
Ash Wilsoncde68122014-08-28 16:15:43 -0400171 User: userReq{ID: &ao.UserID, Password: ao.Password},
Ash Wilson85d82652014-08-28 13:57:46 -0400172 }
173 }
174 }
175
176 // Add a "scope" element if a Scope has been provided.
177 if scope != nil {
178 if scope.ProjectName != "" {
179 // ProjectName provided: either DomainID or DomainName must also be supplied.
180 // ProjectID may not be supplied.
181 if scope.DomainID == "" && scope.DomainName == "" {
182 return nil, ErrScopeDomainIDOrDomainName
183 }
184 if scope.ProjectID != "" {
185 return nil, ErrScopeProjectIDOrProjectName
186 }
187
188 if scope.DomainID != "" {
189 // ProjectName + DomainID
190 req.Auth.Scope = &scopeReq{
191 Project: &projectReq{
192 Name: &scope.ProjectName,
193 Domain: &domainReq{ID: &scope.DomainID},
194 },
195 }
196 }
197
198 if scope.DomainName != "" {
199 // ProjectName + DomainName
200 req.Auth.Scope = &scopeReq{
201 Project: &projectReq{
202 Name: &scope.ProjectName,
203 Domain: &domainReq{Name: &scope.DomainName},
204 },
205 }
206 }
207 } else if scope.ProjectID != "" {
208 // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
Ash Wilson85d82652014-08-28 13:57:46 -0400209 if scope.DomainID != "" {
210 return nil, ErrScopeProjectIDAlone
211 }
212 if scope.DomainName != "" {
213 return nil, ErrScopeProjectIDAlone
214 }
215
216 // ProjectID
217 req.Auth.Scope = &scopeReq{
218 Project: &projectReq{ID: &scope.ProjectID},
219 }
220 } else if scope.DomainID != "" {
221 // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
222 if scope.DomainName != "" {
223 return nil, ErrScopeDomainIDOrDomainName
224 }
225
226 // DomainID
227 req.Auth.Scope = &scopeReq{
228 Domain: &domainReq{ID: &scope.DomainID},
229 }
230 } else if scope.DomainName != "" {
231 return nil, ErrScopeDomainName
232 } else {
233 return nil, ErrScopeEmpty
234 }
235 }
236
Ash Wilson4a52e2a2014-08-29 09:28:00 -0400237 var result TokenCreateResult
238 response, err := perigee.Request("POST", getTokenURL(c), perigee.Options{
Ash Wilson85d82652014-08-28 13:57:46 -0400239 ReqBody: &req,
Ash Wilson4a52e2a2014-08-29 09:28:00 -0400240 Results: &result.response,
Ash Wilson85d82652014-08-28 13:57:46 -0400241 OkCodes: []int{201},
242 })
Ash Wilson4a52e2a2014-08-29 09:28:00 -0400243 if err != nil {
244 return nil, err
245 }
246
247 // Extract the token ID from the response, if present.
248 result.tokenID = response.HttpResponse.Header.Get("X-Subject-Token")
249
250 return &result, nil
Ash Wilson85d82652014-08-28 13:57:46 -0400251}
Ash Wilson46d913f2014-08-29 11:00:11 -0400252
253// Info validates and retrieves information about another token.
Ash Wilson6425a412014-08-29 12:30:35 -0400254func Info(c *gophercloud.ServiceClient, token string) (*TokenCreateResult, error) {
Ash Wilson46d913f2014-08-29 11:00:11 -0400255 var result TokenCreateResult
256
257 response, err := perigee.Request("GET", getTokenURL(c), perigee.Options{
258 MoreHeaders: subjectTokenHeaders(c, token),
259 Results: &result.response,
260 OkCodes: []int{200, 203},
261 })
262
263 if err != nil {
264 return nil, err
265 }
266
267 // Extract the token ID from the response, if present.
268 result.tokenID = response.HttpResponse.Header.Get("X-Subject-Token")
269
270 return &result, nil
271}
272
273// Validate determines if a specified token is valid or not.
Ash Wilson6425a412014-08-29 12:30:35 -0400274func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
Ash Wilson46d913f2014-08-29 11:00:11 -0400275 response, err := perigee.Request("HEAD", getTokenURL(c), perigee.Options{
276 MoreHeaders: subjectTokenHeaders(c, token),
277 OkCodes: []int{204, 404},
278 })
279 if err != nil {
280 return false, err
281 }
282
283 return response.StatusCode == 204, nil
284}
285
286// Revoke immediately makes specified token invalid.
Ash Wilson6425a412014-08-29 12:30:35 -0400287func Revoke(c *gophercloud.ServiceClient, token string) error {
Ash Wilson46d913f2014-08-29 11:00:11 -0400288 _, err := perigee.Request("DELETE", getTokenURL(c), perigee.Options{
289 MoreHeaders: subjectTokenHeaders(c, token),
290 OkCodes: []int{204},
291 })
292 return err
293}