Merge pull request #105 from rafbgarcia/api_key

Rackspace API key support
diff --git a/acceptance/00-authentication.go b/acceptance/00-authentication.go
new file mode 100644
index 0000000..2374efb
--- /dev/null
+++ b/acceptance/00-authentication.go
@@ -0,0 +1,28 @@
+package main
+
+import (
+	"fmt"
+	"github.com/rackspace/gophercloud"
+	"os"
+	"strings"
+)
+
+func main() {
+	provider, username, _, apiKey := getCredentials()
+
+	if !strings.Contains(provider, "rackspace") {
+		fmt.Fprintf(os.Stdout, "Skipping test because provider doesn't support API_KEYs\n")
+		return
+	}
+
+	_, err := gophercloud.Authenticate(
+		provider,
+		gophercloud.AuthOptions{
+			Username: username,
+			ApiKey:   apiKey,
+		},
+	)
+	if err != nil {
+		panic(err)
+	}
+}
diff --git a/acceptance/01-authentication.go b/acceptance/01-authentication.go
index 91fc814..bcd3545 100644
--- a/acceptance/01-authentication.go
+++ b/acceptance/01-authentication.go
@@ -5,7 +5,7 @@
 )
 
 func main() {
-	provider, username, password := getCredentials()
+	provider, username, password, _ := getCredentials()
 
 	_, err := gophercloud.Authenticate(
 		provider,
diff --git a/acceptance/libargs.go b/acceptance/libargs.go
index 23c55f1..78fde06 100644
--- a/acceptance/libargs.go
+++ b/acceptance/libargs.go
@@ -11,10 +11,11 @@
 // getCredentials will verify existence of needed credential information
 // provided through environment variables.  This function will not return
 // if at least one piece of required information is missing.
-func getCredentials() (provider, username, password string) {
+func getCredentials() (provider, username, password, apiKey string) {
 	provider = os.Getenv("SDK_PROVIDER")
 	username = os.Getenv("SDK_USERNAME")
 	password = os.Getenv("SDK_PASSWORD")
+	apiKey = os.Getenv("SDK_API_KEY")
 
 	if (provider == "") || (username == "") || (password == "") {
 		fmt.Fprintf(os.Stderr, "One or more of the following environment variables aren't set:\n")
@@ -139,7 +140,7 @@
 // withIdentity authenticates the user against the provider's identity service, and provides an
 // accessor for additional services.
 func withIdentity(ar bool, f func(gophercloud.AccessProvider)) {
-	provider, username, password := getCredentials()
+	provider, username, password, _ := getCredentials()
 	acc, err := gophercloud.Authenticate(
 		provider,
 		gophercloud.AuthOptions{
diff --git a/authenticate.go b/authenticate.go
index 076c731..0f7c633 100644
--- a/authenticate.go
+++ b/authenticate.go
@@ -14,6 +14,9 @@
 	// account's username and password.
 	Username, Password string
 
+	// ApiKey used for providers that support Api Key authentication
+	ApiKey string
+
 	// The TenantId field is optional for the Identity V2 API.
 	TenantId string
 
@@ -37,9 +40,10 @@
 // Auth provides a JSON encoding wrapper for passing credentials to the Identity
 // service.  You will not work with this structure directly.
 type Auth struct {
-	PasswordCredentials PasswordCredentials `json:"passwordCredentials"`
-	TenantId            string              `json:"tenantId,omitempty"`
-	TenantName          string              `json:"tenantName,omitempty"`
+	PasswordCredentials *PasswordCredentials `json:"passwordCredentials,omitempty"`
+	ApiKeyCredentials   *ApiKeyCredentials   `json:"RAX-KSKEY:apiKeyCredentials,omitempty"`
+	TenantId            string               `json:"tenantId,omitempty"`
+	TenantName          string               `json:"tenantName,omitempty"`
 }
 
 // PasswordCredentials provides a JSON encoding wrapper for passing credentials to the Identity
@@ -49,6 +53,11 @@
 	Password string `json:"password"`
 }
 
+type ApiKeyCredentials struct {
+	Username string `json:"username"`
+	ApiKey   string `json:"apiKey"`
+}
+
 // Access encapsulates the API token and its relevant fields, as well as the
 // services catalog that Identity API returns once authenticated.
 type Access struct {
@@ -99,6 +108,29 @@
 	VersionId, VersionInfo, VersionList string
 }
 
+//
+func getAuthCredentials(options AuthOptions) Auth {
+	if options.ApiKey == "" {
+		return Auth{
+			PasswordCredentials: &PasswordCredentials{
+				Username: options.Username,
+				Password: options.Password,
+			},
+			TenantId:   options.TenantId,
+			TenantName: options.TenantName,
+		}
+	} else {
+		return Auth{
+			ApiKeyCredentials: &ApiKeyCredentials{
+				Username: options.Username,
+				ApiKey:   options.ApiKey,
+			},
+			TenantId:   options.TenantId,
+			TenantName: options.TenantName,
+		}
+	}
+}
+
 // papersPlease contains the common logic between authentication and re-authentication.
 // The name, obviously a joke on the process of authentication, was chosen because
 // of how many other entities exist in the program containing the word Auth or Authorization.
@@ -106,21 +138,14 @@
 func (c *Context) papersPlease(p Provider, options AuthOptions) (*Access, error) {
 	var access *Access
 
-	if (options.Username == "") || (options.Password == "") {
+	if (options.Username == "") || (options.Password == "" && options.ApiKey == "") {
 		return nil, ErrCredentials
 	}
 
 	err := perigee.Post(p.AuthEndpoint, perigee.Options{
 		CustomClient: c.httpClient,
 		ReqBody: &AuthContainer{
-			Auth: Auth{
-				PasswordCredentials: PasswordCredentials{
-					Username: options.Username,
-					Password: options.Password,
-				},
-				TenantId:   options.TenantId,
-				TenantName: options.TenantName,
-			},
+			Auth: getAuthCredentials(options),
 		},
 		Results: &struct {
 			Access **Access `json:"access"`
diff --git a/authenticate_test.go b/authenticate_test.go
index 32329ff..b05c780 100644
--- a/authenticate_test.go
+++ b/authenticate_test.go
@@ -148,6 +148,31 @@
 	}
 }
 
+func TestUserNameAndApiKey(t *testing.T) {
+	c := TestContext().
+		WithProvider("provider", Provider{AuthEndpoint: "http://localhost/"}).
+		UseCustomClient(&http.Client{Transport: newTransport().WithResponse(SUCCESSFUL_RESPONSE)})
+
+	credentials := []AuthOptions{
+		{},
+		{Username: "u"},
+		{ApiKey: "a"},
+	}
+	for i, auth := range credentials {
+		_, err := c.Authenticate("provider", auth)
+		if err == nil {
+			t.Error("Expected error from missing credentials (%d)", i)
+			return
+		}
+	}
+
+	_, err := c.Authenticate("provider", AuthOptions{Username: "u", ApiKey: "a"})
+	if err != nil {
+		t.Error(err)
+		return
+	}
+}
+
 func TestTokenAcquisition(t *testing.T) {
 	c := TestContext().
 		UseCustomClient(&http.Client{Transport: newTransport().WithResponse(SUCCESSFUL_RESPONSE)}).