Skip to content

Commit

Permalink
Fix authenticate with rackspace v2 - fixes #21
Browse files Browse the repository at this point in the history
Rackspace are now rejecting requests which contain both the password
and the api key (which is a Rackspace extension), so

  * Factor v2 authRequest into swift and Rackspace variants
  * Try one then retry to try the other if it fails
  * Take a guess as to which to try first

This isn't a particularly satisfactory fix, but it does it without
breaking the published API and without causing any extra network
transactions normally.
  • Loading branch information
ncw committed Mar 10, 2014
1 parent 72043fc commit f7b19fe
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 19 deletions.
67 changes: 49 additions & 18 deletions auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ type Authenticator interface {
// newAuth - create a new Authenticator from the AuthUrl
//
// A hint for AuthVersion can be provided
func newAuth(AuthUrl string, AuthVersion int) (Authenticator, error) {
func newAuth(c *Connection) (Authenticator, error) {
AuthVersion := c.AuthVersion
if AuthVersion == 0 {
if strings.Contains(AuthUrl, "v2") {
if strings.Contains(c.AuthUrl, "v2") {
AuthVersion = 2
} else if strings.Contains(AuthUrl, "v1") {
} else if strings.Contains(c.AuthUrl, "v1") {
AuthVersion = 1
} else {
return nil, newErrorf(500, "Can't find AuthVersion in AuthUrl - set explicitly")
Expand All @@ -40,7 +41,12 @@ func newAuth(AuthUrl string, AuthVersion int) (Authenticator, error) {
case 1:
return &v1Auth{}, nil
case 2:
return &v2Auth{}, nil
return &v2Auth{
// Guess as to whether using API key or
// password it will try both eventually so
// this is just an optimization.
tryApiKey: len(c.ApiKey) >= 32,
}, nil
}
return nil, newErrorf(500, "Auth Version %d not supported", AuthVersion)
}
Expand Down Expand Up @@ -98,22 +104,35 @@ func (auth *v1Auth) CdnUrl() string {

// v2 Authentication
type v2Auth struct {
Auth *v2AuthResponse
Region string
Auth *v2AuthResponse
Region string
tryApiKey bool
}

// v2 Authentication - make request
func (auth *v2Auth) request(c *Connection) (*http.Request, error) {
auth.Region = c.Region
// Create a V2 auth request for the body of the connection
v2 := v2AuthRequest{}
v2.Auth.ApiKeyCredentials.UserName = c.UserName
v2.Auth.ApiKeyCredentials.ApiKey = c.ApiKey
v2.Auth.PasswordCredentials.UserName = c.UserName
v2.Auth.PasswordCredentials.Password = c.ApiKey
v2.Auth.Tenant = c.Tenant
v2.Auth.TenantId = c.TenantId
body, err := json.Marshal(v2)
var v2i interface{}
if !auth.tryApiKey {
// Normal swift authentication
v2 := v2AuthRequest{}
v2.Auth.PasswordCredentials.UserName = c.UserName
v2.Auth.PasswordCredentials.Password = c.ApiKey
v2.Auth.Tenant = c.Tenant
v2.Auth.TenantId = c.TenantId
v2i = v2
} else {
// Rackspace special with API Key
v2 := v2AuthRequestRackspace{}
v2.Auth.ApiKeyCredentials.UserName = c.UserName
v2.Auth.ApiKeyCredentials.ApiKey = c.ApiKey
v2.Auth.Tenant = c.Tenant
v2.Auth.TenantId = c.TenantId
v2i = v2
}
auth.tryApiKey = !auth.tryApiKey
body, err := json.Marshal(v2i)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -184,10 +203,6 @@ func (auth *v2Auth) CdnUrl() string {
// http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_authenticate_v2.0_tokens_.html
type v2AuthRequest struct {
Auth struct {
ApiKeyCredentials struct {
UserName string `json:"username"`
ApiKey string `json:"apiKey"`
} `json:"RAX-KSKEY:apiKeyCredentials"`
PasswordCredentials struct {
UserName string `json:"username"`
Password string `json:"password"`
Expand All @@ -197,6 +212,22 @@ type v2AuthRequest struct {
} `json:"auth"`
}

// V2 Authentication request - Rackspace variant
//
// http://docs.openstack.org/developer/keystone/api_curl_examples.html
// http://docs.rackspace.com/servers/api/v2/cs-gettingstarted/content/curl_auth.html
// http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_authenticate_v2.0_tokens_.html
type v2AuthRequestRackspace struct {
Auth struct {
ApiKeyCredentials struct {
UserName string `json:"username",`
ApiKey string `json:"apiKey"`
} `json:"RAX-KSKEY:apiKeyCredentials"`
Tenant string `json:"tenantName,omitempty"`
TenantId string `json:"tenantId,omitempty"`
} `json:"auth"`
}

// V2 Authentication reply
//
// http://docs.openstack.org/developer/keystone/api_curl_examples.html
Expand Down
10 changes: 9 additions & 1 deletion swift.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,10 +256,12 @@ func (c *Connection) Authenticate() (err error) {
// Flush the keepalives connection - if we are
// re-authenticating then stuff has gone wrong
flushKeepaliveConnections(c.Transport)
c.Auth, err = newAuth(c.AuthUrl, c.AuthVersion)
c.Auth, err = newAuth(c)
if err != nil {
return err
}
retries := 1
again:
req, err := c.Auth.request(c)
if err != nil {
return err
Expand All @@ -276,6 +278,12 @@ func (c *Connection) Authenticate() (err error) {
flushKeepaliveConnections(c.Transport)
}()
if err = c.parseHeaders(resp, authErrorMap); err != nil {
// Try again for a limited number of times on AuthorizationFailed
// This allows us to try some alternate forms of the request
if err == AuthorizationFailed && retries > 0 {
retries--
goto again
}
return
}
err = c.Auth.response(resp)
Expand Down
1 change: 1 addition & 0 deletions swift_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ func TestInternalAuthenticate(t *testing.T) {
}

func TestInternalAuthenticateDenied(t *testing.T) {
server.AddCheck(t).Error(401, "DENIED")
server.AddCheck(t).Error(401, "DENIED")
defer server.Finished()
err := c.Authenticate()
Expand Down

0 comments on commit f7b19fe

Please sign in to comment.