+maxlinc - Add ApiKey authentication
diff --git a/acceptance/00-authentication.go b/acceptance/00-authentication.go
new file mode 100644
index 0000000..10ed022
--- /dev/null
+++ b/acceptance/00-authentication.go
@@ -0,0 +1,28 @@
+package main
+
+import (
+ "github.com/rackspace/gophercloud"
+ "strings"
+ "fmt"
+ "os"
+)
+
+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..bc34d86 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 886a6fa..c17363c 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,7 +40,8 @@
// 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"`
+ PasswordCredentials *PasswordCredentials `json:"passwordCredentials,omitempty"`
+ ApiKeyCredentials *ApiKeyCredentials `json:"RAX-KSKEY:apiKeyCredentials,omitempty"`
TenantId string `json:"tenantId,omitempty"`
TenantName string `json:"tenantName,omitempty"`
}
@@ -49,6 +53,12 @@
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 +109,30 @@
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 +140,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)}).