1package swift 2 3import ( 4 "bytes" 5 "encoding/json" 6 "net/http" 7 "net/url" 8 "strings" 9 "time" 10) 11 12// Auth defines the operations needed to authenticate with swift 13// 14// This encapsulates the different authentication schemes in use 15type Authenticator interface { 16 // Request creates an http.Request for the auth - return nil if not needed 17 Request(*Connection) (*http.Request, error) 18 // Response parses the http.Response 19 Response(resp *http.Response) error 20 // The public storage URL - set Internal to true to read 21 // internal/service net URL 22 StorageUrl(Internal bool) string 23 // The access token 24 Token() string 25 // The CDN url if available 26 CdnUrl() string 27} 28 29// Expireser is an optional interface to read the expiration time of the token 30type Expireser interface { 31 Expires() time.Time 32} 33 34type CustomEndpointAuthenticator interface { 35 StorageUrlForEndpoint(endpointType EndpointType) string 36} 37 38type EndpointType string 39 40const ( 41 // Use public URL as storage URL 42 EndpointTypePublic = EndpointType("public") 43 44 // Use internal URL as storage URL 45 EndpointTypeInternal = EndpointType("internal") 46 47 // Use admin URL as storage URL 48 EndpointTypeAdmin = EndpointType("admin") 49) 50 51// newAuth - create a new Authenticator from the AuthUrl 52// 53// A hint for AuthVersion can be provided 54func newAuth(c *Connection) (Authenticator, error) { 55 AuthVersion := c.AuthVersion 56 if AuthVersion == 0 { 57 if strings.Contains(c.AuthUrl, "v3") { 58 AuthVersion = 3 59 } else if strings.Contains(c.AuthUrl, "v2") { 60 AuthVersion = 2 61 } else if strings.Contains(c.AuthUrl, "v1") { 62 AuthVersion = 1 63 } else { 64 return nil, newErrorf(500, "Can't find AuthVersion in AuthUrl - set explicitly") 65 } 66 } 67 switch AuthVersion { 68 case 1: 69 return &v1Auth{}, nil 70 case 2: 71 return &v2Auth{ 72 // Guess as to whether using API key or 73 // password it will try both eventually so 74 // this is just an optimization. 75 useApiKey: len(c.ApiKey) >= 32, 76 }, nil 77 case 3: 78 return &v3Auth{}, nil 79 } 80 return nil, newErrorf(500, "Auth Version %d not supported", AuthVersion) 81} 82 83// ------------------------------------------------------------ 84 85// v1 auth 86type v1Auth struct { 87 Headers http.Header // V1 auth: the authentication headers so extensions can access them 88} 89 90// v1 Authentication - make request 91func (auth *v1Auth) Request(c *Connection) (*http.Request, error) { 92 req, err := http.NewRequest("GET", c.AuthUrl, nil) 93 if err != nil { 94 return nil, err 95 } 96 req.Header.Set("User-Agent", c.UserAgent) 97 req.Header.Set("X-Auth-Key", c.ApiKey) 98 req.Header.Set("X-Auth-User", c.UserName) 99 return req, nil 100} 101 102// v1 Authentication - read response 103func (auth *v1Auth) Response(resp *http.Response) error { 104 auth.Headers = resp.Header 105 return nil 106} 107 108// v1 Authentication - read storage url 109func (auth *v1Auth) StorageUrl(Internal bool) string { 110 storageUrl := auth.Headers.Get("X-Storage-Url") 111 if Internal { 112 newUrl, err := url.Parse(storageUrl) 113 if err != nil { 114 return storageUrl 115 } 116 newUrl.Host = "snet-" + newUrl.Host 117 storageUrl = newUrl.String() 118 } 119 return storageUrl 120} 121 122// v1 Authentication - read auth token 123func (auth *v1Auth) Token() string { 124 return auth.Headers.Get("X-Auth-Token") 125} 126 127// v1 Authentication - read cdn url 128func (auth *v1Auth) CdnUrl() string { 129 return auth.Headers.Get("X-CDN-Management-Url") 130} 131 132// ------------------------------------------------------------ 133 134// v2 Authentication 135type v2Auth struct { 136 Auth *v2AuthResponse 137 Region string 138 useApiKey bool // if set will use API key not Password 139 useApiKeyOk bool // if set won't change useApiKey any more 140 notFirst bool // set after first run 141} 142 143// v2 Authentication - make request 144func (auth *v2Auth) Request(c *Connection) (*http.Request, error) { 145 auth.Region = c.Region 146 // Toggle useApiKey if not first run and not OK yet 147 if auth.notFirst && !auth.useApiKeyOk { 148 auth.useApiKey = !auth.useApiKey 149 } 150 auth.notFirst = true 151 // Create a V2 auth request for the body of the connection 152 var v2i interface{} 153 if !auth.useApiKey { 154 // Normal swift authentication 155 v2 := v2AuthRequest{} 156 v2.Auth.PasswordCredentials.UserName = c.UserName 157 v2.Auth.PasswordCredentials.Password = c.ApiKey 158 v2.Auth.Tenant = c.Tenant 159 v2.Auth.TenantId = c.TenantId 160 v2i = v2 161 } else { 162 // Rackspace special with API Key 163 v2 := v2AuthRequestRackspace{} 164 v2.Auth.ApiKeyCredentials.UserName = c.UserName 165 v2.Auth.ApiKeyCredentials.ApiKey = c.ApiKey 166 v2.Auth.Tenant = c.Tenant 167 v2.Auth.TenantId = c.TenantId 168 v2i = v2 169 } 170 body, err := json.Marshal(v2i) 171 if err != nil { 172 return nil, err 173 } 174 url := c.AuthUrl 175 if !strings.HasSuffix(url, "/") { 176 url += "/" 177 } 178 url += "tokens" 179 req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) 180 if err != nil { 181 return nil, err 182 } 183 req.Header.Set("Content-Type", "application/json") 184 req.Header.Set("User-Agent", c.UserAgent) 185 return req, nil 186} 187 188// v2 Authentication - read response 189func (auth *v2Auth) Response(resp *http.Response) error { 190 auth.Auth = new(v2AuthResponse) 191 err := readJson(resp, auth.Auth) 192 // If successfully read Auth then no need to toggle useApiKey any more 193 if err == nil { 194 auth.useApiKeyOk = true 195 } 196 return err 197} 198 199// Finds the Endpoint Url of "type" from the v2AuthResponse using the 200// Region if set or defaulting to the first one if not 201// 202// Returns "" if not found 203func (auth *v2Auth) endpointUrl(Type string, endpointType EndpointType) string { 204 for _, catalog := range auth.Auth.Access.ServiceCatalog { 205 if catalog.Type == Type { 206 for _, endpoint := range catalog.Endpoints { 207 if auth.Region == "" || (auth.Region == endpoint.Region) { 208 switch endpointType { 209 case EndpointTypeInternal: 210 return endpoint.InternalUrl 211 case EndpointTypePublic: 212 return endpoint.PublicUrl 213 case EndpointTypeAdmin: 214 return endpoint.AdminUrl 215 default: 216 return "" 217 } 218 } 219 } 220 } 221 } 222 return "" 223} 224 225// v2 Authentication - read storage url 226// 227// If Internal is true then it reads the private (internal / service 228// net) URL. 229func (auth *v2Auth) StorageUrl(Internal bool) string { 230 endpointType := EndpointTypePublic 231 if Internal { 232 endpointType = EndpointTypeInternal 233 } 234 return auth.StorageUrlForEndpoint(endpointType) 235} 236 237// v2 Authentication - read storage url 238// 239// Use the indicated endpointType to choose a URL. 240func (auth *v2Auth) StorageUrlForEndpoint(endpointType EndpointType) string { 241 return auth.endpointUrl("object-store", endpointType) 242} 243 244// v2 Authentication - read auth token 245func (auth *v2Auth) Token() string { 246 return auth.Auth.Access.Token.Id 247} 248 249// v2 Authentication - read expires 250func (auth *v2Auth) Expires() time.Time { 251 t, err := time.Parse(time.RFC3339, auth.Auth.Access.Token.Expires) 252 if err != nil { 253 return time.Time{} // return Zero if not parsed 254 } 255 return t 256} 257 258// v2 Authentication - read cdn url 259func (auth *v2Auth) CdnUrl() string { 260 return auth.endpointUrl("rax:object-cdn", EndpointTypePublic) 261} 262 263// ------------------------------------------------------------ 264 265// V2 Authentication request 266// 267// http://docs.openstack.org/developer/keystone/api_curl_examples.html 268// http://docs.rackspace.com/servers/api/v2/cs-gettingstarted/content/curl_auth.html 269// http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_authenticate_v2.0_tokens_.html 270type v2AuthRequest struct { 271 Auth struct { 272 PasswordCredentials struct { 273 UserName string `json:"username"` 274 Password string `json:"password"` 275 } `json:"passwordCredentials"` 276 Tenant string `json:"tenantName,omitempty"` 277 TenantId string `json:"tenantId,omitempty"` 278 } `json:"auth"` 279} 280 281// V2 Authentication request - Rackspace variant 282// 283// http://docs.openstack.org/developer/keystone/api_curl_examples.html 284// http://docs.rackspace.com/servers/api/v2/cs-gettingstarted/content/curl_auth.html 285// http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_authenticate_v2.0_tokens_.html 286type v2AuthRequestRackspace struct { 287 Auth struct { 288 ApiKeyCredentials struct { 289 UserName string `json:"username"` 290 ApiKey string `json:"apiKey"` 291 } `json:"RAX-KSKEY:apiKeyCredentials"` 292 Tenant string `json:"tenantName,omitempty"` 293 TenantId string `json:"tenantId,omitempty"` 294 } `json:"auth"` 295} 296 297// V2 Authentication reply 298// 299// http://docs.openstack.org/developer/keystone/api_curl_examples.html 300// http://docs.rackspace.com/servers/api/v2/cs-gettingstarted/content/curl_auth.html 301// http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_authenticate_v2.0_tokens_.html 302type v2AuthResponse struct { 303 Access struct { 304 ServiceCatalog []struct { 305 Endpoints []struct { 306 InternalUrl string 307 PublicUrl string 308 AdminUrl string 309 Region string 310 TenantId string 311 } 312 Name string 313 Type string 314 } 315 Token struct { 316 Expires string 317 Id string 318 Tenant struct { 319 Id string 320 Name string 321 } 322 } 323 User struct { 324 DefaultRegion string `json:"RAX-AUTH:defaultRegion"` 325 Id string 326 Name string 327 Roles []struct { 328 Description string 329 Id string 330 Name string 331 TenantId string 332 } 333 } 334 } 335} 336