Merge pull request #514 from chaolou/bug-fix-reauth

Bug fix reauth and add extract user from token
diff --git a/acceptance/openstack/identity/v2/token_test.go b/acceptance/openstack/identity/v2/token_test.go
index d903140..e01b3b3 100644
--- a/acceptance/openstack/identity/v2/token_test.go
+++ b/acceptance/openstack/identity/v2/token_test.go
@@ -9,7 +9,8 @@
 	th "github.com/rackspace/gophercloud/testhelper"
 )
 
-func TestAuthenticate(t *testing.T) {
+func TestAuthenticateAndValidate(t *testing.T) {
+	// 1. TestAuthenticate
 	ao := v2AuthOptions(t)
 	service := unauthenticatedClient(t)
 
@@ -35,4 +36,19 @@
 			t.Logf("      - region=[%s] publicURL=[%s]", endpoint.Region, endpoint.PublicURL)
 		}
 	}
+
+	// 2. TestValidate
+	client := authenticatedClient(t)
+
+	// Validate Token!
+	getResult := tokens2.Get(client, token.ID)
+
+	// Extract and print the user.
+	user, err := getResult.ExtractUser()
+	th.AssertNoErr(t, err)
+
+	t.Logf("Acquired User: [%s]", user.Name)
+	t.Logf("The User id: [%s]", user.ID)
+	t.Logf("The User username: [%s]", user.UserName)
+	t.Logf("The User roles: [%#v]", user.Roles)
 }
diff --git a/openstack/client.go b/openstack/client.go
index b30d205..33602a6 100644
--- a/openstack/client.go
+++ b/openstack/client.go
@@ -167,6 +167,7 @@
 
 	if options.AllowReauth {
 		client.ReauthFunc = func() error {
+			client.TokenID = ""
 			return AuthenticateV3(client, options)
 		}
 	}
diff --git a/openstack/identity/v2/tokens/fixtures.go b/openstack/identity/v2/tokens/fixtures.go
index 1cb0d05..6245259 100644
--- a/openstack/identity/v2/tokens/fixtures.go
+++ b/openstack/identity/v2/tokens/fixtures.go
@@ -10,6 +10,7 @@
 
 	"github.com/rackspace/gophercloud/openstack/identity/v2/tenants"
 	th "github.com/rackspace/gophercloud/testhelper"
+	thclient "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 // ExpectedToken is the token that should be parsed from TokenCreationResponse.
@@ -54,6 +55,14 @@
 	},
 }
 
+// ExpectedUser is the token that should be parsed from TokenGetResponse.
+var ExpectedUser = &User{
+	ID:       "a530fefc3d594c4ba2693a4ecd6be74e",
+	Name:     "apiserver",
+	Roles:    []Role{{"member"}, {"service"}},
+	UserName: "apiserver",
+}
+
 // TokenCreationResponse is a JSON response that contains ExpectedToken and ExpectedServiceCatalog.
 const TokenCreationResponse = `
 {
@@ -99,6 +108,39 @@
 }
 `
 
+// TokenGetResponse is a JSON response that contains ExpectedToken and ExpectedUser.
+const TokenGetResponse = `
+{
+    "access": {
+		"token": {
+			"issued_at": "2014-01-30T15:30:58.000000Z",
+			"expires": "2014-01-31T15:30:58Z",
+			"id": "aaaabbbbccccdddd",
+			"tenant": {
+				"description": "There are many tenants. This one is yours.",
+				"enabled": true,
+				"id": "fc394f2ab2df4114bde39905f800dc57",
+				"name": "test"
+			}
+		},
+        "serviceCatalog": [], 
+		"user": {
+            "id": "a530fefc3d594c4ba2693a4ecd6be74e", 
+            "name": "apiserver", 
+            "roles": [
+                {
+                    "name": "member"
+                }, 
+                {
+                    "name": "service"
+                }
+            ], 
+            "roles_links": [], 
+            "username": "apiserver"
+        }
+    }
+}`
+
 // HandleTokenPost expects a POST against a /tokens handler, ensures that the request body has been
 // constructed properly given certain auth options, and returns the result.
 func HandleTokenPost(t *testing.T, requestJSON string) {
@@ -115,6 +157,19 @@
 	})
 }
 
+// HandleTokenGet expects a Get against a /tokens handler, ensures that the request body has been
+// constructed properly given certain auth options, and returns the result.
+func HandleTokenGet(t *testing.T, token string) {
+	th.Mux.HandleFunc("/tokens/"+token, func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "Accept", "application/json")
+		th.TestHeader(t, r, "X-Auth-Token", thclient.TokenID)
+
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, TokenGetResponse)
+	})
+}
+
 // IsSuccessful ensures that a CreateResult was successful and contains the correct token and
 // service catalog.
 func IsSuccessful(t *testing.T, result CreateResult) {
@@ -126,3 +181,15 @@
 	th.AssertNoErr(t, err)
 	th.CheckDeepEquals(t, ExpectedServiceCatalog, serviceCatalog)
 }
+
+// GetIsSuccessful ensures that a GetResult was successful and contains the correct token and
+// User Info.
+func GetIsSuccessful(t *testing.T, result GetResult) {
+	token, err := result.ExtractToken()
+	th.AssertNoErr(t, err)
+	th.CheckDeepEquals(t, ExpectedToken, token)
+
+	user, err := result.ExtractUser()
+	th.AssertNoErr(t, err)
+	th.CheckDeepEquals(t, ExpectedUser, user)
+}
diff --git a/openstack/identity/v2/tokens/requests.go b/openstack/identity/v2/tokens/requests.go
index 074a89e..1f51438 100644
--- a/openstack/identity/v2/tokens/requests.go
+++ b/openstack/identity/v2/tokens/requests.go
@@ -88,3 +88,12 @@
 	})
 	return result
 }
+
+// Validates and retrieves information for user's token.
+func Get(client *gophercloud.ServiceClient, token string) GetResult {
+	var result GetResult
+	_, result.Err = client.Get(GetURL(client, token), &result.Body, &gophercloud.RequestOpts{
+		OkCodes: []int{200, 203},
+	})
+	return result
+}
diff --git a/openstack/identity/v2/tokens/requests_test.go b/openstack/identity/v2/tokens/requests_test.go
index 8b78c85..f1ec339 100644
--- a/openstack/identity/v2/tokens/requests_test.go
+++ b/openstack/identity/v2/tokens/requests_test.go
@@ -139,3 +139,14 @@
 
 	tokenPostErr(t, options, ErrPasswordRequired)
 }
+
+func tokenGet(t *testing.T, tokenId string) GetResult {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleTokenGet(t, tokenId)
+	return Get(client.ServiceClient(), tokenId)
+}
+
+func TestGetWithToken(t *testing.T) {
+	GetIsSuccessful(t, tokenGet(t, "db22caf43c934e6c829087c41ff8d8d6"))
+}
diff --git a/openstack/identity/v2/tokens/results.go b/openstack/identity/v2/tokens/results.go
index 1eddb9d..67c577b 100644
--- a/openstack/identity/v2/tokens/results.go
+++ b/openstack/identity/v2/tokens/results.go
@@ -25,6 +25,17 @@
 	Tenant tenants.Tenant
 }
 
+// Authorization need user info which can get from token authentication's response
+type Role struct {
+	Name string `mapstructure:"name"`
+}
+type User struct {
+	ID       string `mapstructure:"id"`
+	Name     string `mapstructure:"name"`
+	UserName string `mapstructure:"username"`
+	Roles    []Role `mapstructure:"roles"`
+}
+
 // Endpoint represents a single API endpoint offered by a service.
 // It provides the public and internal URLs, if supported, along with a region specifier, again if provided.
 // The significance of the Region field will depend upon your provider.
@@ -74,6 +85,12 @@
 	gophercloud.Result
 }
 
+// GetResult is the deferred response from a Get call, which is the same with a Created token.
+// Use ExtractUser() to interpret it as a User.
+type GetResult struct {
+	CreateResult
+}
+
 // ExtractToken returns the just-created Token from a CreateResult.
 func (result CreateResult) ExtractToken() (*Token, error) {
 	if result.Err != nil {
@@ -131,3 +148,23 @@
 func createErr(err error) CreateResult {
 	return CreateResult{gophercloud.Result{Err: err}}
 }
+
+// ExtractUser returns the User from a GetResult.
+func (result GetResult) ExtractUser() (*User, error) {
+	if result.Err != nil {
+		return nil, result.Err
+	}
+
+	var response struct {
+		Access struct {
+			User User `mapstructure:"user"`
+		} `mapstructure:"access"`
+	}
+
+	err := mapstructure.Decode(result.Body, &response)
+	if err != nil {
+		return nil, err
+	}
+
+	return &response.Access.User, nil
+}
diff --git a/openstack/identity/v2/tokens/urls.go b/openstack/identity/v2/tokens/urls.go
index cd4c696..ee13932 100644
--- a/openstack/identity/v2/tokens/urls.go
+++ b/openstack/identity/v2/tokens/urls.go
@@ -6,3 +6,8 @@
 func CreateURL(client *gophercloud.ServiceClient) string {
 	return client.ServiceURL("tokens")
 }
+
+// GetURL generates the URL used to Validate Tokens.
+func GetURL(client *gophercloud.ServiceClient, token string) string {
+	return client.ServiceURL("tokens", token)
+}