Stub out sessions.
Stop using the Login type when getting profile by Login, removing Logins,
or recording Login use. The Login value has to be unique, anyways, and we don't
actually know the Login type when getting a profile by Login. That's sort of the
point.
Create the concept of Sessions and a sessionStore type to manage our
authentication sessions with the server. As per OWASP, we're basically just
going to use a transparent, SHA256-generated random string as an ID, and store
it client-side and server-side and just pass it back and forth.
Add the ProfileID to the Grant type, because we need to remember who granted
access. That's sort of important.
Set a defaultGrantExpiration constant to an hour, so we have that one constant
when creating new Grants.
Create a helper that pulls the session ID out of an auth cookie, checks it
against the sessionStore, and returns the Session if it's valid.
Create a helper that pulls the username and password out of a basic auth header.
Create a helper that authenticates a user's login and passphrase, checking them
against the profileStore securely.
Stub out how the cookie checking is going to work for getting grant approval.
Fix the stored Grant RedirectURI to be the passed in redirect URI, not the
RedirectURI that we ultimately redirect to. This is in accordance with the spec.
Store the profile ID from our session in the created Grant.
Stub out a GetTokenHandler that will allow users to exchange a Grant for a
Token.
Set a constant for the current passphrase scheme, which we will increment for
each revision to the passphrase scheme, for backwards compatibility.
Change the Profile iterations property to an int, not an int64, to match the
code.secondbit.org/pass library (which is matching the PBKDF2 library).
10 "code.secondbit.org/uuid"
14 clientChangeSecret = 1 << iota
21 var clientStores = []clientStore{NewMemstore()}
23 func compareClients(client1, client2 Client) (success bool, field string, val1, val2 interface{}) {
24 if !client1.ID.Equal(client2.ID) {
25 return false, "ID", client1.ID, client2.ID
27 if client1.Secret != client2.Secret {
28 return false, "secret", client1.Secret, client2.Secret
30 if !client1.OwnerID.Equal(client2.OwnerID) {
31 return false, "owner ID", client1.OwnerID, client2.OwnerID
33 if client1.Name != client2.Name {
34 return false, "name", client1.Name, client2.Name
36 if client1.Logo != client2.Logo {
37 return false, "logo", client1.Logo, client2.Logo
39 if client1.Website != client2.Website {
40 return false, "website", client1.Website, client2.Website
42 if client1.Type != client2.Type {
43 return false, "type", client1.Type, client2.Type
45 return true, "", nil, nil
48 func compareEndpoints(endpoint1, endpoint2 Endpoint) (success bool, field string, val1, val2 interface{}) {
49 if !endpoint1.ID.Equal(endpoint2.ID) {
50 return false, "ID", endpoint1.ID, endpoint2.ID
52 if !endpoint1.ClientID.Equal(endpoint2.ClientID) {
53 return false, "OwnerID", endpoint1.ClientID, endpoint2.ClientID
55 if !endpoint1.Added.Equal(endpoint2.Added) {
56 return false, "Added", endpoint1.Added, endpoint2.Added
58 if endpoint1.URI.String() != endpoint2.URI.String() {
59 return false, "URI", endpoint1.URI, endpoint2.URI
61 return true, "", nil, nil
64 func TestClientStoreSuccess(t *testing.T) {
69 OwnerID: uuid.NewID(),
74 for _, store := range clientStores {
75 err := store.saveClient(client)
77 t.Fatalf("Error saving client to %T: %s", store, err)
79 err = store.saveClient(client)
80 if err != ErrClientAlreadyExists {
81 t.Fatalf("Expected ErrClientAlreadyExists, got %v from %T", err, store)
83 retrieved, err := store.getClient(client.ID)
85 t.Fatalf("Error retrieving client from %T: %s", store, err)
87 success, field, expectation, result := compareClients(client, retrieved)
89 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
91 clients, err := store.listClientsByOwner(client.OwnerID, 25, 0)
93 t.Fatalf("Error retrieving clients by owner from %T: %s", store, err)
95 if len(clients) != 1 {
96 t.Fatalf("Expected 1 client in response from %T, got %+v", store, clients)
98 success, field, expectation, result = compareClients(client, clients[0])
100 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
102 err = store.deleteClient(client.ID)
104 t.Fatalf("Error deleting client from %T: %s", store, err)
106 err = store.deleteClient(client.ID)
107 if err != ErrClientNotFound {
108 t.Fatalf("Expected ErrClientNotFound, got %s from %T", err, store)
110 retrieved, err = store.getClient(client.ID)
111 if err != ErrClientNotFound {
112 t.Fatalf("Expected ErrClientNotFound from %T, got %+v and %s", store, retrieved, err)
114 clients, err = store.listClientsByOwner(client.OwnerID, 25, 0)
116 t.Fatalf("Error listing clients by owner from %T: %s", store, err)
118 if len(clients) != 0 {
119 t.Fatalf("Expected 0 clients in response from %T, got %+v", store, clients)
124 func TestEndpointStoreSuccess(t *testing.T) {
129 OwnerID: uuid.NewID(),
134 uri1, _ := url.Parse("https://www.example.com/")
135 uri2, _ := url.Parse("https://www.example.com/my/full/path")
136 endpoint1 := Endpoint{
142 endpoint2 := Endpoint{
148 for _, store := range clientStores {
149 err := store.saveClient(client)
151 t.Fatalf("Error saving client to %T: %s", store, err)
153 err = store.addEndpoint(client.ID, endpoint1)
155 t.Fatalf("Error adding endpoint to client in %T: %s", store, err)
157 endpoints, err := store.listEndpoints(client.ID, 10, 0)
159 t.Fatalf("Error retrieving endpoints from %T: %s", store, err)
161 if len(endpoints) != 1 {
162 t.Fatalf("Expected %d endpoints, got %+v from %T", 1, endpoints, store)
164 success, field, expectation, result := compareEndpoints(endpoint1, endpoints[0])
166 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
168 err = store.addEndpoint(client.ID, endpoint2)
170 t.Fatalf("Error adding endpoint to client in %T: %s", store, err)
172 endpoints, err = store.listEndpoints(client.ID, 10, 0)
174 t.Fatalf("Error retrieving endpoints from %T: %s", store, err)
176 if len(endpoints) != 2 {
177 t.Fatalf("Expected %d endpoints, got %+v from %T", 2, endpoints, store)
179 sortedEnd := sortedEndpoints(endpoints)
181 endpoints = []Endpoint(sortedEnd)
182 success, field, expectation, result = compareEndpoints(endpoint1, endpoints[0])
184 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
186 success, field, expectation, result = compareEndpoints(endpoint2, endpoints[1])
188 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
190 err = store.removeEndpoint(client.ID, endpoint1.ID)
192 t.Fatalf("Error removing endpoint from client in %T: %s", store, err)
194 endpoints, err = store.listEndpoints(client.ID, 10, 0)
196 t.Fatalf("Error listing endpoints in %T: %s", store, err)
198 if len(endpoints) != 1 {
199 t.Fatalf("Expected %d endpoints, got %+v from %T", 1, endpoints, store)
201 success, field, expectation, result = compareEndpoints(endpoint2, endpoints[0])
203 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
205 err = store.removeEndpoint(client.ID, endpoint2.ID)
207 t.Fatalf("Error removing endpoint from client in %T: %s", store, err)
209 endpoints, err = store.listEndpoints(client.ID, 10, 0)
211 t.Fatalf("Error listing endpoints in %T: %s", store, err)
213 if len(endpoints) != 0 {
214 t.Fatalf("Expected %d endpoints, got %+v from %T", 0, endpoints, store)
219 func TestClientUpdates(t *testing.T) {
225 OwnerID: uuid.NewID(),
230 for i := 0; i < variations; i++ {
231 var secret, name, logo, website string
232 change := ClientChange{}
233 expectation := client
235 if i&clientChangeSecret != 0 {
236 secret = fmt.Sprintf("secret-%d", i)
237 change.Secret = &secret
238 expectation.Secret = secret
240 if i&clientChangeOwnerID != 0 {
241 change.OwnerID = uuid.NewID()
242 expectation.OwnerID = change.OwnerID
244 if i&clientChangeName != 0 {
245 name = fmt.Sprintf("name-%d", i)
247 expectation.Name = name
249 if i&clientChangeLogo != 0 {
250 logo = fmt.Sprintf("logo-%d", i)
252 expectation.Logo = logo
254 if i&clientChangeWebsite != 0 {
255 website = fmt.Sprintf("website-%d", i)
256 change.Website = &website
257 expectation.Website = website
259 result.ApplyChange(change)
260 match, field, expected, got := compareClients(expectation, result)
262 t.Fatalf("Expected field `%s` to be `%v`, got `%v`", field, expected, got)
264 for _, store := range clientStores {
265 err := store.saveClient(client)
267 t.Fatalf("Error saving client in %T: %s", store, err)
269 err = store.updateClient(client.ID, change)
271 t.Fatalf("Error updating client in %T: %s", store, err)
273 retrieved, err := store.getClient(client.ID)
275 t.Fatalf("Error getting profile from %T: %s", store, err)
277 match, field, expected, got = compareClients(expectation, retrieved)
279 t.Fatalf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
281 err = store.deleteClient(client.ID)
283 t.Fatalf("Error deleting client from %T: %s", store, err)
285 err = store.updateClient(client.ID, change)
286 if err != ErrClientNotFound {
287 t.Fatalf("Expected ErrClientNotFound, got %v from %T", err, store)
293 func TestClientEndpointChecks(t *testing.T) {
298 OwnerID: uuid.NewID(),
303 uri1, _ := url.Parse("https://www.example.com/first")
304 uri2, _ := url.Parse("https://www.example.com/my/full/path")
305 endpoint1 := Endpoint{
311 endpoint2 := Endpoint{
317 candidates := map[string]bool{
318 "https://www.example.com/": false,
319 "https://www.example.com/first": true,
320 "https://www.example.com/first/extra/path": false,
321 "https://www.example.com/my": false,
322 "https://www.example.com/my/full/path": true,
324 for _, store := range clientStores {
325 err := store.saveClient(client)
327 t.Fatalf("Error saving client in %T: %s", store, err)
329 err = store.addEndpoint(client.ID, endpoint1)
331 t.Fatalf("Error saving endpoint in %T: %s", store, err)
333 err = store.addEndpoint(client.ID, endpoint2)
335 t.Fatalf("Error saving endpoint in %T: %s", store, err)
337 for candidate, expectation := range candidates {
338 result, err := store.checkEndpoint(client.ID, candidate)
340 t.Fatalf("Error checking endpoint %s in %T: %s", candidate, store, err)
342 if result != expectation {
349 t.Errorf("Expected %s match for %s in %T, got %s match", expectStr, candidate, store, resultStr)
355 func TestClientEndpointChecksStrict(t *testing.T) {
360 OwnerID: uuid.NewID(),
365 uri1, _ := url.Parse("https://www.example.com/first")
366 uri2, _ := url.Parse("https://www.example.com/my/full/path")
367 endpoint1 := Endpoint{
373 endpoint2 := Endpoint{
379 candidates := map[string]bool{
380 "https://www.example.com/": false,
381 "https://www.example.com/first": true,
382 "https://www.example.com/first/extra/path": false,
383 "https://www.example.com/my": false,
384 "https://www.example.com/my/full/path": true,
386 for _, store := range clientStores {
387 err := store.saveClient(client)
389 t.Fatalf("Error saving client in %T: %s", store, err)
391 err = store.addEndpoint(client.ID, endpoint1)
393 t.Fatalf("Error saving endpoint in %T: %s", store, err)
395 err = store.addEndpoint(client.ID, endpoint2)
397 t.Fatalf("Error saving endpoint in %T: %s", store, err)
399 for candidate, expectation := range candidates {
400 result, err := store.checkEndpoint(client.ID, candidate)
402 t.Fatalf("Error checking endpoint %s in %T: %s", candidate, store, err)
404 if result != expectation {
411 t.Errorf("Expected %s match for %s in %T, got %s match", expectStr, candidate, store, resultStr)
417 func TestClientChangeValidation(t *testing.T) {
419 change := ClientChange{}
420 if err := change.Validate(); err != ErrEmptyChange {
421 t.Errorf("Expected %s to give an error of %s, gave %s", "empty change", ErrEmptyChange, err)
423 names := map[string]error{
424 "a": ErrClientNameTooShort,
427 "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopq": ErrClientNameTooLong,
429 for name, expectation := range names {
430 change = ClientChange{Name: &name}
431 if err := change.Validate(); err != expectation {
432 t.Errorf("Expected %s to give an error of %s, gave %s", name, expectation, err)
436 for i := 0; i < 1025; i++ {
437 longPath = fmt.Sprintf("%s%d", longPath, i)
439 logos := map[string]error{
440 "https://www.example.com/" + longPath: ErrClientLogoTooLong,
441 "https://www.example.com/ab": nil,
442 "www.example.com/ab": ErrClientLogoNotURL,
443 "test": ErrClientLogoNotURL,
446 for logo, expectation := range logos {
447 change = ClientChange{Logo: &logo}
448 if err := change.Validate(); err != expectation {
449 t.Errorf("Expected %s to give an error of %s, gave %s", logo, expectation, err)
452 websites := map[string]error{
453 "https://www.example.com/" + longPath: ErrClientWebsiteTooLong,
454 "https://www.example.com/ab": nil,
455 "www.example.com/ab": ErrClientWebsiteNotURL,
456 "test": ErrClientWebsiteNotURL,
459 for website, expectation := range websites {
460 change = ClientChange{Website: &website}
461 if err := change.Validate(); err != expectation {
462 t.Errorf("Expected %s to give an error of %s, gave %s", website, expectation, err)