blob: d449ca36e89c04eb9ef9e96766e28062d4a680e1 [file] [log] [blame]
Ash Wilson85d82652014-08-28 13:57:46 -04001package tokens
2
3import (
Ash Wilson2491b4c2015-02-12 16:13:39 -05004 "net/http"
5
Ash Wilson85d82652014-08-28 13:57:46 -04006 "github.com/rackspace/gophercloud"
Ash Wilson85d82652014-08-28 13:57:46 -04007)
8
Ash Wilson85d82652014-08-28 13:57:46 -04009// Scope allows a created token to be limited to a specific domain or project.
10type Scope struct {
11 ProjectID string
12 ProjectName string
13 DomainID string
14 DomainName string
15}
16
Ash Wilson6425a412014-08-29 12:30:35 -040017func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
Ash Wilson77857dc2014-10-22 09:09:02 -040018 h := c.AuthenticatedHeaders()
Ash Wilson46d913f2014-08-29 11:00:11 -040019 h["X-Subject-Token"] = subjectToken
20 return h
21}
22
Ash Wilsone5550862014-08-28 15:37:09 -040023// Create authenticates and either generates a new token, or changes the Scope of an existing token.
Ash Wilsonf8d546a2014-09-30 17:43:25 -040024func Create(c *gophercloud.ServiceClient, options gophercloud.AuthOptions, scope *Scope) CreateResult {
Ash Wilson85d82652014-08-28 13:57:46 -040025 type domainReq struct {
26 ID *string `json:"id,omitempty"`
Ash Wilson417d9222014-08-29 07:58:35 -040027 Name *string `json:"name,omitempty"`
Ash Wilson85d82652014-08-28 13:57:46 -040028 }
29
30 type projectReq struct {
31 Domain *domainReq `json:"domain,omitempty"`
32 Name *string `json:"name,omitempty"`
33 ID *string `json:"id,omitempty"`
34 }
35
36 type userReq struct {
37 ID *string `json:"id,omitempty"`
38 Name *string `json:"name,omitempty"`
39 Password string `json:"password"`
Ash Wilsoncde68122014-08-28 16:15:43 -040040 Domain *domainReq `json:"domain,omitempty"`
Ash Wilson85d82652014-08-28 13:57:46 -040041 }
42
43 type passwordReq struct {
44 User userReq `json:"user"`
45 }
46
47 type tokenReq struct {
48 ID string `json:"id"`
49 }
50
51 type identityReq struct {
52 Methods []string `json:"methods"`
Ash Wilsoncde68122014-08-28 16:15:43 -040053 Password *passwordReq `json:"password,omitempty"`
Ash Wilson85d82652014-08-28 13:57:46 -040054 Token *tokenReq `json:"token,omitempty"`
55 }
56
57 type scopeReq struct {
58 Domain *domainReq `json:"domain,omitempty"`
59 Project *projectReq `json:"project,omitempty"`
60 }
61
62 type authReq struct {
63 Identity identityReq `json:"identity"`
Ash Wilsoncde68122014-08-28 16:15:43 -040064 Scope *scopeReq `json:"scope,omitempty"`
Ash Wilson85d82652014-08-28 13:57:46 -040065 }
66
67 type request struct {
68 Auth authReq `json:"auth"`
69 }
70
Ash Wilson85d82652014-08-28 13:57:46 -040071 // Populate the request structure based on the provided arguments. Create and return an error
72 // if insufficient or incompatible information is present.
73 var req request
74
75 // Test first for unrecognized arguments.
Ash Wilsona87ee062014-09-03 11:26:06 -040076 if options.APIKey != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -040077 return createErr(ErrAPIKeyProvided)
Ash Wilson85d82652014-08-28 13:57:46 -040078 }
Ash Wilsona87ee062014-09-03 11:26:06 -040079 if options.TenantID != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -040080 return createErr(ErrTenantIDProvided)
Ash Wilson85d82652014-08-28 13:57:46 -040081 }
Ash Wilsona87ee062014-09-03 11:26:06 -040082 if options.TenantName != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -040083 return createErr(ErrTenantNameProvided)
Ash Wilson85d82652014-08-28 13:57:46 -040084 }
85
Ash Wilsona87ee062014-09-03 11:26:06 -040086 if options.Password == "" {
Ash Wilsond7f73e92014-10-22 09:11:49 -040087 if c.TokenID != "" {
Ash Wilson85d82652014-08-28 13:57:46 -040088 // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
89 // parameters.
Ash Wilsona87ee062014-09-03 11:26:06 -040090 if options.Username != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -040091 return createErr(ErrUsernameWithToken)
Ash Wilson85d82652014-08-28 13:57:46 -040092 }
Ash Wilsona87ee062014-09-03 11:26:06 -040093 if options.UserID != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -040094 return createErr(ErrUserIDWithToken)
Ash Wilson85d82652014-08-28 13:57:46 -040095 }
Ash Wilsona87ee062014-09-03 11:26:06 -040096 if options.DomainID != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -040097 return createErr(ErrDomainIDWithToken)
Ash Wilson85d82652014-08-28 13:57:46 -040098 }
Ash Wilsona87ee062014-09-03 11:26:06 -040099 if options.DomainName != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400100 return createErr(ErrDomainNameWithToken)
Ash Wilson85d82652014-08-28 13:57:46 -0400101 }
102
103 // Configure the request for Token authentication.
104 req.Auth.Identity.Methods = []string{"token"}
105 req.Auth.Identity.Token = &tokenReq{
Ash Wilsond7f73e92014-10-22 09:11:49 -0400106 ID: c.TokenID,
Ash Wilson85d82652014-08-28 13:57:46 -0400107 }
108 } else {
109 // If no password or token ID are available, authentication can't continue.
Ash Wilson55f24332014-10-02 09:37:05 -0400110 return createErr(ErrMissingPassword)
Ash Wilson85d82652014-08-28 13:57:46 -0400111 }
112 } else {
113 // Password authentication.
114 req.Auth.Identity.Methods = []string{"password"}
115
116 // At least one of Username and UserID must be specified.
Ash Wilsona87ee062014-09-03 11:26:06 -0400117 if options.Username == "" && options.UserID == "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400118 return createErr(ErrUsernameOrUserID)
Ash Wilson85d82652014-08-28 13:57:46 -0400119 }
120
Ash Wilsona87ee062014-09-03 11:26:06 -0400121 if options.Username != "" {
Ash Wilson85d82652014-08-28 13:57:46 -0400122 // If Username is provided, UserID may not be provided.
Ash Wilsona87ee062014-09-03 11:26:06 -0400123 if options.UserID != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400124 return createErr(ErrUsernameOrUserID)
Ash Wilson85d82652014-08-28 13:57:46 -0400125 }
126
127 // Either DomainID or DomainName must also be specified.
Ash Wilsona87ee062014-09-03 11:26:06 -0400128 if options.DomainID == "" && options.DomainName == "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400129 return createErr(ErrDomainIDOrDomainName)
Ash Wilson85d82652014-08-28 13:57:46 -0400130 }
131
Ash Wilsona87ee062014-09-03 11:26:06 -0400132 if options.DomainID != "" {
133 if options.DomainName != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400134 return createErr(ErrDomainIDOrDomainName)
Ash Wilson85d82652014-08-28 13:57:46 -0400135 }
136
137 // Configure the request for Username and Password authentication with a DomainID.
138 req.Auth.Identity.Password = &passwordReq{
139 User: userReq{
Ash Wilsona87ee062014-09-03 11:26:06 -0400140 Name: &options.Username,
141 Password: options.Password,
142 Domain: &domainReq{ID: &options.DomainID},
Ash Wilson85d82652014-08-28 13:57:46 -0400143 },
144 }
145 }
146
Ash Wilsona87ee062014-09-03 11:26:06 -0400147 if options.DomainName != "" {
Ash Wilson85d82652014-08-28 13:57:46 -0400148 // Configure the request for Username and Password authentication with a DomainName.
149 req.Auth.Identity.Password = &passwordReq{
150 User: userReq{
Ash Wilsona87ee062014-09-03 11:26:06 -0400151 Name: &options.Username,
152 Password: options.Password,
153 Domain: &domainReq{Name: &options.DomainName},
Ash Wilson85d82652014-08-28 13:57:46 -0400154 },
155 }
156 }
157 }
158
Ash Wilsona87ee062014-09-03 11:26:06 -0400159 if options.UserID != "" {
Ash Wilson85d82652014-08-28 13:57:46 -0400160 // If UserID is specified, neither DomainID nor DomainName may be.
Ash Wilsona87ee062014-09-03 11:26:06 -0400161 if options.DomainID != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400162 return createErr(ErrDomainIDWithUserID)
Ash Wilson85d82652014-08-28 13:57:46 -0400163 }
Ash Wilsona87ee062014-09-03 11:26:06 -0400164 if options.DomainName != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400165 return createErr(ErrDomainNameWithUserID)
Ash Wilson85d82652014-08-28 13:57:46 -0400166 }
167
168 // Configure the request for UserID and Password authentication.
169 req.Auth.Identity.Password = &passwordReq{
Ash Wilsona87ee062014-09-03 11:26:06 -0400170 User: userReq{ID: &options.UserID, Password: options.Password},
Ash Wilson85d82652014-08-28 13:57:46 -0400171 }
172 }
173 }
174
175 // Add a "scope" element if a Scope has been provided.
176 if scope != nil {
177 if scope.ProjectName != "" {
178 // ProjectName provided: either DomainID or DomainName must also be supplied.
179 // ProjectID may not be supplied.
180 if scope.DomainID == "" && scope.DomainName == "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400181 return createErr(ErrScopeDomainIDOrDomainName)
Ash Wilson85d82652014-08-28 13:57:46 -0400182 }
183 if scope.ProjectID != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400184 return createErr(ErrScopeProjectIDOrProjectName)
Ash Wilson85d82652014-08-28 13:57:46 -0400185 }
186
187 if scope.DomainID != "" {
188 // ProjectName + DomainID
189 req.Auth.Scope = &scopeReq{
190 Project: &projectReq{
191 Name: &scope.ProjectName,
192 Domain: &domainReq{ID: &scope.DomainID},
193 },
194 }
195 }
196
197 if scope.DomainName != "" {
198 // ProjectName + DomainName
199 req.Auth.Scope = &scopeReq{
200 Project: &projectReq{
201 Name: &scope.ProjectName,
202 Domain: &domainReq{Name: &scope.DomainName},
203 },
204 }
205 }
206 } else if scope.ProjectID != "" {
207 // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
Ash Wilson85d82652014-08-28 13:57:46 -0400208 if scope.DomainID != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400209 return createErr(ErrScopeProjectIDAlone)
Ash Wilson85d82652014-08-28 13:57:46 -0400210 }
211 if scope.DomainName != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400212 return createErr(ErrScopeProjectIDAlone)
Ash Wilson85d82652014-08-28 13:57:46 -0400213 }
214
215 // ProjectID
216 req.Auth.Scope = &scopeReq{
217 Project: &projectReq{ID: &scope.ProjectID},
218 }
219 } else if scope.DomainID != "" {
220 // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
221 if scope.DomainName != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400222 return createErr(ErrScopeDomainIDOrDomainName)
Ash Wilson85d82652014-08-28 13:57:46 -0400223 }
224
225 // DomainID
226 req.Auth.Scope = &scopeReq{
227 Domain: &domainReq{ID: &scope.DomainID},
228 }
229 } else if scope.DomainName != "" {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400230 return createErr(ErrScopeDomainName)
Ash Wilson85d82652014-08-28 13:57:46 -0400231 } else {
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400232 return createErr(ErrScopeEmpty)
Ash Wilson85d82652014-08-28 13:57:46 -0400233 }
234 }
235
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400236 var result CreateResult
Ash Wilson2491b4c2015-02-12 16:13:39 -0500237 var response *http.Response
Jamie Hannaford562a7d52015-03-24 16:20:16 +0100238 response, result.Err = c.Post(tokenURL(c), req, &result.Body, nil)
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400239 if result.Err != nil {
240 return result
Ash Wilson4a52e2a2014-08-29 09:28:00 -0400241 }
Ash Wilson2491b4c2015-02-12 16:13:39 -0500242 result.Header = response.Header
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400243 return result
Ash Wilson85d82652014-08-28 13:57:46 -0400244}
Ash Wilson46d913f2014-08-29 11:00:11 -0400245
Ash Wilson5266e492014-09-09 15:44:30 -0400246// Get validates and retrieves information about another token.
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400247func Get(c *gophercloud.ServiceClient, token string) GetResult {
248 var result GetResult
Ash Wilson2491b4c2015-02-12 16:13:39 -0500249 var response *http.Response
Jamie Hannaford562a7d52015-03-24 16:20:16 +0100250 response, result.Err = c.Get(tokenURL(c), &result.Body, &gophercloud.RequestOpts{
251 MoreHeaders: subjectTokenHeaders(c, token),
252 OkCodes: []int{200, 203},
Ash Wilson46d913f2014-08-29 11:00:11 -0400253 })
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400254 if result.Err != nil {
255 return result
Ash Wilson46d913f2014-08-29 11:00:11 -0400256 }
Ash Wilson2491b4c2015-02-12 16:13:39 -0500257 result.Header = response.Header
Ash Wilsonf8d546a2014-09-30 17:43:25 -0400258 return result
Ash Wilson46d913f2014-08-29 11:00:11 -0400259}
260
261// Validate determines if a specified token is valid or not.
Ash Wilson6425a412014-08-29 12:30:35 -0400262func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
Ash Wilson4bf41a32015-02-12 15:52:44 -0500263 response, err := c.Request("HEAD", tokenURL(c), gophercloud.RequestOpts{
Ash Wilson46d913f2014-08-29 11:00:11 -0400264 MoreHeaders: subjectTokenHeaders(c, token),
265 OkCodes: []int{204, 404},
266 })
267 if err != nil {
268 return false, err
269 }
270
271 return response.StatusCode == 204, nil
272}
273
274// Revoke immediately makes specified token invalid.
Jamie Hannafordf38dd2e2014-10-27 11:36:54 +0100275func Revoke(c *gophercloud.ServiceClient, token string) RevokeResult {
276 var res RevokeResult
Jamie Hannaford562a7d52015-03-24 16:20:16 +0100277 _, res.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{
Ash Wilson46d913f2014-08-29 11:00:11 -0400278 MoreHeaders: subjectTokenHeaders(c, token),
Ash Wilson46d913f2014-08-29 11:00:11 -0400279 })
Jamie Hannafordf38dd2e2014-10-27 11:36:54 +0100280 return res
Ash Wilson46d913f2014-08-29 11:00:11 -0400281}