And all of the rest of the token operations.
Plus tests for each.
diff --git a/openstack/identity/v3/tokens/doc.go b/openstack/identity/v3/tokens/doc.go
new file mode 100644
index 0000000..02fce0d
--- /dev/null
+++ b/openstack/identity/v3/tokens/doc.go
@@ -0,0 +1,6 @@
+/*
+Package tokens defines operations performed on the token resource.
+
+Documentation: http://developer.openstack.org/api-ref-identity-v3.html#tokens-v3
+*/
+package tokens
diff --git a/openstack/identity/v3/tokens/requests.go b/openstack/identity/v3/tokens/requests.go
index b8f0d76..ab84d81 100644
--- a/openstack/identity/v3/tokens/requests.go
+++ b/openstack/identity/v3/tokens/requests.go
@@ -13,6 +13,12 @@
DomainName string
}
+func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
+ h := c.AuthenticatedHeaders()
+ h["X-Subject-Token"] = subjectToken
+ return h
+}
+
// Create authenticates and either generates a new token, or changes the Scope of an existing token.
func Create(c *gophercloud.ServiceClient, scope *Scope) (gophercloud.AuthResults, error) {
type domainReq struct {
@@ -243,3 +249,45 @@
return &result, nil
}
+
+// Info validates and retrieves information about another token.
+func Info(c *gophercloud.ServiceClient, token string) (*TokenCreateResult, error) {
+ var result TokenCreateResult
+
+ response, err := perigee.Request("GET", getTokenURL(c), perigee.Options{
+ MoreHeaders: subjectTokenHeaders(c, token),
+ Results: &result.response,
+ OkCodes: []int{200, 203},
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ // Extract the token ID from the response, if present.
+ result.tokenID = response.HttpResponse.Header.Get("X-Subject-Token")
+
+ return &result, nil
+}
+
+// Validate determines if a specified token is valid or not.
+func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
+ response, err := perigee.Request("HEAD", getTokenURL(c), perigee.Options{
+ MoreHeaders: subjectTokenHeaders(c, token),
+ OkCodes: []int{204, 404},
+ })
+ if err != nil {
+ return false, err
+ }
+
+ return response.StatusCode == 204, nil
+}
+
+// Revoke immediately makes specified token invalid.
+func Revoke(c *gophercloud.ServiceClient, token string) error {
+ _, err := perigee.Request("DELETE", getTokenURL(c), perigee.Options{
+ MoreHeaders: subjectTokenHeaders(c, token),
+ OkCodes: []int{204},
+ })
+ return err
+}
diff --git a/openstack/identity/v3/tokens/requests_test.go b/openstack/identity/v3/tokens/requests_test.go
index 6038f96..db0fba7 100644
--- a/openstack/identity/v3/tokens/requests_test.go
+++ b/openstack/identity/v3/tokens/requests_test.go
@@ -4,6 +4,7 @@
"fmt"
"net/http"
"testing"
+ "time"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/testhelper"
@@ -385,3 +386,123 @@
scope := &Scope{}
authTokenPostErr(t, options, scope, false, ErrScopeEmpty)
}
+
+func TestInfoRequest(t *testing.T) {
+ setup()
+ defer teardown()
+
+ client := gophercloud.ServiceClient{
+ Endpoint: endpoint(),
+ TokenID: "12345abcdef",
+ }
+
+ mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) {
+ testhelper.TestMethod(t, r, "GET")
+ testhelper.TestHeader(t, r, "Content-Type", "application/json")
+ testhelper.TestHeader(t, r, "Accept", "application/json")
+ testhelper.TestHeader(t, r, "X-Auth-Token", "12345abcdef")
+ testhelper.TestHeader(t, r, "X-Subject-Token", "abcdef12345")
+
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, `
+ { "token": { "expires_at": "2014-08-29T13:10:01.000000Z" } }
+ `)
+ })
+
+ result, err := Info(&client, "abcdef12345")
+ if err != nil {
+ t.Errorf("Info returned an error: %v", err)
+ }
+
+ expires, err := result.ExpiresAt()
+ if err != nil {
+ t.Errorf("Error extracting token expiration time: %v", err)
+ }
+
+ expected, _ := time.Parse(time.UnixDate, "Fri Aug 29 13:10:01 UTC 2014")
+ if expires != expected {
+ t.Errorf("Expected expiration time %s, but was %s", expected.Format(time.UnixDate), expires.Format(time.UnixDate))
+ }
+}
+
+func prepareAuthTokenHandler(t *testing.T, expectedMethod string, status int) gophercloud.ServiceClient {
+ client := gophercloud.ServiceClient{
+ Endpoint: endpoint(),
+ TokenID: "12345abcdef",
+ }
+
+ mux.HandleFunc("/auth/tokens", func(w http.ResponseWriter, r *http.Request) {
+ testhelper.TestMethod(t, r, expectedMethod)
+ testhelper.TestHeader(t, r, "Content-Type", "application/json")
+ testhelper.TestHeader(t, r, "Accept", "application/json")
+ testhelper.TestHeader(t, r, "X-Auth-Token", "12345abcdef")
+ testhelper.TestHeader(t, r, "X-Subject-Token", "abcdef12345")
+
+ w.WriteHeader(status)
+ })
+
+ return client
+}
+
+func TestValidateRequestSuccessful(t *testing.T) {
+ setup()
+ defer teardown()
+ client := prepareAuthTokenHandler(t, "HEAD", http.StatusNoContent)
+
+ ok, err := Validate(&client, "abcdef12345")
+ if err != nil {
+ t.Errorf("Unexpected error from Validate: %v", err)
+ }
+
+ if !ok {
+ t.Errorf("Validate returned false for a valid token")
+ }
+}
+
+func TestValidateRequestFailure(t *testing.T) {
+ setup()
+ defer teardown()
+ client := prepareAuthTokenHandler(t, "HEAD", http.StatusNotFound)
+
+ ok, err := Validate(&client, "abcdef12345")
+ if err != nil {
+ t.Errorf("Unexpected error from Validate: %v", err)
+ }
+
+ if ok {
+ t.Errorf("Validate returned true for an invalid token")
+ }
+}
+
+func TestValidateRequestError(t *testing.T) {
+ setup()
+ defer teardown()
+ client := prepareAuthTokenHandler(t, "HEAD", http.StatusUnauthorized)
+
+ _, err := Validate(&client, "abcdef12345")
+ if err == nil {
+ t.Errorf("Missing expected error from Validate")
+ }
+}
+
+func TestRevokeRequestSuccessful(t *testing.T) {
+ setup()
+ defer teardown()
+ client := prepareAuthTokenHandler(t, "DELETE", http.StatusNoContent)
+
+ err := Revoke(&client, "abcdef12345")
+ if err != nil {
+ t.Errorf("Unexpected error from Revoke: %v", err)
+ }
+}
+
+func TestRevokeRequestError(t *testing.T) {
+ setup()
+ defer teardown()
+ client := prepareAuthTokenHandler(t, "DELETE", http.StatusNotFound)
+
+ err := Revoke(&client, "abcdef12345")
+ if err == nil {
+ t.Errorf("Missing expected error from Revoke")
+ }
+}
diff --git a/openstack/identity/v3/tokens/results.go b/openstack/identity/v3/tokens/results.go
index 9f0d623..8e0f018 100644
--- a/openstack/identity/v3/tokens/results.go
+++ b/openstack/identity/v3/tokens/results.go
@@ -1,5 +1,14 @@
package tokens
+import (
+ "time"
+
+ "github.com/mitchellh/mapstructure"
+)
+
+// RFC3339Milli describes the time format used by identity API responses.
+const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
+
// TokenCreateResult contains the document structure returned from a Create call.
type TokenCreateResult struct {
response map[string]interface{}