blob: c8587b686bf11dc64c96d594a286bc0956958d5c [file] [log] [blame]
Jamie Hannaford2aaf1a62014-10-16 12:55:50 +02001package tokens
2
3import (
4 "github.com/racker/perigee"
5 "github.com/rackspace/gophercloud"
6)
7
8// 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
16func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
17 h := c.Provider.AuthenticatedHeaders()
18 h["X-Subject-Token"] = subjectToken
19 return h
20}
21
22// Create authenticates and either generates a new token, or changes the Scope of an existing token.
23func Create(c *gophercloud.ServiceClient, options gophercloud.AuthOptions, scope *Scope) CreateResult {
24 type domainReq struct {
25 ID *string `json:"id,omitempty"`
26 Name *string `json:"name,omitempty"`
27 }
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"`
39 Domain *domainReq `json:"domain,omitempty"`
40 }
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"`
52 Password *passwordReq `json:"password,omitempty"`
53 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"`
63 Scope *scopeReq `json:"scope,omitempty"`
64 }
65
66 type request struct {
67 Auth authReq `json:"auth"`
68 }
69
70 // 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.
75 if options.APIKey != "" {
76 return createErr(ErrAPIKeyProvided)
77 }
78 if options.TenantID != "" {
79 return createErr(ErrTenantIDProvided)
80 }
81 if options.TenantName != "" {
82 return createErr(ErrTenantNameProvided)
83 }
84
85 if options.Password == "" {
86 if c.Provider.TokenID != "" {
87 // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
88 // parameters.
89 if options.Username != "" {
90 return createErr(ErrUsernameWithToken)
91 }
92 if options.UserID != "" {
93 return createErr(ErrUserIDWithToken)
94 }
95 if options.DomainID != "" {
96 return createErr(ErrDomainIDWithToken)
97 }
98 if options.DomainName != "" {
99 return createErr(ErrDomainNameWithToken)
100 }
101
102 // Configure the request for Token authentication.
103 req.Auth.Identity.Methods = []string{"token"}
104 req.Auth.Identity.Token = &tokenReq{
105 ID: c.Provider.TokenID,
106 }
107 } else {
108 // If no password or token ID are available, authentication can't continue.
109 return createErr(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.
116 if options.Username == "" && options.UserID == "" {
117 return createErr(ErrUsernameOrUserID)
118 }
119
120 if options.Username != "" {
121 // If Username is provided, UserID may not be provided.
122 if options.UserID != "" {
123 return createErr(ErrUsernameOrUserID)
124 }
125
126 // Either DomainID or DomainName must also be specified.
127 if options.DomainID == "" && options.DomainName == "" {
128 return createErr(ErrDomainIDOrDomainName)
129 }
130
131 if options.DomainID != "" {
132 if options.DomainName != "" {
133 return createErr(ErrDomainIDOrDomainName)
134 }
135
136 // Configure the request for Username and Password authentication with a DomainID.
137 req.Auth.Identity.Password = &passwordReq{
138 User: userReq{
139 Name: &options.Username,
140 Password: options.Password,
141 Domain: &domainReq{ID: &options.DomainID},
142 },
143 }
144 }
145
146 if options.DomainName != "" {
147 // Configure the request for Username and Password authentication with a DomainName.
148 req.Auth.Identity.Password = &passwordReq{
149 User: userReq{
150 Name: &options.Username,
151 Password: options.Password,
152 Domain: &domainReq{Name: &options.DomainName},
153 },
154 }
155 }
156 }
157
158 if options.UserID != "" {
159 // If UserID is specified, neither DomainID nor DomainName may be.
160 if options.DomainID != "" {
161 return createErr(ErrDomainIDWithUserID)
162 }
163 if options.DomainName != "" {
164 return createErr(ErrDomainNameWithUserID)
165 }
166
167 // Configure the request for UserID and Password authentication.
168 req.Auth.Identity.Password = &passwordReq{
169 User: userReq{ID: &options.UserID, Password: options.Password},
170 }
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 createErr(ErrScopeDomainIDOrDomainName)
181 }
182 if scope.ProjectID != "" {
183 return createErr(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.
207 if scope.DomainID != "" {
208 return createErr(ErrScopeProjectIDAlone)
209 }
210 if scope.DomainName != "" {
211 return createErr(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 createErr(ErrScopeDomainIDOrDomainName)
222 }
223
224 // DomainID
225 req.Auth.Scope = &scopeReq{
226 Domain: &domainReq{ID: &scope.DomainID},
227 }
228 } else if scope.DomainName != "" {
229 return createErr(ErrScopeDomainName)
230 } else {
231 return createErr(ErrScopeEmpty)
232 }
233 }
234
235 var result CreateResult
236 var response *perigee.Response
237 response, result.Err = perigee.Request("POST", tokenURL(c), perigee.Options{
238 ReqBody: &req,
239 Results: &result.Resp,
240 OkCodes: []int{201},
241 })
242 if result.Err != nil {
243 return result
244 }
245 result.header = response.HttpResponse.Header
246 return result
247}
248
249// Get validates and retrieves information about another token.
250func Get(c *gophercloud.ServiceClient, token string) GetResult {
251 var result GetResult
252 var response *perigee.Response
253 response, result.Err = perigee.Request("GET", tokenURL(c), perigee.Options{
254 MoreHeaders: subjectTokenHeaders(c, token),
255 Results: &result.Resp,
256 OkCodes: []int{200, 203},
257 })
258 if result.Err != nil {
259 return result
260 }
261 result.header = response.HttpResponse.Header
262 return result
263}
264
265// Validate determines if a specified token is valid or not.
266func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
267 response, err := perigee.Request("HEAD", tokenURL(c), perigee.Options{
268 MoreHeaders: subjectTokenHeaders(c, token),
269 OkCodes: []int{204, 404},
270 })
271 if err != nil {
272 return false, err
273 }
274
275 return response.StatusCode == 204, nil
276}
277
278// Revoke immediately makes specified token invalid.
279func Revoke(c *gophercloud.ServiceClient, token string) error {
280 _, err := perigee.Request("DELETE", tokenURL(c), perigee.Options{
281 MoreHeaders: subjectTokenHeaders(c, token),
282 OkCodes: []int{204},
283 })
284 return err
285}