auth

Paddy 2015-01-18 Parent:fa8ee6a4507c Child:da77e083cf02

116:e000b1c24fc0 Browse Files

Make all tests that deal with the store interfaces go through the Context. This is mainly important so that pre- and post- save/retrieval/deletion/whatever transforms can be done without doing them in every single implementation of the store. Change the Endpoint URI property to be a string, not a *url.URL. This makes testing easier, JSON responses cleaner, and is all around just a better strategy. Just because we turn it into a URL every now and then doesn't mean that's how we need to store it. Add JSON tags to the Client type and Endpoint type. Create normalizeURI and normalizeURIString methods to... well, normalize the Endpoint URIs. This makes it so that we can compare them, and forgive some arbitrary user behaviour (like slashes, etc.) Add a NormalizedURI property to the Endpoint type. This is where we store the NormalizedURI, which is what we'll be using when we want to check if an endpoint is valid or not. For the sake of tests and predictability, however, we always want to redirect to the URI, not the NormalizedURI. Add checks to the Client creation API endpoint to give better errors. Now leaving out the Type won't be considered an invalid type, it will be considered a missing parameter. An empty name will be reported as a missing parameter, a name with too few characters will be reported as an insufficient name, and a name with too many characters will be reported as an overflow name. We gather as many of these errors as apply before returning. Check if an Endpoint URI is absolute before adding it as an endpoint, or return an invalid value error if it is not. Always return the errors array when creating a client. We could succeed in creating one or more things and still have errors. We should return anything that's created _as well as_ any errors encountered. Add unit testing for our CreateClientHandler. Fix our oauth2 tests so that if there's an error in the body, it's in the test logs. This should help debugging significantly. Fix our oauth2 tests so that the Profile only requires 1 iteration for its password hashing. This means each time we want to validate a session, it doesn't add a full second to our test runs. This is a big speed improvement for our tests. Add test helper methods for comparing API errors, API responses, and filling in server-generated information in a response that it's impossible to have an expectation around (e.g., IDs) so that we can use our comparison helpers to check if a response is as we expect it. Fix a typo in our Context helpers that was reporting no sessionStore being set _only_ when a sessionStore was set. So yes, the opposite of what we wanted. Oops. This was discovered by passing all our tests through the context. methods instead of operating on the stores themselves.

authcode_test.go client.go client_test.go context.go oauth2.go oauth2_test.go profile_test.go request_test.go session_test.go token_test.go

     1.1 --- a/authcode_test.go	Wed Jan 14 00:23:30 2015 -0500
     1.2 +++ b/authcode_test.go	Sun Jan 18 01:02:14 2015 -0500
     1.3 @@ -58,15 +58,16 @@
     1.4  		State:       "state",
     1.5  	}
     1.6  	for _, store := range authCodeStores {
     1.7 -		err := store.saveAuthorizationCode(authCode)
     1.8 +		context := Context{authCodes: store}
     1.9 +		err := context.SaveAuthorizationCode(authCode)
    1.10  		if err != nil {
    1.11  			t.Errorf("Error saving auth code to %T: %s", store, err)
    1.12  		}
    1.13 -		err = store.saveAuthorizationCode(authCode)
    1.14 +		err = context.SaveAuthorizationCode(authCode)
    1.15  		if err != ErrAuthorizationCodeAlreadyExists {
    1.16  			t.Errorf("Expected ErrAuthorizationCodeAlreadyExists from %T, got %+v", store, err)
    1.17  		}
    1.18 -		retrieved, err := store.getAuthorizationCode(authCode.Code)
    1.19 +		retrieved, err := context.GetAuthorizationCode(authCode.Code)
    1.20  		if err != nil {
    1.21  			t.Errorf("Error retrieving auth code from %T: %s", store, err)
    1.22  		}
    1.23 @@ -74,11 +75,11 @@
    1.24  		if !match {
    1.25  			t.Errorf("Expected `%v` in the `%s` field of auth code retrieved from %T, got `%v`", expectation, field, store, result)
    1.26  		}
    1.27 -		err = store.useAuthorizationCode(authCode.Code)
    1.28 +		err = context.UseAuthorizationCode(authCode.Code)
    1.29  		if err != nil {
    1.30  			t.Errorf("Error retrieving auth code from %T: %s", store, err)
    1.31  		}
    1.32 -		retrieved, err = store.getAuthorizationCode(authCode.Code)
    1.33 +		retrieved, err = context.GetAuthorizationCode(authCode.Code)
    1.34  		if err != nil {
    1.35  			t.Errorf("Error retrieving auth code from %T: %s", store, err)
    1.36  		}
    1.37 @@ -87,19 +88,19 @@
    1.38  		if !match {
    1.39  			t.Errorf("Expected `%v` in the `%s` field of auth code retrieved from %T, got `%v`", expectation, field, store, result)
    1.40  		}
    1.41 -		err = store.deleteAuthorizationCode(authCode.Code)
    1.42 +		err = context.DeleteAuthorizationCode(authCode.Code)
    1.43  		if err != nil {
    1.44  			t.Errorf("Error removing auth code from %T: %s", store, err)
    1.45  		}
    1.46 -		retrieved, err = store.getAuthorizationCode(authCode.Code)
    1.47 +		retrieved, err = context.GetAuthorizationCode(authCode.Code)
    1.48  		if err != ErrAuthorizationCodeNotFound {
    1.49  			t.Errorf("Expected ErrAuthorizationCodeNotFound from %T, got %+v and %+v", store, retrieved, err)
    1.50  		}
    1.51 -		err = store.deleteAuthorizationCode(authCode.Code)
    1.52 +		err = context.DeleteAuthorizationCode(authCode.Code)
    1.53  		if err != ErrAuthorizationCodeNotFound {
    1.54  			t.Errorf("Expected ErrAuthorizationCodeNotFound from %T, got %+v", store, err)
    1.55  		}
    1.56 -		err = store.useAuthorizationCode(authCode.Code)
    1.57 +		err = context.UseAuthorizationCode(authCode.Code)
    1.58  		if err != ErrAuthorizationCodeNotFound {
    1.59  			t.Errorf("Expected ErrAuthorizationCodeNotFound from %T, got %+v", store, err)
    1.60  		}
    1.61 @@ -125,17 +126,13 @@
    1.62  		Website: "https://secondbit.org/",
    1.63  		Type:    "public",
    1.64  	}
    1.65 -	uri, err := url.Parse("https://test.secondbit.org/redirect")
    1.66 -	if err != nil {
    1.67 -		t.Fatal("Can't parse URL:", err)
    1.68 -	}
    1.69  	endpoint := Endpoint{
    1.70  		ID:       uuid.NewID(),
    1.71  		ClientID: client.ID,
    1.72 -		URI:      *uri,
    1.73 +		URI:      "https://test.secondbit.org/redirect",
    1.74  		Added:    time.Now(),
    1.75  	}
    1.76 -	err = testContext.SaveClient(client)
    1.77 +	err := testContext.SaveClient(client)
    1.78  	if err != nil {
    1.79  		t.Fatal("Can't store client:", err)
    1.80  	}
     2.1 --- a/client.go	Wed Jan 14 00:23:30 2015 -0500
     2.2 +++ b/client.go	Sun Jan 18 01:02:14 2015 -0500
     2.3 @@ -5,12 +5,15 @@
     2.4  	"encoding/hex"
     2.5  	"encoding/json"
     2.6  	"errors"
     2.7 -	"github.com/gorilla/mux"
     2.8 +	"log"
     2.9  	"net/http"
    2.10  	"net/url"
    2.11  	"strconv"
    2.12  	"time"
    2.13  
    2.14 +	"github.com/PuerkitoBio/purell"
    2.15 +	"github.com/gorilla/mux"
    2.16 +
    2.17  	"code.secondbit.org/uuid.hg"
    2.18  )
    2.19  
    2.20 @@ -37,24 +40,28 @@
    2.21  	ErrClientWebsiteTooLong = errors.New("client website must be at most 1024 characters")
    2.22  	// ErrClientWebsiteNotURL is returned when a Client's Website property is not a valid absolute URL.
    2.23  	ErrClientWebsiteNotURL = errors.New("client website must be a valid absolute URL")
    2.24 +	// ErrEndpointURINotURL is returned when an Endpoint's URI property is not a valid absolute URL.
    2.25 +	ErrEndpointURINotURL = errors.New("endpoint URI must be a valid absolute URL")
    2.26  )
    2.27  
    2.28  const (
    2.29  	clientTypePublic       = "public"
    2.30  	clientTypeConfidential = "confidential"
    2.31 +	minClientNameLen       = 2
    2.32 +	maxClientNameLen       = 24
    2.33  )
    2.34  
    2.35  // Client represents a client that grants access
    2.36  // to the auth server, exchanging grants for tokens,
    2.37  // and tokens for access.
    2.38  type Client struct {
    2.39 -	ID      uuid.ID
    2.40 -	Secret  string
    2.41 -	OwnerID uuid.ID
    2.42 -	Name    string
    2.43 -	Logo    string
    2.44 -	Website string
    2.45 -	Type    string
    2.46 +	ID      uuid.ID `json:"id,omitempty"`
    2.47 +	Secret  string  `json:"secret,omitempty"`
    2.48 +	OwnerID uuid.ID `json:"owner_id,omitempty"`
    2.49 +	Name    string  `json:"name,omitempty"`
    2.50 +	Logo    string  `json:"logo,omitempty"`
    2.51 +	Website string  `json:"website,omitempty"`
    2.52 +	Type    string  `json:"type,omitempty"`
    2.53  }
    2.54  
    2.55  // ApplyChange applies the properties of the passed
    2.56 @@ -169,10 +176,24 @@
    2.57  // following successful authorization grants and
    2.58  // exchanges for access tokens.
    2.59  type Endpoint struct {
    2.60 -	ID       uuid.ID
    2.61 -	ClientID uuid.ID
    2.62 -	URI      url.URL
    2.63 -	Added    time.Time
    2.64 +	ID            uuid.ID   `json:"id,omitempty"`
    2.65 +	ClientID      uuid.ID   `json:"client_id,omitempty"`
    2.66 +	URI           string    `json:"uri,omitempty"`
    2.67 +	NormalizedURI string    `json:"-"`
    2.68 +	Added         time.Time `json:"added,omitempty"`
    2.69 +}
    2.70 +
    2.71 +func normalizeURIString(in string) (string, error) {
    2.72 +	n, err := purell.NormalizeURLString(in, purell.FlagsUsuallySafeNonGreedy|purell.FlagSortQuery)
    2.73 +	if err != nil {
    2.74 +		log.Println(err)
    2.75 +		return in, ErrEndpointURINotURL
    2.76 +	}
    2.77 +	return n, nil
    2.78 +}
    2.79 +
    2.80 +func normalizeURI(in *url.URL) string {
    2.81 +	return purell.NormalizeURL(in, purell.FlagsUsuallySafeNonGreedy|purell.FlagSortQuery)
    2.82  }
    2.83  
    2.84  type sortedEndpoints []Endpoint
    2.85 @@ -304,7 +325,7 @@
    2.86  	m.endpointLock.RLock()
    2.87  	defer m.endpointLock.RUnlock()
    2.88  	for _, candidate := range m.endpoints[client.String()] {
    2.89 -		if endpoint == candidate.URI.String() {
    2.90 +		if endpoint == candidate.NormalizedURI {
    2.91  			return true, nil
    2.92  		}
    2.93  	}
    2.94 @@ -356,8 +377,19 @@
    2.95  		encode(w, r, http.StatusBadRequest, invalidFormatResponse)
    2.96  		return
    2.97  	}
    2.98 -	if req.Type != clientTypePublic && req.Type != clientTypeConfidential {
    2.99 +	if req.Type == "" {
   2.100 +		errors = append(errors, requestError{Slug: requestErrMissing, Field: "/type"})
   2.101 +	} else if req.Type != clientTypePublic && req.Type != clientTypeConfidential {
   2.102  		errors = append(errors, requestError{Slug: requestErrInvalidValue, Field: "/type"})
   2.103 +	}
   2.104 +	if req.Name == "" {
   2.105 +		errors = append(errors, requestError{Slug: requestErrMissing, Field: "/name"})
   2.106 +	} else if len(req.Name) < minClientNameLen {
   2.107 +		errors = append(errors, requestError{Slug: requestErrInsufficient, Field: "/name"})
   2.108 +	} else if len(req.Name) > maxClientNameLen {
   2.109 +		errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/name"})
   2.110 +	}
   2.111 +	if len(errors) > 0 {
   2.112  		encode(w, r, http.StatusBadRequest, response{Errors: errors})
   2.113  		return
   2.114  	}
   2.115 @@ -395,10 +427,14 @@
   2.116  			errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/endpoints/" + strconv.Itoa(pos)})
   2.117  			continue
   2.118  		}
   2.119 +		if !uri.IsAbs() {
   2.120 +			errors = append(errors, requestError{Slug: requestErrInvalidValue, Field: "/endpoints/" + strconv.Itoa(pos)})
   2.121 +			continue
   2.122 +		}
   2.123  		endpoint := Endpoint{
   2.124  			ID:       uuid.NewID(),
   2.125  			ClientID: client.ID,
   2.126 -			URI:      *uri,
   2.127 +			URI:      uri.String(),
   2.128  			Added:    time.Now(),
   2.129  		}
   2.130  		endpoints = append(endpoints, endpoint)
   2.131 @@ -412,6 +448,7 @@
   2.132  	resp := response{
   2.133  		Clients:   []Client{client},
   2.134  		Endpoints: endpoints,
   2.135 +		Errors:    errors,
   2.136  	}
   2.137  	encode(w, r, http.StatusCreated, resp)
   2.138  }
     3.1 --- a/client_test.go	Wed Jan 14 00:23:30 2015 -0500
     3.2 +++ b/client_test.go	Sun Jan 18 01:02:14 2015 -0500
     3.3 @@ -2,6 +2,7 @@
     3.4  
     3.5  import (
     3.6  	"bytes"
     3.7 +	"encoding/json"
     3.8  	"fmt"
     3.9  	"io/ioutil"
    3.10  	"net/http"
    3.11 @@ -60,7 +61,7 @@
    3.12  	if !endpoint1.Added.Equal(endpoint2.Added) {
    3.13  		return false, "Added", endpoint1.Added, endpoint2.Added
    3.14  	}
    3.15 -	if endpoint1.URI.String() != endpoint2.URI.String() {
    3.16 +	if endpoint1.URI != endpoint2.URI {
    3.17  		return false, "URI", endpoint1.URI, endpoint2.URI
    3.18  	}
    3.19  	return true, "", nil, nil
    3.20 @@ -77,15 +78,16 @@
    3.21  		Website: "website",
    3.22  	}
    3.23  	for _, store := range clientStores {
    3.24 -		err := store.saveClient(client)
    3.25 +		context := Context{clients: store}
    3.26 +		err := context.SaveClient(client)
    3.27  		if err != nil {
    3.28  			t.Fatalf("Error saving client to %T: %s", store, err)
    3.29  		}
    3.30 -		err = store.saveClient(client)
    3.31 +		err = context.SaveClient(client)
    3.32  		if err != ErrClientAlreadyExists {
    3.33  			t.Fatalf("Expected ErrClientAlreadyExists, got %v from %T", err, store)
    3.34  		}
    3.35 -		retrieved, err := store.getClient(client.ID)
    3.36 +		retrieved, err := context.GetClient(client.ID)
    3.37  		if err != nil {
    3.38  			t.Fatalf("Error retrieving client from %T: %s", store, err)
    3.39  		}
    3.40 @@ -93,7 +95,7 @@
    3.41  		if !success {
    3.42  			t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
    3.43  		}
    3.44 -		clients, err := store.listClientsByOwner(client.OwnerID, 25, 0)
    3.45 +		clients, err := context.ListClientsByOwner(client.OwnerID, 25, 0)
    3.46  		if err != nil {
    3.47  			t.Fatalf("Error retrieving clients by owner from %T: %s", store, err)
    3.48  		}
    3.49 @@ -104,19 +106,19 @@
    3.50  		if !success {
    3.51  			t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
    3.52  		}
    3.53 -		err = store.deleteClient(client.ID)
    3.54 +		err = context.DeleteClient(client.ID)
    3.55  		if err != nil {
    3.56  			t.Fatalf("Error deleting client from %T: %s", store, err)
    3.57  		}
    3.58 -		err = store.deleteClient(client.ID)
    3.59 +		err = context.DeleteClient(client.ID)
    3.60  		if err != ErrClientNotFound {
    3.61  			t.Fatalf("Expected ErrClientNotFound, got %s from %T", err, store)
    3.62  		}
    3.63 -		retrieved, err = store.getClient(client.ID)
    3.64 +		retrieved, err = context.GetClient(client.ID)
    3.65  		if err != ErrClientNotFound {
    3.66  			t.Fatalf("Expected ErrClientNotFound from %T, got %+v and %s", store, retrieved, err)
    3.67  		}
    3.68 -		clients, err = store.listClientsByOwner(client.OwnerID, 25, 0)
    3.69 +		clients, err = context.ListClientsByOwner(client.OwnerID, 25, 0)
    3.70  		if err != nil {
    3.71  			t.Fatalf("Error listing clients by owner from %T: %s", store, err)
    3.72  		}
    3.73 @@ -136,30 +138,29 @@
    3.74  		Logo:    "logo",
    3.75  		Website: "website",
    3.76  	}
    3.77 -	uri1, _ := url.Parse("https://www.example.com/")
    3.78 -	uri2, _ := url.Parse("https://www.example.com/my/full/path")
    3.79  	endpoint1 := Endpoint{
    3.80  		ID:       uuid.NewID(),
    3.81  		ClientID: client.ID,
    3.82  		Added:    time.Now(),
    3.83 -		URI:      *uri1,
    3.84 +		URI:      "https://www.example.com/",
    3.85  	}
    3.86  	endpoint2 := Endpoint{
    3.87  		ID:       uuid.NewID(),
    3.88  		ClientID: client.ID,
    3.89  		Added:    time.Now(),
    3.90 -		URI:      *uri2,
    3.91 +		URI:      "https://www.example.com/my/full/path",
    3.92  	}
    3.93  	for _, store := range clientStores {
    3.94 -		err := store.saveClient(client)
    3.95 +		context := Context{clients: store}
    3.96 +		err := context.SaveClient(client)
    3.97  		if err != nil {
    3.98  			t.Fatalf("Error saving client to %T: %s", store, err)
    3.99  		}
   3.100 -		err = store.addEndpoints(client.ID, []Endpoint{endpoint1})
   3.101 +		err = context.AddEndpoints(client.ID, []Endpoint{endpoint1})
   3.102  		if err != nil {
   3.103  			t.Fatalf("Error adding endpoint to client in %T: %s", store, err)
   3.104  		}
   3.105 -		endpoints, err := store.listEndpoints(client.ID, 10, 0)
   3.106 +		endpoints, err := context.ListEndpoints(client.ID, 10, 0)
   3.107  		if err != nil {
   3.108  			t.Fatalf("Error retrieving endpoints from %T: %s", store, err)
   3.109  		}
   3.110 @@ -170,11 +171,11 @@
   3.111  		if !success {
   3.112  			t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
   3.113  		}
   3.114 -		err = store.addEndpoints(client.ID, []Endpoint{endpoint2})
   3.115 +		err = context.AddEndpoints(client.ID, []Endpoint{endpoint2})
   3.116  		if err != nil {
   3.117  			t.Fatalf("Error adding endpoint to client in %T: %s", store, err)
   3.118  		}
   3.119 -		endpoints, err = store.listEndpoints(client.ID, 10, 0)
   3.120 +		endpoints, err = context.ListEndpoints(client.ID, 10, 0)
   3.121  		if err != nil {
   3.122  			t.Fatalf("Error retrieving endpoints from %T: %s", store, err)
   3.123  		}
   3.124 @@ -192,11 +193,11 @@
   3.125  		if !success {
   3.126  			t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
   3.127  		}
   3.128 -		err = store.removeEndpoint(client.ID, endpoint1.ID)
   3.129 +		err = context.RemoveEndpoint(client.ID, endpoint1.ID)
   3.130  		if err != nil {
   3.131  			t.Fatalf("Error removing endpoint from client in %T: %s", store, err)
   3.132  		}
   3.133 -		endpoints, err = store.listEndpoints(client.ID, 10, 0)
   3.134 +		endpoints, err = context.ListEndpoints(client.ID, 10, 0)
   3.135  		if err != nil {
   3.136  			t.Fatalf("Error listing endpoints in %T: %s", store, err)
   3.137  		}
   3.138 @@ -207,11 +208,11 @@
   3.139  		if !success {
   3.140  			t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
   3.141  		}
   3.142 -		err = store.removeEndpoint(client.ID, endpoint2.ID)
   3.143 +		err = context.RemoveEndpoint(client.ID, endpoint2.ID)
   3.144  		if err != nil {
   3.145  			t.Fatalf("Error removing endpoint from client in %T: %s", store, err)
   3.146  		}
   3.147 -		endpoints, err = store.listEndpoints(client.ID, 10, 0)
   3.148 +		endpoints, err = context.ListEndpoints(client.ID, 10, 0)
   3.149  		if err != nil {
   3.150  			t.Fatalf("Error listing endpoints in %T: %s", store, err)
   3.151  		}
   3.152 @@ -267,27 +268,28 @@
   3.153  			t.Fatalf("Expected field `%s` to be `%v`, got `%v`", field, expected, got)
   3.154  		}
   3.155  		for _, store := range clientStores {
   3.156 -			err := store.saveClient(client)
   3.157 +			context := Context{clients: store}
   3.158 +			err := context.SaveClient(client)
   3.159  			if err != nil {
   3.160  				t.Fatalf("Error saving client in %T: %s", store, err)
   3.161  			}
   3.162 -			err = store.updateClient(client.ID, change)
   3.163 +			err = context.UpdateClient(client.ID, change)
   3.164  			if err != nil {
   3.165  				t.Fatalf("Error updating client in %T: %s", store, err)
   3.166  			}
   3.167 -			retrieved, err := store.getClient(client.ID)
   3.168 +			retrieved, err := context.GetClient(client.ID)
   3.169  			if err != nil {
   3.170 -				t.Fatalf("Error getting profile from %T: %s", store, err)
   3.171 +				t.Fatalf("Error getting client from %T: %s", store, err)
   3.172  			}
   3.173  			match, field, expected, got = compareClients(expectation, retrieved)
   3.174  			if !match {
   3.175  				t.Fatalf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
   3.176  			}
   3.177 -			err = store.deleteClient(client.ID)
   3.178 +			err = context.DeleteClient(client.ID)
   3.179  			if err != nil {
   3.180  				t.Fatalf("Error deleting client from %T: %s", store, err)
   3.181  			}
   3.182 -			err = store.updateClient(client.ID, change)
   3.183 +			err = context.UpdateClient(client.ID, change)
   3.184  			if err != ErrClientNotFound {
   3.185  				t.Fatalf("Expected ErrClientNotFound, got %v from %T", err, store)
   3.186  			}
   3.187 @@ -305,19 +307,17 @@
   3.188  		Logo:    "logo",
   3.189  		Website: "website",
   3.190  	}
   3.191 -	uri1, _ := url.Parse("https://www.example.com/first")
   3.192 -	uri2, _ := url.Parse("https://www.example.com/my/full/path")
   3.193  	endpoint1 := Endpoint{
   3.194  		ID:       uuid.NewID(),
   3.195  		ClientID: client.ID,
   3.196  		Added:    time.Now(),
   3.197 -		URI:      *uri1,
   3.198 +		URI:      "https://www.example.com/first",
   3.199  	}
   3.200  	endpoint2 := Endpoint{
   3.201  		ID:       uuid.NewID(),
   3.202  		ClientID: client.ID,
   3.203  		Added:    time.Now(),
   3.204 -		URI:      *uri2,
   3.205 +		URI:      "https://www.example.com/my/full/path",
   3.206  	}
   3.207  	candidates := map[string]bool{
   3.208  		"https://www.example.com/":                 false,
   3.209 @@ -327,20 +327,21 @@
   3.210  		"https://www.example.com/my/full/path":     true,
   3.211  	}
   3.212  	for _, store := range clientStores {
   3.213 -		err := store.saveClient(client)
   3.214 +		context := Context{clients: store}
   3.215 +		err := context.SaveClient(client)
   3.216  		if err != nil {
   3.217  			t.Fatalf("Error saving client in %T: %s", store, err)
   3.218  		}
   3.219 -		err = store.addEndpoints(client.ID, []Endpoint{endpoint1})
   3.220 +		err = context.AddEndpoints(client.ID, []Endpoint{endpoint1})
   3.221  		if err != nil {
   3.222  			t.Fatalf("Error saving endpoint in %T: %s", store, err)
   3.223  		}
   3.224 -		err = store.addEndpoints(client.ID, []Endpoint{endpoint2})
   3.225 +		err = context.AddEndpoints(client.ID, []Endpoint{endpoint2})
   3.226  		if err != nil {
   3.227  			t.Fatalf("Error saving endpoint in %T: %s", store, err)
   3.228  		}
   3.229  		for candidate, expectation := range candidates {
   3.230 -			result, err := store.checkEndpoint(client.ID, candidate)
   3.231 +			result, err := context.CheckEndpoint(client.ID, candidate)
   3.232  			if err != nil {
   3.233  				t.Fatalf("Error checking endpoint %s in %T: %s", candidate, store, err)
   3.234  			}
   3.235 @@ -367,19 +368,17 @@
   3.236  		Logo:    "logo",
   3.237  		Website: "website",
   3.238  	}
   3.239 -	uri1, _ := url.Parse("https://www.example.com/first")
   3.240 -	uri2, _ := url.Parse("https://www.example.com/my/full/path")
   3.241  	endpoint1 := Endpoint{
   3.242  		ID:       uuid.NewID(),
   3.243  		ClientID: client.ID,
   3.244  		Added:    time.Now(),
   3.245 -		URI:      *uri1,
   3.246 +		URI:      "https://www.example.com/first",
   3.247  	}
   3.248  	endpoint2 := Endpoint{
   3.249  		ID:       uuid.NewID(),
   3.250  		ClientID: client.ID,
   3.251  		Added:    time.Now(),
   3.252 -		URI:      *uri2,
   3.253 +		URI:      "https://www.example.com/my/full/path",
   3.254  	}
   3.255  	candidates := map[string]bool{
   3.256  		"https://www.example.com/":                 false,
   3.257 @@ -389,20 +388,21 @@
   3.258  		"https://www.example.com/my/full/path":     true,
   3.259  	}
   3.260  	for _, store := range clientStores {
   3.261 -		err := store.saveClient(client)
   3.262 +		context := Context{clients: store}
   3.263 +		err := context.SaveClient(client)
   3.264  		if err != nil {
   3.265  			t.Fatalf("Error saving client in %T: %s", store, err)
   3.266  		}
   3.267 -		err = store.addEndpoints(client.ID, []Endpoint{endpoint1})
   3.268 +		err = context.AddEndpoints(client.ID, []Endpoint{endpoint1})
   3.269  		if err != nil {
   3.270  			t.Fatalf("Error saving endpoint in %T: %s", store, err)
   3.271  		}
   3.272 -		err = store.addEndpoints(client.ID, []Endpoint{endpoint2})
   3.273 +		err = context.AddEndpoints(client.ID, []Endpoint{endpoint2})
   3.274  		if err != nil {
   3.275  			t.Fatalf("Error saving endpoint in %T: %s", store, err)
   3.276  		}
   3.277  		for candidate, expectation := range candidates {
   3.278 -			result, err := store.checkEndpoint(client.ID, candidate)
   3.279 +			result, err := context.CheckEndpoint(client.ID, candidate)
   3.280  			if err != nil {
   3.281  				t.Fatalf("Error checking endpoint %s in %T: %s", candidate, store, err)
   3.282  			}
   3.283 @@ -863,3 +863,143 @@
   3.284  		t.Errorf(`Expected empty body, got "%s"`, w.Body.String())
   3.285  	}
   3.286  }
   3.287 +
   3.288 +func TestCreateClientHandler(t *testing.T) {
   3.289 +	t.Parallel()
   3.290 +	memstore := NewMemstore()
   3.291 +	c := Context{
   3.292 +		clients:  memstore,
   3.293 +		profiles: memstore,
   3.294 +	}
   3.295 +	w := httptest.NewRecorder()
   3.296 +	r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/clients", nil)
   3.297 +	if err != nil {
   3.298 +		t.Fatal("Can't build request:", err)
   3.299 +	}
   3.300 +	r.Header.Set("Content-Type", "application/json")
   3.301 +	CreateClientHandler(w, r, c)
   3.302 +	if w.Code != http.StatusUnauthorized {
   3.303 +		t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
   3.304 +	}
   3.305 +	expected := `{"errors":[{"error":"access_denied"}]}`
   3.306 +	result := strings.TrimSpace(w.Body.String())
   3.307 +	if result != expected {
   3.308 +		t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
   3.309 +	}
   3.310 +	w = httptest.NewRecorder()
   3.311 +	r.Header.Set("Authorization", "Not basic at all...")
   3.312 +	CreateClientHandler(w, r, c)
   3.313 +	if w.Code != http.StatusUnauthorized {
   3.314 +		t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
   3.315 +	}
   3.316 +	expected = `{"errors":[{"error":"access_denied"}]}`
   3.317 +	result = strings.TrimSpace(w.Body.String())
   3.318 +	if result != expected {
   3.319 +		t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
   3.320 +	}
   3.321 +	w = httptest.NewRecorder()
   3.322 +	r.Header.Set("Authorization", "Basic TotallyNotBase64Encoded")
   3.323 +	CreateClientHandler(w, r, c)
   3.324 +	if w.Code != http.StatusUnauthorized {
   3.325 +		t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
   3.326 +	}
   3.327 +	expected = `{"errors":[{"error":"access_denied"}]}`
   3.328 +	result = strings.TrimSpace(w.Body.String())
   3.329 +	if result != expected {
   3.330 +		t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
   3.331 +	}
   3.332 +	w = httptest.NewRecorder()
   3.333 +	r.Header.Set("Authorization", "Basic dGhpc2hhc25vY29sb24=")
   3.334 +	CreateClientHandler(w, r, c)
   3.335 +	if w.Code != http.StatusUnauthorized {
   3.336 +		t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
   3.337 +	}
   3.338 +	expected = `{"errors":[{"error":"access_denied"}]}`
   3.339 +	result = strings.TrimSpace(w.Body.String())
   3.340 +	if result != expected {
   3.341 +		t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
   3.342 +	}
   3.343 +	profile := Profile{
   3.344 +		ID:                     uuid.NewID(),
   3.345 +		Name:                   "Test User",
   3.346 +		Passphrase:             "f3a4ac4f1d657b2e6e776d24213e39406d50a87a52691a2a78891425af1271d0",
   3.347 +		Iterations:             1,
   3.348 +		Salt:                   "d82d92cfa8bfb5a08270ebbf39a3710d24b352b937fcc8959ebcb40384cc616b",
   3.349 +		PassphraseScheme:       1,
   3.350 +		Compromised:            false,
   3.351 +		LockedUntil:            time.Time{},
   3.352 +		PassphraseReset:        "",
   3.353 +		PassphraseResetCreated: time.Time{},
   3.354 +		Created:                time.Now(),
   3.355 +		LastSeen:               time.Time{},
   3.356 +	}
   3.357 +	login := Login{
   3.358 +		Type:      "email",
   3.359 +		Value:     "test@example.com",
   3.360 +		ProfileID: profile.ID,
   3.361 +		Created:   time.Now(),
   3.362 +		LastUsed:  time.Time{},
   3.363 +	}
   3.364 +	w = httptest.NewRecorder()
   3.365 +	r.SetBasicAuth("test@example.com", "mysecurepassphrase")
   3.366 +	CreateClientHandler(w, r, c)
   3.367 +	if w.Code != http.StatusUnauthorized {
   3.368 +		t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
   3.369 +	}
   3.370 +	expected = `{"errors":[{"error":"access_denied"}]}`
   3.371 +	result = strings.TrimSpace(w.Body.String())
   3.372 +	if result != expected {
   3.373 +		t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
   3.374 +	}
   3.375 +	err = c.SaveProfile(profile)
   3.376 +	if err != nil {
   3.377 +		t.Error("Error saving profile:", err)
   3.378 +	}
   3.379 +	err = c.AddLogin(login)
   3.380 +	if err != nil {
   3.381 +		t.Error("Error adding login:", err)
   3.382 +	}
   3.383 +	r.SetBasicAuth("test@example.com", "mysecurepassphrase")
   3.384 +	type testStruct struct {
   3.385 +		request string
   3.386 +		code    int
   3.387 +		resp    response
   3.388 +	}
   3.389 +	tests := []testStruct{
   3.390 +		{``, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidFormat, Field: "/"}}}},
   3.391 +		{`{}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/type"}, {Slug: requestErrMissing, Field: "/name"}}}},
   3.392 +		{`{"type":"notarealtype"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrMissing, Field: "/name"}}}},
   3.393 +		{`{"type":"notarealtype","name":"myreallylongnameislongerthatthemaximumnamelength"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrOverflow, Field: "/name"}}}},
   3.394 +		{`{"type":"notarealtype","name":"a"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrInsufficient, Field: "/name"}}}},
   3.395 +		{`{"type":"public"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/name"}}}},
   3.396 +		{`{"type":"public","name":"myreallylongnameislongerthatthemaximumnamelength"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrOverflow, Field: "/name"}}}},
   3.397 +		{`{"type":"public","name":"a"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInsufficient, Field: "/name"}}}},
   3.398 +		{`{"name":"My Client"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/type"}}}},
   3.399 +		{`{"type":"notarealtype","name":"My Client"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}}}},
   3.400 +		{`{"type":"public","name":"My Client"}`, http.StatusCreated, response{Clients: []Client{{Name: "My Client", OwnerID: profile.ID, Type: "public"}}}},
   3.401 +		{`{"type":"public","name":"My Client", "endpoints": ["https://test.secondbit.org/", "https://paddy.io"]}`, http.StatusCreated, response{Clients: []Client{{Name: "My Client", OwnerID: profile.ID, Type: "public"}}, Endpoints: []Endpoint{{URI: "https://test.secondbit.org/"}, {URI: "https://paddy.io"}}}},
   3.402 +		{`{"type":"public","name":"My Client", "endpoints": [":/not a url", "https://paddy.io"]}`, http.StatusCreated, response{Clients: []Client{{Name: "My Client", OwnerID: profile.ID, Type: "public"}}, Endpoints: []Endpoint{{URI: "https://paddy.io"}}, Errors: []requestError{{Slug: requestErrInvalidFormat, Field: "/endpoints/0"}}}},
   3.403 +		{`{"type":"public","name":"My Client", "endpoints": [":/not a url", "/relative/uri", "https://paddy.io"]}`, http.StatusCreated, response{Clients: []Client{{Name: "My Client", OwnerID: profile.ID, Type: "public"}}, Endpoints: []Endpoint{{URI: "https://paddy.io"}}, Errors: []requestError{{Slug: requestErrInvalidFormat, Field: "/endpoints/0"}, {Slug: requestErrInvalidValue, Field: "/endpoints/1"}}}},
   3.404 +	}
   3.405 +	for pos, test := range tests {
   3.406 +		t.Logf("Test #%d: `%s`", pos, test.request)
   3.407 +		w = httptest.NewRecorder()
   3.408 +		body := bytes.NewBufferString(test.request)
   3.409 +		r.Body = ioutil.NopCloser(body)
   3.410 +		CreateClientHandler(w, r, c)
   3.411 +		if w.Code != test.code {
   3.412 +			t.Errorf("Expected response code to be %d, got %d", test.code, w.Code)
   3.413 +		}
   3.414 +		t.Logf("Response: %s", w.Body.String())
   3.415 +		var res response
   3.416 +		err = json.Unmarshal(w.Body.Bytes(), &res)
   3.417 +		if err != nil {
   3.418 +			t.Error("Unexpected error unmarshalling response:", err)
   3.419 +		}
   3.420 +		fillInServerGenerated(test.resp, res)
   3.421 +		success, field, expectation, result := compareResponses(test.resp, res)
   3.422 +		if !success {
   3.423 +			t.Errorf("Unexpected result for %s in response: expected %v, got %v", field, expectation, result)
   3.424 +		}
   3.425 +	}
   3.426 +}
     4.1 --- a/context.go	Wed Jan 14 00:23:30 2015 -0500
     4.2 +++ b/context.go	Sun Jan 18 01:02:14 2015 -0500
     4.3 @@ -113,6 +113,14 @@
     4.4  	if c.clients == nil {
     4.5  		return ErrNoClientStore
     4.6  	}
     4.7 +	for pos, endpoint := range endpoints {
     4.8 +		u, err := normalizeURIString(endpoint.URI)
     4.9 +		if err != nil {
    4.10 +			return err
    4.11 +		}
    4.12 +		endpoint.NormalizedURI = u
    4.13 +		endpoints[pos] = endpoint
    4.14 +	}
    4.15  	return c.clients.addEndpoints(client, endpoints)
    4.16  }
    4.17  
    4.18 @@ -132,7 +140,11 @@
    4.19  	if c.clients == nil {
    4.20  		return false, ErrNoClientStore
    4.21  	}
    4.22 -	return c.clients.checkEndpoint(client, URI)
    4.23 +	u, err := normalizeURIString(URI)
    4.24 +	if err != nil {
    4.25 +		return false, err
    4.26 +	}
    4.27 +	return c.clients.checkEndpoint(client, u)
    4.28  }
    4.29  
    4.30  // ListEndpoints finds Endpoints in the clientStore associated with the Context that belong
    4.31 @@ -355,7 +367,7 @@
    4.32  // that were created before that time will be returned. If profile is not nil, only Sessions belonging to
    4.33  // that Profile will be returned.
    4.34  func (c Context) ListSessions(profile uuid.ID, before time.Time, num int64) ([]Session, error) {
    4.35 -	if c.sessions != nil {
    4.36 +	if c.sessions == nil {
    4.37  		return []Session{}, ErrNoSessionStore
    4.38  	}
    4.39  	return c.sessions.listSessions(profile, before, num)
     5.1 --- a/oauth2.go	Wed Jan 14 00:23:30 2015 -0500
     5.2 +++ b/oauth2.go	Sun Jan 18 01:02:14 2015 -0500
     5.3 @@ -173,14 +173,6 @@
     5.4  		return
     5.5  	}
     5.6  	redirectURI := r.URL.Query().Get("redirect_uri")
     5.7 -	redirectURL, err := url.Parse(redirectURI)
     5.8 -	if err != nil {
     5.9 -		w.WriteHeader(http.StatusBadRequest)
    5.10 -		context.Render(w, getAuthorizationCodeTemplateName, map[string]interface{}{
    5.11 -			"error": template.HTML("The redirect_uri specified is not valid."),
    5.12 -		})
    5.13 -		return
    5.14 -	}
    5.15  	client, err := context.GetClient(clientID)
    5.16  	if err != nil {
    5.17  		if err == ErrClientNotFound {
    5.18 @@ -211,9 +203,15 @@
    5.19  	}
    5.20  	var validURI bool
    5.21  	if redirectURI != "" {
    5.22 -		// BUG(paddy): We really should normalize URIs before trying to compare them.
    5.23  		validURI, err = context.CheckEndpoint(clientID, redirectURI)
    5.24  		if err != nil {
    5.25 +			if err == ErrEndpointURINotURL {
    5.26 +				w.WriteHeader(http.StatusBadRequest)
    5.27 +				context.Render(w, getAuthorizationCodeTemplateName, map[string]interface{}{
    5.28 +					"error": template.HTML("The redirect_uri specified is not valid."),
    5.29 +				})
    5.30 +				return
    5.31 +			}
    5.32  			log.Println(err.Error())
    5.33  			w.WriteHeader(http.StatusInternalServerError)
    5.34  			context.Render(w, getAuthorizationCodeTemplateName, map[string]interface{}{
    5.35 @@ -237,9 +235,7 @@
    5.36  		if len(endpoints) != 1 {
    5.37  			validURI = false
    5.38  		} else {
    5.39 -			u := endpoints[0].URI // Copy it here to avoid grabbing a pointer to the memstore
    5.40 -			redirectURI = u.String()
    5.41 -			redirectURL = &u
    5.42 +			redirectURI = endpoints[0].URI
    5.43  		}
    5.44  	} else {
    5.45  		validURI = false
    5.46 @@ -251,6 +247,14 @@
    5.47  		})
    5.48  		return
    5.49  	}
    5.50 +	redirectURL, err := url.Parse(redirectURI)
    5.51 +	if err != nil {
    5.52 +		w.WriteHeader(http.StatusBadRequest)
    5.53 +		context.Render(w, getAuthorizationCodeTemplateName, map[string]interface{}{
    5.54 +			"error": template.HTML("The redirect_uri specified is not valid."),
    5.55 +		})
    5.56 +		return
    5.57 +	}
    5.58  	scope := r.URL.Query().Get("scope")
    5.59  	state := r.URL.Query().Get("state")
    5.60  	if r.URL.Query().Get("response_type") != "code" {
     6.1 --- a/oauth2_test.go	Wed Jan 14 00:23:30 2015 -0500
     6.2 +++ b/oauth2_test.go	Sun Jan 18 01:02:14 2015 -0500
     6.3 @@ -30,7 +30,7 @@
     6.4  	t.Parallel()
     6.5  	store := NewMemstore()
     6.6  	testContext := Context{
     6.7 -		template:  template.Must(template.New(getAuthorizationCodeTemplateName).Parse("Get auth grant")),
     6.8 +		template:  template.Must(template.New(getAuthorizationCodeTemplateName).Parse("{{ if not .error }}Get auth grant{{ else }}{{ .error }}{{ end }}")),
     6.9  		clients:   store,
    6.10  		authCodes: store,
    6.11  		profiles:  store,
    6.12 @@ -46,17 +46,13 @@
    6.13  		Website: "https://secondbit.org",
    6.14  		Type:    "public",
    6.15  	}
    6.16 -	uri, err := url.Parse("https://test.secondbit.org/redirect")
    6.17 -	if err != nil {
    6.18 -		t.Fatal("Can't parse URL:", err)
    6.19 -	}
    6.20  	endpoint := Endpoint{
    6.21  		ID:       uuid.NewID(),
    6.22  		ClientID: client.ID,
    6.23 -		URI:      *uri,
    6.24 +		URI:      "https://test.secondbit.org/redirect",
    6.25  		Added:    time.Now(),
    6.26  	}
    6.27 -	err = testContext.SaveClient(client)
    6.28 +	err := testContext.SaveClient(client)
    6.29  	if err != nil {
    6.30  		t.Fatal("Can't store client:", err)
    6.31  	}
    6.32 @@ -96,7 +92,7 @@
    6.33  		params.Set("response_type", "code")
    6.34  		params.Set("client_id", client.ID.String())
    6.35  		if i&uriSet != 0 {
    6.36 -			params.Set("redirect_uri", endpoint.URI.String())
    6.37 +			params.Set("redirect_uri", endpoint.URI)
    6.38  		}
    6.39  		if i&scopeSet != 0 {
    6.40  			params.Set("scope", "testscope")
    6.41 @@ -110,6 +106,7 @@
    6.42  		req.Header.Del("Content-Type")
    6.43  		GetAuthorizationCodeHandler(w, req, testContext)
    6.44  		if w.Code != http.StatusOK {
    6.45 +			t.Log("Response body:", w.Body.String())
    6.46  			t.Errorf("Expected status code to be %d, got %d for %s", http.StatusOK, w.Code, req.URL.String())
    6.47  		}
    6.48  		if w.Body.String() != "Get auth grant" {
    6.49 @@ -147,8 +144,8 @@
    6.50  			t.Errorf(`Expected state param in redirect URL to be "%s", got "%s" for %s`, params.Get("state"), red.Query().Get("state"), req.URL.String())
    6.51  		}
    6.52  		stripParam("state", red)
    6.53 -		if red.String() != endpoint.URI.String() {
    6.54 -			t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI.String(), red.String())
    6.55 +		if red.String() != endpoint.URI {
    6.56 +			t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI, red.String())
    6.57  		}
    6.58  	}
    6.59  }
    6.60 @@ -244,11 +241,7 @@
    6.61  		Name:    "My test client",
    6.62  		Type:    "public",
    6.63  	}
    6.64 -	uri, err := url.Parse("https://test.secondbit.org/redirect")
    6.65 -	if err != nil {
    6.66 -		t.Fatal("Can't parse URL:", err)
    6.67 -	}
    6.68 -	err = testContext.SaveClient(client)
    6.69 +	err := testContext.SaveClient(client)
    6.70  	if err != nil {
    6.71  		t.Fatal("Can't store client:", err)
    6.72  	}
    6.73 @@ -284,7 +277,7 @@
    6.74  	endpoint := Endpoint{
    6.75  		ID:       uuid.NewID(),
    6.76  		ClientID: client.ID,
    6.77 -		URI:      *uri,
    6.78 +		URI:      "https://test.secondbit.org/redirect",
    6.79  		Added:    time.Now(),
    6.80  	}
    6.81  	err = testContext.AddEndpoints(client.ID, []Endpoint{endpoint})
    6.82 @@ -304,7 +297,7 @@
    6.83  	endpoint2 := Endpoint{
    6.84  		ID:       uuid.NewID(),
    6.85  		ClientID: client.ID,
    6.86 -		URI:      *uri,
    6.87 +		URI:      "https://test.secondbit.org/redirect",
    6.88  		Added:    time.Now(),
    6.89  	}
    6.90  	err = testContext.AddEndpoints(client.ID, []Endpoint{endpoint2})
    6.91 @@ -353,17 +346,13 @@
    6.92  		Website: "https://secondbit.org",
    6.93  		Type:    "public",
    6.94  	}
    6.95 -	uri, err := url.Parse("https://test.secondbit.org/redirect")
    6.96 -	if err != nil {
    6.97 -		t.Fatal("Can't parse URL:", err)
    6.98 -	}
    6.99  	endpoint := Endpoint{
   6.100  		ID:       uuid.NewID(),
   6.101  		ClientID: client.ID,
   6.102 -		URI:      *uri,
   6.103 +		URI:      "https://test.secondbit.org/redirect",
   6.104  		Added:    time.Now(),
   6.105  	}
   6.106 -	err = testContext.SaveClient(client)
   6.107 +	err := testContext.SaveClient(client)
   6.108  	if err != nil {
   6.109  		t.Fatal("Can't store client:", err)
   6.110  	}
   6.111 @@ -391,7 +380,7 @@
   6.112  	params := url.Values{}
   6.113  	params.Set("response_type", "totally not code")
   6.114  	params.Set("client_id", client.ID.String())
   6.115 -	params.Set("redirect_uri", endpoint.URI.String())
   6.116 +	params.Set("redirect_uri", endpoint.URI)
   6.117  	params.Set("scope", "testscope")
   6.118  	params.Set("state", "my super secure state string")
   6.119  	req.URL.RawQuery = params.Encode()
   6.120 @@ -413,8 +402,8 @@
   6.121  		t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state"))
   6.122  	}
   6.123  	stripParam("state", red)
   6.124 -	if red.String() != endpoint.URI.String() {
   6.125 -		t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI.String(), red.String())
   6.126 +	if red.String() != endpoint.URI {
   6.127 +		t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI, red.String())
   6.128  	}
   6.129  	stripParam("response_type", req.URL)
   6.130  	w = httptest.NewRecorder()
   6.131 @@ -435,8 +424,8 @@
   6.132  		t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state"))
   6.133  	}
   6.134  	stripParam("state", red)
   6.135 -	if red.String() != endpoint.URI.String() {
   6.136 -		t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI.String(), red.String())
   6.137 +	if red.String() != endpoint.URI {
   6.138 +		t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI, red.String())
   6.139  	}
   6.140  }
   6.141  
   6.142 @@ -460,17 +449,13 @@
   6.143  		Website: "https://secondbit.org",
   6.144  		Type:    "public",
   6.145  	}
   6.146 -	uri, err := url.Parse("https://test.secondbit.org/redirect")
   6.147 -	if err != nil {
   6.148 -		t.Fatal("Can't parse URL:", err)
   6.149 -	}
   6.150  	endpoint := Endpoint{
   6.151  		ID:       uuid.NewID(),
   6.152  		ClientID: client.ID,
   6.153 -		URI:      *uri,
   6.154 +		URI:      "https://test.secondbit.org/redirect",
   6.155  		Added:    time.Now(),
   6.156  	}
   6.157 -	err = testContext.SaveClient(client)
   6.158 +	err := testContext.SaveClient(client)
   6.159  	if err != nil {
   6.160  		t.Fatal("Can't store client:", err)
   6.161  	}
   6.162 @@ -498,7 +483,7 @@
   6.163  	params := url.Values{}
   6.164  	params.Set("response_type", "code")
   6.165  	params.Set("client_id", client.ID.String())
   6.166 -	params.Set("redirect_uri", endpoint.URI.String())
   6.167 +	params.Set("redirect_uri", endpoint.URI)
   6.168  	params.Set("scope", "testscope")
   6.169  	params.Set("state", "my super secure state string")
   6.170  	data := url.Values{}
   6.171 @@ -524,8 +509,8 @@
   6.172  		t.Errorf(`Expected state param in redirect URL to be "%s", got "%s"`, params.Get("state"), red.Query().Get("state"))
   6.173  	}
   6.174  	stripParam("state", red)
   6.175 -	if red.String() != endpoint.URI.String() {
   6.176 -		t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI.String(), red.String())
   6.177 +	if red.String() != endpoint.URI {
   6.178 +		t.Errorf(`Expected redirect URL to be "%s", got "%s"`, endpoint.URI, red.String())
   6.179  	}
   6.180  }
   6.181  
   6.182 @@ -690,9 +675,9 @@
   6.183  	profile := Profile{
   6.184  		ID:                     uuid.NewID(),
   6.185  		Name:                   "Test User",
   6.186 -		Passphrase:             "febcbe74b9555ab3dd0135bdc3aa86d2ee5c38dd7fd44f7b6e2ea908e93b1362",
   6.187 -		Iterations:             1048576,
   6.188 -		Salt:                   "c0feab6ae682e7f7d14343b669b8afaa3b17ed72e9bb18a73f002be4c6b21686",
   6.189 +		Passphrase:             "f3a4ac4f1d657b2e6e776d24213e39406d50a87a52691a2a78891425af1271d0",
   6.190 +		Iterations:             1,
   6.191 +		Salt:                   "d82d92cfa8bfb5a08270ebbf39a3710d24b352b937fcc8959ebcb40384cc616b",
   6.192  		PassphraseScheme:       1,
   6.193  		Compromised:            false,
   6.194  		LockedUntil:            time.Time{},
     7.1 --- a/profile_test.go	Wed Jan 14 00:23:30 2015 -0500
     7.2 +++ b/profile_test.go	Sun Jan 18 01:02:14 2015 -0500
     7.3 @@ -99,15 +99,16 @@
     7.4  		LastSeen:               time.Now(),
     7.5  	}
     7.6  	for _, store := range profileStores {
     7.7 -		err := store.saveProfile(profile)
     7.8 +		context := Context{profiles: store}
     7.9 +		err := context.SaveProfile(profile)
    7.10  		if err != nil {
    7.11  			t.Errorf("Error saving profile to %T: %s", store, err)
    7.12  		}
    7.13 -		err = store.saveProfile(profile)
    7.14 +		err = context.SaveProfile(profile)
    7.15  		if err != ErrProfileAlreadyExists {
    7.16  			t.Errorf("Expected ErrProfileAlreadyExists from %T, got %+v", store, err)
    7.17  		}
    7.18 -		retrieved, err := store.getProfileByID(profile.ID)
    7.19 +		retrieved, err := context.GetProfileByID(profile.ID)
    7.20  		if err != nil {
    7.21  			t.Errorf("Error retrieving profile from %T: %s", store, err)
    7.22  		}
    7.23 @@ -115,15 +116,15 @@
    7.24  		if !match {
    7.25  			t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
    7.26  		}
    7.27 -		err = store.deleteProfile(profile.ID)
    7.28 +		err = context.DeleteProfile(profile.ID)
    7.29  		if err != nil {
    7.30  			t.Errorf("Error removing profile from %T: %s", store, err)
    7.31  		}
    7.32 -		retrieved, err = store.getProfileByID(profile.ID)
    7.33 +		retrieved, err = context.GetProfileByID(profile.ID)
    7.34  		if err != ErrProfileNotFound {
    7.35  			t.Errorf("Expected ErrProfileNotFound from %T, got %+v and %+v", store, retrieved, err)
    7.36  		}
    7.37 -		err = store.deleteProfile(profile.ID)
    7.38 +		err = context.DeleteProfile(profile.ID)
    7.39  		if err != ErrProfileNotFound {
    7.40  			t.Errorf("Expected ErrProfileNotFound from %T, got %+v", store, err)
    7.41  		}
    7.42 @@ -213,15 +214,16 @@
    7.43  			t.Errorf("Expected field `%s` to be `%v`, got `%v`", field, expected, got)
    7.44  		}
    7.45  		for _, store := range profileStores {
    7.46 -			err := store.saveProfile(profile)
    7.47 +			context := Context{profiles: store}
    7.48 +			err := context.SaveProfile(profile)
    7.49  			if err != nil {
    7.50  				t.Errorf("Error saving profile in %T: %s", store, err)
    7.51  			}
    7.52 -			err = store.updateProfile(profile.ID, change)
    7.53 +			err = context.UpdateProfile(profile.ID, change)
    7.54  			if err != nil {
    7.55  				t.Errorf("Error updating profile in %T: %s", store, err)
    7.56  			}
    7.57 -			retrieved, err := store.getProfileByID(profile.ID)
    7.58 +			retrieved, err := context.GetProfileByID(profile.ID)
    7.59  			if err != nil {
    7.60  				t.Errorf("Error getting profile from %T: %s", store, err)
    7.61  			}
    7.62 @@ -229,11 +231,11 @@
    7.63  			if !match {
    7.64  				t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
    7.65  			}
    7.66 -			err = store.deleteProfile(profile.ID)
    7.67 +			err = context.DeleteProfile(profile.ID)
    7.68  			if err != nil {
    7.69  				t.Errorf("Error deleting profile from %T: %s", store, err)
    7.70  			}
    7.71 -			err = store.updateProfile(profile.ID, change)
    7.72 +			err = context.UpdateProfile(profile.ID, change)
    7.73  			if err != ErrProfileNotFound {
    7.74  				t.Errorf("Expected ErrProfileNotFound, got %v from %T", err, store)
    7.75  			}
    7.76 @@ -256,25 +258,26 @@
    7.77  		Compromised: &truth,
    7.78  	}
    7.79  	for _, store := range profileStores {
    7.80 -		err := store.saveProfile(profile1)
    7.81 +		context := Context{profiles: store}
    7.82 +		err := context.SaveProfile(profile1)
    7.83  		if err != nil {
    7.84  			t.Errorf("Error saving profile in %T: %s", store, err)
    7.85  		}
    7.86 -		err = store.saveProfile(profile2)
    7.87 +		err = context.SaveProfile(profile2)
    7.88  		if err != nil {
    7.89  			t.Errorf("Error saving profile in %T: %s", store, err)
    7.90  		}
    7.91 -		err = store.saveProfile(profile3)
    7.92 +		err = context.SaveProfile(profile3)
    7.93  		if err != nil {
    7.94  			t.Errorf("Error saving profile in %T: %s", store, err)
    7.95  		}
    7.96 -		err = store.updateProfiles([]uuid.ID{profile1.ID, profile3.ID}, change)
    7.97 +		err = context.UpdateProfiles([]uuid.ID{profile1.ID, profile3.ID}, change)
    7.98  		if err != nil {
    7.99  			t.Errorf("Error updating profile in %T: %s", store, err)
   7.100  		}
   7.101  		profile1.Compromised = truth
   7.102  		profile3.Compromised = truth
   7.103 -		retrieved, err := store.getProfileByID(profile1.ID)
   7.104 +		retrieved, err := context.GetProfileByID(profile1.ID)
   7.105  		if err != nil {
   7.106  			t.Errorf("Error getting profile from %T: %s", store, err)
   7.107  		}
   7.108 @@ -282,7 +285,7 @@
   7.109  		if !match {
   7.110  			t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
   7.111  		}
   7.112 -		retrieved, err = store.getProfileByID(profile2.ID)
   7.113 +		retrieved, err = context.GetProfileByID(profile2.ID)
   7.114  		if err != nil {
   7.115  			t.Errorf("Error getting profile from %T: %s", store, err)
   7.116  		}
   7.117 @@ -290,7 +293,7 @@
   7.118  		if !match {
   7.119  			t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
   7.120  		}
   7.121 -		retrieved, err = store.getProfileByID(profile3.ID)
   7.122 +		retrieved, err = context.GetProfileByID(profile3.ID)
   7.123  		if err != nil {
   7.124  			t.Errorf("Error getting profile from %T: %s", store, err)
   7.125  		}
   7.126 @@ -311,15 +314,16 @@
   7.127  		LastUsed:  time.Now().Add(-1 * time.Minute),
   7.128  	}
   7.129  	for _, store := range profileStores {
   7.130 -		err := store.addLogin(login)
   7.131 +		context := Context{profiles: store}
   7.132 +		err := context.AddLogin(login)
   7.133  		if err != nil {
   7.134  			t.Errorf("Error adding login to %T: %s", store, err)
   7.135  		}
   7.136 -		err = store.addLogin(login)
   7.137 +		err = context.AddLogin(login)
   7.138  		if err != ErrLoginAlreadyExists {
   7.139  			t.Errorf("Expected ErrLoginAlreadyExists from %T, got %+v", store, err)
   7.140  		}
   7.141 -		retrieved, err := store.listLogins(login.ProfileID, 10, 0)
   7.142 +		retrieved, err := context.ListLogins(login.ProfileID, 10, 0)
   7.143  		if err != nil {
   7.144  			t.Errorf("Error retrieving logins from %T: %s", store, err)
   7.145  		}
   7.146 @@ -331,12 +335,12 @@
   7.147  			t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
   7.148  		}
   7.149  		lastUsed := time.Now()
   7.150 -		err = store.recordLoginUse(login.Value, lastUsed)
   7.151 +		err = context.RecordLoginUse(login.Value, lastUsed)
   7.152  		if err != nil {
   7.153  			t.Errorf("Error recording use of login to %T: %s", store, err)
   7.154  		}
   7.155  		login.LastUsed = lastUsed
   7.156 -		retrieved, err = store.listLogins(login.ProfileID, 10, 0)
   7.157 +		retrieved, err = context.ListLogins(login.ProfileID, 10, 0)
   7.158  		if err != nil {
   7.159  			t.Errorf("Error retrieving logins from %T: %s", store, err)
   7.160  		}
   7.161 @@ -347,15 +351,15 @@
   7.162  		if !match {
   7.163  			t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
   7.164  		}
   7.165 -		err = store.removeLogin(login.Value, login.ProfileID)
   7.166 +		err = context.RemoveLogin(login.Value, login.ProfileID)
   7.167  		if err != nil {
   7.168  			t.Errorf("Error removing login from %T: %s", store, err)
   7.169  		}
   7.170 -		retrieved, err = store.listLogins(login.ProfileID, 10, 0)
   7.171 +		retrieved, err = context.ListLogins(login.ProfileID, 10, 0)
   7.172  		if len(retrieved) != 0 {
   7.173  			t.Errorf("Expected 0 login results from %T, got %d: %+v", store, len(retrieved), retrieved)
   7.174  		}
   7.175 -		err = store.removeLogin(login.Value, login.ProfileID)
   7.176 +		err = context.RemoveLogin(login.Value, login.ProfileID)
   7.177  		if err != ErrLoginNotFound {
   7.178  			t.Errorf("Expected ErrLoginNotFound from %T, got %+v", store, err)
   7.179  		}
   7.180 @@ -386,15 +390,16 @@
   7.181  		LastUsed:  time.Now().Add(-1 * time.Minute),
   7.182  	}
   7.183  	for _, store := range profileStores {
   7.184 -		err := store.saveProfile(profile)
   7.185 +		context := Context{profiles: store}
   7.186 +		err := context.SaveProfile(profile)
   7.187  		if err != nil {
   7.188  			t.Errorf("Error saving profile in %T: %s", store, err)
   7.189  		}
   7.190 -		err = store.addLogin(login)
   7.191 +		err = context.AddLogin(login)
   7.192  		if err != nil {
   7.193  			t.Errorf("Error storing login in %T: %s", store, err)
   7.194  		}
   7.195 -		retrieved, err := store.getProfileByLogin(login.Value)
   7.196 +		retrieved, err := context.GetProfileByLogin(login.Value)
   7.197  		if err != nil {
   7.198  			t.Errorf("Error retrieving profile by login from %T: %s", store, err)
   7.199  		}
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/request_test.go	Sun Jan 18 01:02:14 2015 -0500
     8.3 @@ -0,0 +1,108 @@
     8.4 +package auth
     8.5 +
     8.6 +import "fmt"
     8.7 +
     8.8 +func compareErrors(err1, err2 requestError) (success bool, field string, val1, val2 interface{}) {
     8.9 +	if err1.Slug != err2.Slug {
    8.10 +		return false, "Slug", err1.Slug, err2.Slug
    8.11 +	}
    8.12 +	if err1.Field != err2.Field {
    8.13 +		return false, "Field", err1.Field, err2.Field
    8.14 +	}
    8.15 +	if err1.Param != err2.Param {
    8.16 +		return false, "Param", err1.Param, err2.Param
    8.17 +	}
    8.18 +	if err1.Header != err2.Header {
    8.19 +		return false, "Header", err1.Header, err2.Header
    8.20 +	}
    8.21 +	return true, "", nil, nil
    8.22 +}
    8.23 +
    8.24 +func compareResponses(resp1, resp2 response) (success bool, field string, val1, val2 interface{}) {
    8.25 +	if len(resp1.Errors) != len(resp2.Errors) {
    8.26 +		return false, "Errors", resp1.Errors, resp2.Errors
    8.27 +	}
    8.28 +	if len(resp1.Logins) != len(resp2.Logins) {
    8.29 +		return false, "Logins", resp1.Logins, resp2.Logins
    8.30 +	}
    8.31 +	if len(resp1.Profiles) != len(resp2.Profiles) {
    8.32 +		return false, "Profiles", resp1.Profiles, resp2.Profiles
    8.33 +	}
    8.34 +	if len(resp1.Clients) != len(resp2.Clients) {
    8.35 +		return false, "Clients", resp1.Clients, resp2.Clients
    8.36 +	}
    8.37 +	if len(resp1.Endpoints) != len(resp2.Endpoints) {
    8.38 +		return false, "Endpoints", resp1.Endpoints, resp2.Endpoints
    8.39 +	}
    8.40 +	for pos := range resp1.Errors {
    8.41 +		success, field, val1, val2 = compareErrors(resp1.Errors[pos], resp2.Errors[pos])
    8.42 +		if !success {
    8.43 +			field = fmt.Sprintf("Error %d %s", pos, field)
    8.44 +			return
    8.45 +		}
    8.46 +	}
    8.47 +	for pos := range resp1.Logins {
    8.48 +		success, field, val1, val2 = compareLogins(resp1.Logins[pos], resp2.Logins[pos])
    8.49 +		if !success {
    8.50 +			field = fmt.Sprintf("Login %d %s", pos, field)
    8.51 +			return
    8.52 +		}
    8.53 +	}
    8.54 +	for pos := range resp1.Profiles {
    8.55 +		success, field, val1, val2 = compareProfiles(resp1.Profiles[pos], resp2.Profiles[pos])
    8.56 +		if !success {
    8.57 +			field = fmt.Sprintf("Profile %d %s", pos, field)
    8.58 +			return
    8.59 +		}
    8.60 +	}
    8.61 +	for pos := range resp1.Clients {
    8.62 +		success, field, val1, val2 = compareClients(resp1.Clients[pos], resp2.Clients[pos])
    8.63 +		if !success {
    8.64 +			field = fmt.Sprintf("Client %d %s", pos, field)
    8.65 +			return
    8.66 +		}
    8.67 +	}
    8.68 +	for pos := range resp1.Endpoints {
    8.69 +		success, field, val1, val2 = compareEndpoints(resp1.Endpoints[pos], resp2.Endpoints[pos])
    8.70 +		if !success {
    8.71 +			field = fmt.Sprintf("Endpoint %d %s", pos, field)
    8.72 +			return
    8.73 +		}
    8.74 +	}
    8.75 +	return true, "", nil, nil
    8.76 +}
    8.77 +
    8.78 +func fillInServerGenerated(expectation, result response) {
    8.79 +	if len(expectation.Profiles) > 0 {
    8.80 +		for pos, profile := range expectation.Profiles {
    8.81 +			profile.ID = result.Profiles[pos].ID
    8.82 +			profile.Created = result.Profiles[pos].Created
    8.83 +			profile.LastSeen = result.Profiles[pos].LastSeen
    8.84 +			expectation.Profiles[pos] = profile
    8.85 +		}
    8.86 +	}
    8.87 +	if len(expectation.Logins) > 0 {
    8.88 +		for pos, login := range expectation.Logins {
    8.89 +			login.ProfileID = result.Logins[pos].ProfileID
    8.90 +			login.Created = result.Logins[pos].Created
    8.91 +			login.LastUsed = result.Logins[pos].LastUsed
    8.92 +			expectation.Logins[pos] = login
    8.93 +		}
    8.94 +	}
    8.95 +	if len(expectation.Clients) > 0 {
    8.96 +		for pos, client := range expectation.Clients {
    8.97 +			client.ID = result.Clients[pos].ID
    8.98 +			client.Secret = result.Clients[pos].Secret
    8.99 +			client.OwnerID = result.Clients[pos].OwnerID
   8.100 +			expectation.Clients[pos] = client
   8.101 +		}
   8.102 +	}
   8.103 +	if len(expectation.Endpoints) > 0 {
   8.104 +		for pos, endpoint := range expectation.Endpoints {
   8.105 +			endpoint.ID = result.Endpoints[pos].ID
   8.106 +			endpoint.ClientID = result.Endpoints[pos].ClientID
   8.107 +			endpoint.Added = result.Endpoints[pos].Added
   8.108 +			expectation.Endpoints[pos] = endpoint
   8.109 +		}
   8.110 +	}
   8.111 +}
     9.1 --- a/session_test.go	Wed Jan 14 00:23:30 2015 -0500
     9.2 +++ b/session_test.go	Sun Jan 18 01:02:14 2015 -0500
     9.3 @@ -46,15 +46,16 @@
     9.4  		Active:    true,
     9.5  	}
     9.6  	for _, store := range sessionStores {
     9.7 -		err := store.createSession(session)
     9.8 +		context := Context{sessions: store}
     9.9 +		err := context.CreateSession(session)
    9.10  		if err != nil {
    9.11  			t.Errorf("Error saving session to %T: %s", store, err)
    9.12  		}
    9.13 -		err = store.createSession(session)
    9.14 +		err = context.CreateSession(session)
    9.15  		if err != ErrSessionAlreadyExists {
    9.16  			t.Errorf("Expected ErrSessionAlreadyExists from %T, got %s", store, err)
    9.17  		}
    9.18 -		retrieved, err := store.getSession(session.ID)
    9.19 +		retrieved, err := context.GetSession(session.ID)
    9.20  		if err != nil {
    9.21  			t.Errorf("Error retrieving session from %T: %s", store, err)
    9.22  		}
    9.23 @@ -62,7 +63,7 @@
    9.24  		if !success {
    9.25  			t.Errorf("Expected field %s to be %v, but got %v from %T", field, expectation, result, store)
    9.26  		}
    9.27 -		retrievedList, err := store.listSessions(session.ProfileID, time.Time{}, 10)
    9.28 +		retrievedList, err := context.ListSessions(session.ProfileID, time.Time{}, 10)
    9.29  		if err != nil {
    9.30  			t.Errorf("Error retrieving sessions by profile from %T: %s", store, err)
    9.31  		}
    9.32 @@ -73,22 +74,22 @@
    9.33  		if !success {
    9.34  			t.Errorf("Expected field %s to be %v, but got %v from %T", field, expectation, result, store)
    9.35  		}
    9.36 -		err = store.removeSession(session.ID)
    9.37 +		err = context.RemoveSession(session.ID)
    9.38  		if err != nil {
    9.39  			t.Errorf("Error removing session from %T: %s", store, err)
    9.40  		}
    9.41 -		retrieved, err = store.getSession(session.ID)
    9.42 +		retrieved, err = context.GetSession(session.ID)
    9.43  		if err != ErrSessionNotFound {
    9.44  			t.Errorf("Expected ErrSessionNotFound from %T, got %s", store, err)
    9.45  		}
    9.46 -		retrievedList, err = store.listSessions(session.ProfileID, time.Time{}, 10)
    9.47 +		retrievedList, err = context.ListSessions(session.ProfileID, time.Time{}, 10)
    9.48  		if err != nil {
    9.49  			t.Errorf("Error retrieving sessions by profile from %T: %s", store, err)
    9.50  		}
    9.51  		if len(retrievedList) != 0 {
    9.52  			t.Errorf("Expected 0 sessions retrieved by profile from %T, got %d", store, len(retrievedList))
    9.53  		}
    9.54 -		err = store.removeSession(session.ID)
    9.55 +		err = context.RemoveSession(session.ID)
    9.56  		if err != ErrSessionNotFound {
    9.57  			t.Errorf("Expected ErrSessionNotFound from %T, got %s", store, err)
    9.58  		}
    10.1 --- a/token_test.go	Wed Jan 14 00:23:30 2015 -0500
    10.2 +++ b/token_test.go	Sun Jan 18 01:02:14 2015 -0500
    10.3 @@ -55,15 +55,16 @@
    10.4  		ProfileID:    uuid.NewID(),
    10.5  	}
    10.6  	for _, store := range tokenStores {
    10.7 -		err := store.saveToken(token)
    10.8 +		context := Context{tokens: store}
    10.9 +		err := context.SaveToken(token)
   10.10  		if err != nil {
   10.11  			t.Errorf("Error saving token to %T: %s", store, err)
   10.12  		}
   10.13 -		err = store.saveToken(token)
   10.14 +		err = context.SaveToken(token)
   10.15  		if err != ErrTokenAlreadyExists {
   10.16  			t.Errorf("Expected ErrTokenAlreadyExists from %T, got %s", store, err)
   10.17  		}
   10.18 -		retrievedAccess, err := store.getToken(token.AccessToken, false)
   10.19 +		retrievedAccess, err := context.GetToken(token.AccessToken, false)
   10.20  		if err != nil {
   10.21  			t.Errorf("Error retrieving token from %T: %s", store, err)
   10.22  		}
   10.23 @@ -71,7 +72,7 @@
   10.24  		if !success {
   10.25  			t.Errorf("Expected field %s to be %v, but got %v from %T", field, expectation, result, store)
   10.26  		}
   10.27 -		retrievedRefresh, err := store.getToken(token.RefreshToken, true)
   10.28 +		retrievedRefresh, err := context.GetToken(token.RefreshToken, true)
   10.29  		if err != nil {
   10.30  			t.Errorf("Error retrieving refresh token from %T: %s", store, err)
   10.31  		}
   10.32 @@ -79,7 +80,7 @@
   10.33  		if !success {
   10.34  			t.Errorf("Expected field %s to be %v, but got %v from %T", field, expectation, result, store)
   10.35  		}
   10.36 -		retrievedProfile, err := store.getTokensByProfileID(token.ProfileID, 25, 0)
   10.37 +		retrievedProfile, err := context.GetTokensByProfileID(token.ProfileID, 25, 0)
   10.38  		if err != nil {
   10.39  			t.Errorf("Error retrieving token by profile from %T: %s", store, err)
   10.40  		}
   10.41 @@ -90,11 +91,11 @@
   10.42  		if !success {
   10.43  			t.Errorf("Expected field %s to be %v, but got %v from %T", field, expectation, result, store)
   10.44  		}
   10.45 -		err = store.revokeToken(token.AccessToken, false)
   10.46 +		err = context.RevokeToken(token.AccessToken, false)
   10.47  		if err != nil {
   10.48  			t.Errorf("Error revoking token in %T: %s", store, err)
   10.49  		}
   10.50 -		retrievedRevoked, err := store.getToken(token.AccessToken, false)
   10.51 +		retrievedRevoked, err := context.GetToken(token.AccessToken, false)
   10.52  		if err != nil {
   10.53  			t.Errorf("Error retrieving token from %T: %s", store, err)
   10.54  		}
   10.55 @@ -104,34 +105,34 @@
   10.56  			t.Errorf("Expected field %s to be %v, but got %v from %T", field, expectation, result, store)
   10.57  		}
   10.58  		// TODO(paddy): test revoking by refresh token.
   10.59 -		err = store.removeToken(token.AccessToken)
   10.60 +		err = context.RemoveToken(token.AccessToken)
   10.61  		if err != nil {
   10.62  			t.Errorf("Error removing token from %T: %s", store, err)
   10.63  		}
   10.64 -		_, err = store.getToken(token.AccessToken, false)
   10.65 +		_, err = context.GetToken(token.AccessToken, false)
   10.66  		if err != ErrTokenNotFound {
   10.67  			t.Errorf("Expected ErrTokenNotFound from %T, got %s", store, err)
   10.68  		}
   10.69 -		_, err = store.getToken(token.RefreshToken, true)
   10.70 +		_, err = context.GetToken(token.RefreshToken, true)
   10.71  		if err != ErrTokenNotFound {
   10.72  			t.Errorf("Expected ErrTokenNotFound from %T, got %s", store, err)
   10.73  		}
   10.74 -		retrievedProfile, err = store.getTokensByProfileID(token.ProfileID, 25, 0)
   10.75 +		retrievedProfile, err = context.GetTokensByProfileID(token.ProfileID, 25, 0)
   10.76  		if err != nil {
   10.77  			t.Errorf("Error retrieving token by profile from %T: %s", store, err)
   10.78  		}
   10.79  		if len(retrievedProfile) != 0 {
   10.80  			t.Errorf("Expected list of 0 tokens from %T, got %+v", store, retrievedProfile)
   10.81  		}
   10.82 -		err = store.removeToken(token.AccessToken)
   10.83 +		err = context.RemoveToken(token.AccessToken)
   10.84  		if err != ErrTokenNotFound {
   10.85  			t.Errorf("Expected ErrTokenNotFound from %T, got %s", store, err)
   10.86  		}
   10.87 -		err = store.revokeToken(token.AccessToken, false)
   10.88 +		err = context.RevokeToken(token.AccessToken, false)
   10.89  		if err != ErrTokenNotFound {
   10.90  			t.Errorf("Expected ErrTokenNotFound from %T, got %s", store, err)
   10.91  		}
   10.92 -		err = store.revokeToken(token.RefreshToken, true)
   10.93 +		err = context.RevokeToken(token.RefreshToken, true)
   10.94  		if err != ErrTokenNotFound {
   10.95  			t.Errorf("Expected ErrTokenNotFound from %T, got %s", store, err)
   10.96  		}