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