Merge pull request #451 from jrperritt/token-auth
allow token authentication
diff --git a/acceptance/rackspace/identity/v2/tokens_test.go b/acceptance/rackspace/identity/v2/tokens_test.go
new file mode 100644
index 0000000..95ee7e6
--- /dev/null
+++ b/acceptance/rackspace/identity/v2/tokens_test.go
@@ -0,0 +1,61 @@
+// +build acceptance
+
+package v2
+
+import (
+ "os"
+ "testing"
+
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/acceptance/tools"
+ "github.com/rackspace/gophercloud/rackspace"
+ "github.com/rackspace/gophercloud/rackspace/identity/v2/tokens"
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func rackspaceAuthOptions(t *testing.T) gophercloud.AuthOptions {
+ // Obtain credentials from the environment.
+ options, err := rackspace.AuthOptionsFromEnv()
+ th.AssertNoErr(t, err)
+ options = tools.OnlyRS(options)
+
+ if options.Username == "" {
+ t.Fatal("Please provide a Rackspace username as RS_USERNAME.")
+ }
+ if options.APIKey == "" {
+ t.Fatal("Please provide a Rackspace API key as RS_API_KEY.")
+ }
+
+ return options
+}
+
+func createClient(t *testing.T, auth bool) *gophercloud.ServiceClient {
+ ao := rackspaceAuthOptions(t)
+
+ provider, err := rackspace.NewClient(ao.IdentityEndpoint)
+ th.AssertNoErr(t, err)
+
+ if auth {
+ err = rackspace.Authenticate(provider, ao)
+ th.AssertNoErr(t, err)
+ }
+
+ return rackspace.NewIdentityV2(provider)
+}
+
+func TestTokenAuth(t *testing.T) {
+ authedClient := createClient(t, true)
+ token := authedClient.TokenID
+
+ tenantID := os.Getenv("RS_TENANT_ID")
+ if tenantID == "" {
+ t.Skip("You must set RS_TENANT_ID environment variable to run this test")
+ }
+
+ authOpts := tokens.AuthOptions{}
+ authOpts.TenantID = tenantID
+ authOpts.Token = token
+
+ _, err := tokens.Create(authedClient, authOpts).ExtractToken()
+ th.AssertNoErr(t, err)
+}
diff --git a/auth_options.go b/auth_options.go
index 9819e45..d26e16a 100644
--- a/auth_options.go
+++ b/auth_options.go
@@ -43,4 +43,8 @@
// false, it will not cache these settings, but re-authentication will not be
// possible. This setting defaults to false.
AllowReauth bool
+
+ // TokenID allows users to authenticate (possibly as another user) with an
+ // authentication token ID.
+ TokenID string
}
diff --git a/openstack/identity/v2/tokens/errors.go b/openstack/identity/v2/tokens/errors.go
index 3a9172e..3dfdc08 100644
--- a/openstack/identity/v2/tokens/errors.go
+++ b/openstack/identity/v2/tokens/errors.go
@@ -18,7 +18,7 @@
// ErrDomainNameProvided is returned if you attempt to authenticate with a DomainName.
ErrDomainNameProvided = unacceptedAttributeErr("DomainName")
- // ErrUsernameRequired is returned if you attempt ot authenticate without a Username.
+ // ErrUsernameRequired is returned if you attempt to authenticate without a Username.
ErrUsernameRequired = errors.New("You must supply a Username in your AuthOptions.")
// ErrPasswordRequired is returned if you don't provide a password.
diff --git a/openstack/identity/v2/tokens/requests.go b/openstack/identity/v2/tokens/requests.go
index efa054f..074a89e 100644
--- a/openstack/identity/v2/tokens/requests.go
+++ b/openstack/identity/v2/tokens/requests.go
@@ -1,6 +1,10 @@
package tokens
-import "github.com/rackspace/gophercloud"
+import (
+ "fmt"
+
+ "github.com/rackspace/gophercloud"
+)
// AuthOptionsBuilder describes any argument that may be passed to the Create call.
type AuthOptionsBuilder interface {
@@ -38,20 +42,24 @@
return nil, ErrDomainNameProvided
}
- // Username and Password are always required.
- if auth.Username == "" {
- return nil, ErrUsernameRequired
- }
- if auth.Password == "" {
- return nil, ErrPasswordRequired
- }
-
// Populate the request map.
authMap := make(map[string]interface{})
- authMap["passwordCredentials"] = map[string]interface{}{
- "username": auth.Username,
- "password": auth.Password,
+ if auth.Username != "" {
+ if auth.Password != "" {
+ authMap["passwordCredentials"] = map[string]interface{}{
+ "username": auth.Username,
+ "password": auth.Password,
+ }
+ } else {
+ return nil, ErrPasswordRequired
+ }
+ } else if auth.TokenID != "" {
+ authMap["token"] = map[string]interface{}{
+ "id": auth.TokenID,
+ }
+ } else {
+ return nil, fmt.Errorf("You must provide either username/password or tenantID/token values.")
}
if auth.TenantID != "" {
diff --git a/openstack/identity/v2/tokens/requests_test.go b/openstack/identity/v2/tokens/requests_test.go
index 2f02825..8b78c85 100644
--- a/openstack/identity/v2/tokens/requests_test.go
+++ b/openstack/identity/v2/tokens/requests_test.go
@@ -1,6 +1,7 @@
package tokens
import (
+ "fmt"
"testing"
"github.com/rackspace/gophercloud"
@@ -22,7 +23,7 @@
HandleTokenPost(t, "")
actualErr := Create(client.ServiceClient(), AuthOptions{options}).Err
- th.CheckEquals(t, expectedErr, actualErr)
+ th.CheckDeepEquals(t, expectedErr, actualErr)
}
func TestCreateWithPassword(t *testing.T) {
@@ -128,7 +129,7 @@
Password: "thing",
}
- tokenPostErr(t, options, ErrUsernameRequired)
+ tokenPostErr(t, options, fmt.Errorf("You must provide either username/password or tenantID/token values."))
}
func TestRequirePassword(t *testing.T) {