blob: 351d7d664b492ac7d725b3c495c3b6c66278b7aa [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 Wilson77857dc2014-10-22 09:09:02 -040017 h := c.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 Wilsonf8d546a2014-09-30 17:43:25 -040023func Create(c *gophercloud.ServiceClient, options gophercloud.AuthOptions, scope *Scope) CreateResult {
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 Wilsonf8d546a2014-09-30 17:43:25 -040076 return createErr(ErrAPIKeyProvided)
Ash Wilson85d82652014-08-28 13:57:46 -040077 }
Ash Wilsona87ee062014-09-03 11:26:06 -040078 if options.TenantID != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -040079 return createErr(ErrTenantIDProvided)
Ash Wilson85d82652014-08-28 13:57:46 -040080 }
Ash Wilsona87ee062014-09-03 11:26:06 -040081 if options.TenantName != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -040082 return createErr(ErrTenantNameProvided)
Ash Wilson85d82652014-08-28 13:57:46 -040083 }
84
Ash Wilsona87ee062014-09-03 11:26:06 -040085 if options.Password == "" {
Ash Wilsond7f73e92014-10-22 09:11:49 -040086 if c.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 Wilsonf8d546a2014-09-30 17:43:25 -040090 return createErr(ErrUsernameWithToken)
Ash Wilson85d82652014-08-28 13:57:46 -040091 }
Ash Wilsona87ee062014-09-03 11:26:06 -040092 if options.UserID != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -040093 return createErr(ErrUserIDWithToken)
Ash Wilson85d82652014-08-28 13:57:46 -040094 }
Ash Wilsona87ee062014-09-03 11:26:06 -040095 if options.DomainID != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -040096 return createErr(ErrDomainIDWithToken)
Ash Wilson85d82652014-08-28 13:57:46 -040097 }
Ash Wilsona87ee062014-09-03 11:26:06 -040098 if options.DomainName != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -040099 return createErr(ErrDomainNameWithToken)
Ash Wilson85d82652014-08-28 13:57:46 -0400100 }
101
102 // Configure the request for Token authentication.
103 req.Auth.Identity.Methods = []string{"token"}
104 req.Auth.Identity.Token = &tokenReq{
Ash Wilsond7f73e92014-10-22 09:11:49 -0400105 ID: c.TokenID,
Ash Wilson85d82652014-08-28 13:57:46 -0400106 }
107 } else {
108 // If no password or token ID are available, authentication can't continue.
Ash Wilson55f24332014-10-02 09:37:05 -0400109 return createErr(ErrMissingPassword)
Ash Wilson85d82652014-08-28 13:57:46 -0400110 }
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 Wilsonf8d546a2014-09-30 17:43:25 -0400117 return createErr(ErrUsernameOrUserID)
Ash Wilson85d82652014-08-28 13:57:46 -0400118 }
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 Wilsonf8d546a2014-09-30 17:43:25 -0400123 return createErr(ErrUsernameOrUserID)
Ash Wilson85d82652014-08-28 13:57:46 -0400124 }
125
126 // Either DomainID or DomainName must also be specified.
Ash Wilsona87ee062014-09-03 11:26:06 -0400127 if options.DomainID == "" && options.DomainName == "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400128 return createErr(ErrDomainIDOrDomainName)
Ash Wilson85d82652014-08-28 13:57:46 -0400129 }
130
Ash Wilsona87ee062014-09-03 11:26:06 -0400131 if options.DomainID != "" {
132 if options.DomainName != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400133 return createErr(ErrDomainIDOrDomainName)
Ash Wilson85d82652014-08-28 13:57:46 -0400134 }
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 Wilsonf8d546a2014-09-30 17:43:25 -0400161 return createErr(ErrDomainIDWithUserID)
Ash Wilson85d82652014-08-28 13:57:46 -0400162 }
Ash Wilsona87ee062014-09-03 11:26:06 -0400163 if options.DomainName != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400164 return createErr(ErrDomainNameWithUserID)
Ash Wilson85d82652014-08-28 13:57:46 -0400165 }
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 == "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400180 return createErr(ErrScopeDomainIDOrDomainName)
Ash Wilson85d82652014-08-28 13:57:46 -0400181 }
182 if scope.ProjectID != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400183 return createErr(ErrScopeProjectIDOrProjectName)
Ash Wilson85d82652014-08-28 13:57:46 -0400184 }
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 != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400208 return createErr(ErrScopeProjectIDAlone)
Ash Wilson85d82652014-08-28 13:57:46 -0400209 }
210 if scope.DomainName != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400211 return createErr(ErrScopeProjectIDAlone)
Ash Wilson85d82652014-08-28 13:57:46 -0400212 }
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 != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400221 return createErr(ErrScopeDomainIDOrDomainName)
Ash Wilson85d82652014-08-28 13:57:46 -0400222 }
223
224 // DomainID
225 req.Auth.Scope = &scopeReq{
226 Domain: &domainReq{ID: &scope.DomainID},
227 }
228 } else if scope.DomainName != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400229 return createErr(ErrScopeDomainName)
Ash Wilson85d82652014-08-28 13:57:46 -0400230 } else {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400231 return createErr(ErrScopeEmpty)
Ash Wilson85d82652014-08-28 13:57:46 -0400232 }
233 }
234
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400235 var result CreateResult
236 var response *perigee.Response
237 response, result.Err = perigee.Request("POST", tokenURL(c), perigee.Options{
Ash Wilson85d82652014-08-28 13:57:46 -0400238 ReqBody: &req,
Ash Wilsond3dc2542014-10-20 10:10:48 -0400239 Results: &result.Body,
Ash Wilson85d82652014-08-28 13:57:46 -0400240 OkCodes: []int{201},
241 })
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400242 if result.Err != nil {
243 return result
Ash Wilson4a52e2a2014-08-29 09:28:00 -0400244 }
Ash Wilson72e4d2c2014-10-20 10:27:30 -0400245 result.Header = response.HttpResponse.Header
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400246 return result
Ash Wilson85d82652014-08-28 13:57:46 -0400247}
Ash Wilson46d913f2014-08-29 11:00:11 -0400248
Ash Wilson5266e492014-09-09 15:44:30 -0400249// Get validates and retrieves information about another token.
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400250func 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{
Ash Wilson46d913f2014-08-29 11:00:11 -0400254 MoreHeaders: subjectTokenHeaders(c, token),
Ash Wilsond3dc2542014-10-20 10:10:48 -0400255 Results: &result.Body,
Ash Wilson46d913f2014-08-29 11:00:11 -0400256 OkCodes: []int{200, 203},
257 })
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400258 if result.Err != nil {
259 return result
Ash Wilson46d913f2014-08-29 11:00:11 -0400260 }
Ash Wilson72e4d2c2014-10-20 10:27:30 -0400261 result.Header = response.HttpResponse.Header
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400262 return result
Ash Wilson46d913f2014-08-29 11:00:11 -0400263}
264
265// Validate determines if a specified token is valid or not.
Ash Wilson6425a412014-08-29 12:30:35 -0400266func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
Ash Wilson1e1b7332014-09-30 16:38:11 -0400267 response, err := perigee.Request("HEAD", tokenURL(c), perigee.Options{
Ash Wilson46d913f2014-08-29 11:00:11 -0400268 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.
Ash Wilson6425a412014-08-29 12:30:35 -0400279func Revoke(c *gophercloud.ServiceClient, token string) error {
Ash Wilson1e1b7332014-09-30 16:38:11 -0400280 _, err := perigee.Request("DELETE", tokenURL(c), perigee.Options{
Ash Wilson46d913f2014-08-29 11:00:11 -0400281 MoreHeaders: subjectTokenHeaders(c, token),
282 OkCodes: []int{204},
283 })
284 return err
285}