Ensure authenticate never re-auths.
Other API functions will support re-auth as a matter of course.
If an auth token expires, we need to re-authenticate to acquire a new
token. If re-authentication were itself to attempt re-auth, we
would end up in an endless loop.
If after authenticating gophercloud receives a 401 Unauthorized
response, then we must assume that the provided credentials are
incorrect.
diff --git a/authenticate_test.go b/authenticate_test.go
index a19fec4..e08a0a6 100644
--- a/authenticate_test.go
+++ b/authenticate_test.go
@@ -212,3 +212,28 @@
return
}
}
+
+func TestAuthenticationNeverReauths(t *testing.T) {
+ tt := newTransport().WithError(401)
+ c := TestContext().
+ UseCustomClient(&http.Client{Transport: tt}).
+ WithProvider("provider", Provider{AuthEndpoint: "http://localhost"})
+
+ _, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
+ if err == nil {
+ t.Error("Expected an error from a 401 Unauthorized response")
+ return
+ }
+
+ rc, _ := ActualResponseCode(err)
+ if rc != 401 {
+ t.Error("Expected a 401 error code")
+ return
+ }
+
+ err = tt.VerifyCalls(t, 1)
+ if err != nil {
+ // Test object already flagged.
+ return
+ }
+}
diff --git a/errors.go b/errors.go
index f113446..5ea3991 100644
--- a/errors.go
+++ b/errors.go
@@ -30,3 +30,8 @@
// exists in the service catalog. This can also happen if your tenant lacks
// adequate permissions to access a given endpoint.
var ErrEndpoint = fmt.Errorf("Missing endpoint, or insufficient privileges to access endpoint")
+
+// ErrError errors happen when you attempt to discover the response code
+// responsible for a previous request bombing with an error, but pass in an
+// error interface which doesn't belong to the web client.
+var ErrError = fmt.Errorf("Attempt to solicit actual HTTP response code from error entity which doesn't know")
diff --git a/global_context.go b/global_context.go
index c89ac17..9977aa8 100644
--- a/global_context.go
+++ b/global_context.go
@@ -1,5 +1,9 @@
package gophercloud
+import (
+ "github.com/racker/perigee"
+)
+
// globalContext is the, well, "global context."
// Most of this SDK is written in a manner to facilitate easier testing,
// which doesn't require all the configuration a real-world application would require.
@@ -46,3 +50,13 @@
func ServersApi(acc AccessProvider, criteria ApiCriteria) (CloudServersProvider, error) {
return globalContext.ServersApi(acc, criteria)
}
+
+// ActualResponseCode inspects a returned error, and discovers the actual response actual
+// response code that caused the error to be raised.
+func ActualResponseCode(e error) (int, error) {
+ err, ok := e.(*perigee.UnexpectedResponseCodeError)
+ if !ok {
+ return 0, ErrError
+ }
+ return err.Actual, nil
+}
\ No newline at end of file
diff --git a/transport_double.go b/transport_double.go
index b65d5db..e764c9a 100644
--- a/transport_double.go
+++ b/transport_double.go
@@ -5,6 +5,8 @@
"io/ioutil"
"net/http"
"strings"
+ "fmt"
+ "testing"
)
type transport struct {
@@ -12,6 +14,7 @@
response string
expectTenantId bool
tenantIdFound bool
+ status int
}
func (t *transport) RoundTrip(req *http.Request) (rsp *http.Response, err error) {
@@ -24,9 +27,17 @@
body := ioutil.NopCloser(strings.NewReader(t.response))
+ if t.status == 0 {
+ t.status = 200
+ }
+ statusMsg := "OK"
+ if (t.status < 200) || (299 < t.status) {
+ statusMsg = "Error"
+ }
+
rsp = &http.Response{
- Status: "200 OK",
- StatusCode: 200,
+ Status: fmt.Sprintf("%d %s", t.status, statusMsg),
+ StatusCode: t.status,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
@@ -72,5 +83,21 @@
func (t *transport) WithResponse(r string) *transport {
t.response = r
+ t.status = 200
return t
}
+
+func (t *transport) WithError(code int) *transport {
+ t.response = fmt.Sprintf("Error %d", code)
+ t.status = code
+ return t
+}
+
+func (t *transport) VerifyCalls(test *testing.T, n int) error {
+ if t.called != n {
+ err := fmt.Errorf("Expected Transport to be called %d times; found %d instead", n, t.called)
+ test.Error(err)
+ return err
+ }
+ return nil
+}
\ No newline at end of file