auth
auth/client_test.go
Implement postgres clientStore. Stop requiring the client ID be passed to clientStore.addEndpoints and context.AddEndpoints. The Endpoints themselves contain the client ID. When using the authd server, set the log flags to include the file path and line number. Add an ErrEndpointAlreadyExists error, to return when creating an endpoint and its ID already exists in the database. Add a Deleted property to Clients and remove the clientStore.deleteClient and context.DeleteClient methods. We're not going to actually remove that data, and we want to be able to restore it, so include it in the ClientChange type and call it using UpdateClient. Create a ClientChange.Empty helper method that will return whether the ClientChange has any changes to perform. Return ErrClientNotFound from clientStore.getClient if the Client's Deleted property is set to true. This also requires us to ignore ErrClientNotFound errors when calling memstore.listClientsByOwner, as they should just be skipped instead of returning an error. Add the postgres type methods needed to implement clientStore. Include postgres as a clientStore if the testing.Short() flag is not set. Generate a new ID for the Client on every run in the tests, now that we can't actually remove it from the database/memstore in code. We really just need a *Store.Reset() function that erases all the data and starts over again, to give the tests a clean execution environment (and so they can clean up after themselves). Add the CREATE TABLE statements for the Clients table and the Endpoints table to sql/postgres_init.sql.
| paddy@31 | 1 package auth |
| paddy@31 | 2 |
| paddy@31 | 3 import ( |
| paddy@113 | 4 "bytes" |
| paddy@116 | 5 "encoding/json" |
| paddy@39 | 6 "fmt" |
| paddy@139 | 7 "github.com/gorilla/mux" |
| paddy@113 | 8 "io/ioutil" |
| paddy@113 | 9 "net/http" |
| paddy@113 | 10 "net/http/httptest" |
| paddy@41 | 11 "net/url" |
| paddy@82 | 12 "sort" |
| paddy@113 | 13 "strings" |
| paddy@31 | 14 "testing" |
| paddy@41 | 15 "time" |
| paddy@31 | 16 |
| paddy@107 | 17 "code.secondbit.org/uuid.hg" |
| paddy@31 | 18 ) |
| paddy@31 | 19 |
| paddy@39 | 20 const ( |
| paddy@39 | 21 clientChangeSecret = 1 << iota |
| paddy@39 | 22 clientChangeOwnerID |
| paddy@39 | 23 clientChangeName |
| paddy@39 | 24 clientChangeLogo |
| paddy@39 | 25 clientChangeWebsite |
| paddy@39 | 26 ) |
| paddy@39 | 27 |
| paddy@151 | 28 func init() { |
| paddy@151 | 29 p, err := NewPostgres("dbname=testdb sslmode=disable") |
| paddy@151 | 30 if err != nil { |
| paddy@151 | 31 panic(err) |
| paddy@151 | 32 } |
| paddy@151 | 33 if !testing.Short() { |
| paddy@151 | 34 clientStores = append(clientStores, &p) |
| paddy@151 | 35 } |
| paddy@151 | 36 } |
| paddy@151 | 37 |
| paddy@57 | 38 var clientStores = []clientStore{NewMemstore()} |
| paddy@31 | 39 |
| paddy@33 | 40 func compareClients(client1, client2 Client) (success bool, field string, val1, val2 interface{}) { |
| paddy@33 | 41 if !client1.ID.Equal(client2.ID) { |
| paddy@33 | 42 return false, "ID", client1.ID, client2.ID |
| paddy@33 | 43 } |
| paddy@33 | 44 if client1.Secret != client2.Secret { |
| paddy@33 | 45 return false, "secret", client1.Secret, client2.Secret |
| paddy@33 | 46 } |
| paddy@33 | 47 if !client1.OwnerID.Equal(client2.OwnerID) { |
| paddy@33 | 48 return false, "owner ID", client1.OwnerID, client2.OwnerID |
| paddy@33 | 49 } |
| paddy@33 | 50 if client1.Name != client2.Name { |
| paddy@33 | 51 return false, "name", client1.Name, client2.Name |
| paddy@33 | 52 } |
| paddy@33 | 53 if client1.Logo != client2.Logo { |
| paddy@33 | 54 return false, "logo", client1.Logo, client2.Logo |
| paddy@33 | 55 } |
| paddy@33 | 56 if client1.Website != client2.Website { |
| paddy@33 | 57 return false, "website", client1.Website, client2.Website |
| paddy@33 | 58 } |
| paddy@41 | 59 if client1.Type != client2.Type { |
| paddy@41 | 60 return false, "type", client1.Type, client2.Type |
| paddy@41 | 61 } |
| paddy@41 | 62 return true, "", nil, nil |
| paddy@41 | 63 } |
| paddy@41 | 64 |
| paddy@41 | 65 func compareEndpoints(endpoint1, endpoint2 Endpoint) (success bool, field string, val1, val2 interface{}) { |
| paddy@41 | 66 if !endpoint1.ID.Equal(endpoint2.ID) { |
| paddy@41 | 67 return false, "ID", endpoint1.ID, endpoint2.ID |
| paddy@41 | 68 } |
| paddy@41 | 69 if !endpoint1.ClientID.Equal(endpoint2.ClientID) { |
| paddy@41 | 70 return false, "OwnerID", endpoint1.ClientID, endpoint2.ClientID |
| paddy@41 | 71 } |
| paddy@41 | 72 if !endpoint1.Added.Equal(endpoint2.Added) { |
| paddy@41 | 73 return false, "Added", endpoint1.Added, endpoint2.Added |
| paddy@41 | 74 } |
| paddy@116 | 75 if endpoint1.URI != endpoint2.URI { |
| paddy@41 | 76 return false, "URI", endpoint1.URI, endpoint2.URI |
| paddy@41 | 77 } |
| paddy@33 | 78 return true, "", nil, nil |
| paddy@33 | 79 } |
| paddy@33 | 80 |
| paddy@31 | 81 func TestClientStoreSuccess(t *testing.T) { |
| paddy@36 | 82 t.Parallel() |
| paddy@31 | 83 client := Client{ |
| paddy@41 | 84 ID: uuid.NewID(), |
| paddy@41 | 85 Secret: "secret", |
| paddy@41 | 86 OwnerID: uuid.NewID(), |
| paddy@41 | 87 Name: "name", |
| paddy@41 | 88 Logo: "logo", |
| paddy@41 | 89 Website: "website", |
| paddy@31 | 90 } |
| paddy@31 | 91 for _, store := range clientStores { |
| paddy@116 | 92 context := Context{clients: store} |
| paddy@116 | 93 err := context.SaveClient(client) |
| paddy@31 | 94 if err != nil { |
| paddy@41 | 95 t.Fatalf("Error saving client to %T: %s", store, err) |
| paddy@31 | 96 } |
| paddy@116 | 97 err = context.SaveClient(client) |
| paddy@33 | 98 if err != ErrClientAlreadyExists { |
| paddy@41 | 99 t.Fatalf("Expected ErrClientAlreadyExists, got %v from %T", err, store) |
| paddy@33 | 100 } |
| paddy@116 | 101 retrieved, err := context.GetClient(client.ID) |
| paddy@31 | 102 if err != nil { |
| paddy@41 | 103 t.Fatalf("Error retrieving client from %T: %s", store, err) |
| paddy@31 | 104 } |
| paddy@33 | 105 success, field, expectation, result := compareClients(client, retrieved) |
| paddy@33 | 106 if !success { |
| paddy@41 | 107 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result) |
| paddy@33 | 108 } |
| paddy@116 | 109 clients, err := context.ListClientsByOwner(client.OwnerID, 25, 0) |
| paddy@31 | 110 if err != nil { |
| paddy@41 | 111 t.Fatalf("Error retrieving clients by owner from %T: %s", store, err) |
| paddy@31 | 112 } |
| paddy@31 | 113 if len(clients) != 1 { |
| paddy@41 | 114 t.Fatalf("Expected 1 client in response from %T, got %+v", store, clients) |
| paddy@31 | 115 } |
| paddy@33 | 116 success, field, expectation, result = compareClients(client, clients[0]) |
| paddy@33 | 117 if !success { |
| paddy@41 | 118 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result) |
| paddy@33 | 119 } |
| paddy@151 | 120 deleted := true |
| paddy@151 | 121 err = context.UpdateClient(client.ID, ClientChange{Deleted: &deleted}) |
| paddy@31 | 122 if err != nil { |
| paddy@41 | 123 t.Fatalf("Error deleting client from %T: %s", store, err) |
| paddy@31 | 124 } |
| paddy@116 | 125 retrieved, err = context.GetClient(client.ID) |
| paddy@31 | 126 if err != ErrClientNotFound { |
| paddy@41 | 127 t.Fatalf("Expected ErrClientNotFound from %T, got %+v and %s", store, retrieved, err) |
| paddy@31 | 128 } |
| paddy@116 | 129 clients, err = context.ListClientsByOwner(client.OwnerID, 25, 0) |
| paddy@31 | 130 if err != nil { |
| paddy@41 | 131 t.Fatalf("Error listing clients by owner from %T: %s", store, err) |
| paddy@31 | 132 } |
| paddy@31 | 133 if len(clients) != 0 { |
| paddy@41 | 134 t.Fatalf("Expected 0 clients in response from %T, got %+v", store, clients) |
| paddy@41 | 135 } |
| paddy@41 | 136 } |
| paddy@41 | 137 } |
| paddy@41 | 138 |
| paddy@41 | 139 func TestEndpointStoreSuccess(t *testing.T) { |
| paddy@41 | 140 t.Parallel() |
| paddy@41 | 141 client := Client{ |
| paddy@41 | 142 ID: uuid.NewID(), |
| paddy@41 | 143 Secret: "secret", |
| paddy@41 | 144 OwnerID: uuid.NewID(), |
| paddy@41 | 145 Name: "name", |
| paddy@41 | 146 Logo: "logo", |
| paddy@41 | 147 Website: "website", |
| paddy@41 | 148 } |
| paddy@41 | 149 endpoint1 := Endpoint{ |
| paddy@41 | 150 ID: uuid.NewID(), |
| paddy@41 | 151 ClientID: client.ID, |
| paddy@149 | 152 Added: time.Now().Round(time.Millisecond), |
| paddy@116 | 153 URI: "https://www.example.com/", |
| paddy@41 | 154 } |
| paddy@41 | 155 endpoint2 := Endpoint{ |
| paddy@41 | 156 ID: uuid.NewID(), |
| paddy@41 | 157 ClientID: client.ID, |
| paddy@149 | 158 Added: time.Now().Round(time.Millisecond), |
| paddy@116 | 159 URI: "https://www.example.com/my/full/path", |
| paddy@41 | 160 } |
| paddy@41 | 161 for _, store := range clientStores { |
| paddy@116 | 162 context := Context{clients: store} |
| paddy@116 | 163 err := context.SaveClient(client) |
| paddy@41 | 164 if err != nil { |
| paddy@41 | 165 t.Fatalf("Error saving client to %T: %s", store, err) |
| paddy@41 | 166 } |
| paddy@151 | 167 err = context.AddEndpoints([]Endpoint{endpoint1}) |
| paddy@41 | 168 if err != nil { |
| paddy@41 | 169 t.Fatalf("Error adding endpoint to client in %T: %s", store, err) |
| paddy@41 | 170 } |
| paddy@116 | 171 endpoints, err := context.ListEndpoints(client.ID, 10, 0) |
| paddy@41 | 172 if err != nil { |
| paddy@41 | 173 t.Fatalf("Error retrieving endpoints from %T: %s", store, err) |
| paddy@41 | 174 } |
| paddy@41 | 175 if len(endpoints) != 1 { |
| paddy@41 | 176 t.Fatalf("Expected %d endpoints, got %+v from %T", 1, endpoints, store) |
| paddy@41 | 177 } |
| paddy@41 | 178 success, field, expectation, result := compareEndpoints(endpoint1, endpoints[0]) |
| paddy@41 | 179 if !success { |
| paddy@41 | 180 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result) |
| paddy@41 | 181 } |
| paddy@151 | 182 err = context.AddEndpoints([]Endpoint{endpoint2}) |
| paddy@41 | 183 if err != nil { |
| paddy@41 | 184 t.Fatalf("Error adding endpoint to client in %T: %s", store, err) |
| paddy@41 | 185 } |
| paddy@116 | 186 endpoints, err = context.ListEndpoints(client.ID, 10, 0) |
| paddy@41 | 187 if err != nil { |
| paddy@41 | 188 t.Fatalf("Error retrieving endpoints from %T: %s", store, err) |
| paddy@41 | 189 } |
| paddy@41 | 190 if len(endpoints) != 2 { |
| paddy@41 | 191 t.Fatalf("Expected %d endpoints, got %+v from %T", 2, endpoints, store) |
| paddy@41 | 192 } |
| paddy@41 | 193 sortedEnd := sortedEndpoints(endpoints) |
| paddy@41 | 194 sort.Sort(sortedEnd) |
| paddy@41 | 195 endpoints = []Endpoint(sortedEnd) |
| paddy@41 | 196 success, field, expectation, result = compareEndpoints(endpoint1, endpoints[0]) |
| paddy@41 | 197 if !success { |
| paddy@41 | 198 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result) |
| paddy@41 | 199 } |
| paddy@41 | 200 success, field, expectation, result = compareEndpoints(endpoint2, endpoints[1]) |
| paddy@41 | 201 if !success { |
| paddy@41 | 202 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result) |
| paddy@41 | 203 } |
| paddy@116 | 204 err = context.RemoveEndpoint(client.ID, endpoint1.ID) |
| paddy@41 | 205 if err != nil { |
| paddy@41 | 206 t.Fatalf("Error removing endpoint from client in %T: %s", store, err) |
| paddy@41 | 207 } |
| paddy@116 | 208 endpoints, err = context.ListEndpoints(client.ID, 10, 0) |
| paddy@41 | 209 if err != nil { |
| paddy@41 | 210 t.Fatalf("Error listing endpoints in %T: %s", store, err) |
| paddy@41 | 211 } |
| paddy@41 | 212 if len(endpoints) != 1 { |
| paddy@41 | 213 t.Fatalf("Expected %d endpoints, got %+v from %T", 1, endpoints, store) |
| paddy@41 | 214 } |
| paddy@41 | 215 success, field, expectation, result = compareEndpoints(endpoint2, endpoints[0]) |
| paddy@41 | 216 if !success { |
| paddy@41 | 217 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result) |
| paddy@41 | 218 } |
| paddy@116 | 219 err = context.RemoveEndpoint(client.ID, endpoint2.ID) |
| paddy@41 | 220 if err != nil { |
| paddy@41 | 221 t.Fatalf("Error removing endpoint from client in %T: %s", store, err) |
| paddy@41 | 222 } |
| paddy@116 | 223 endpoints, err = context.ListEndpoints(client.ID, 10, 0) |
| paddy@41 | 224 if err != nil { |
| paddy@41 | 225 t.Fatalf("Error listing endpoints in %T: %s", store, err) |
| paddy@41 | 226 } |
| paddy@41 | 227 if len(endpoints) != 0 { |
| paddy@41 | 228 t.Fatalf("Expected %d endpoints, got %+v from %T", 0, endpoints, store) |
| paddy@31 | 229 } |
| paddy@31 | 230 } |
| paddy@31 | 231 } |
| paddy@39 | 232 |
| paddy@39 | 233 func TestClientUpdates(t *testing.T) { |
| paddy@39 | 234 t.Parallel() |
| paddy@41 | 235 variations := 1 << 5 |
| paddy@39 | 236 client := Client{ |
| paddy@41 | 237 ID: uuid.NewID(), |
| paddy@41 | 238 Secret: "secret", |
| paddy@41 | 239 OwnerID: uuid.NewID(), |
| paddy@41 | 240 Name: "name", |
| paddy@41 | 241 Logo: "logo", |
| paddy@41 | 242 Website: "website", |
| paddy@39 | 243 } |
| paddy@39 | 244 for i := 0; i < variations; i++ { |
| paddy@41 | 245 var secret, name, logo, website string |
| paddy@39 | 246 change := ClientChange{} |
| paddy@151 | 247 client.ID = uuid.NewID() |
| paddy@39 | 248 expectation := client |
| paddy@39 | 249 result := client |
| paddy@39 | 250 if i&clientChangeSecret != 0 { |
| paddy@39 | 251 secret = fmt.Sprintf("secret-%d", i) |
| paddy@39 | 252 change.Secret = &secret |
| paddy@39 | 253 expectation.Secret = secret |
| paddy@39 | 254 } |
| paddy@39 | 255 if i&clientChangeOwnerID != 0 { |
| paddy@39 | 256 change.OwnerID = uuid.NewID() |
| paddy@39 | 257 expectation.OwnerID = change.OwnerID |
| paddy@39 | 258 } |
| paddy@39 | 259 if i&clientChangeName != 0 { |
| paddy@39 | 260 name = fmt.Sprintf("name-%d", i) |
| paddy@39 | 261 change.Name = &name |
| paddy@39 | 262 expectation.Name = name |
| paddy@39 | 263 } |
| paddy@39 | 264 if i&clientChangeLogo != 0 { |
| paddy@39 | 265 logo = fmt.Sprintf("logo-%d", i) |
| paddy@39 | 266 change.Logo = &logo |
| paddy@39 | 267 expectation.Logo = logo |
| paddy@39 | 268 } |
| paddy@39 | 269 if i&clientChangeWebsite != 0 { |
| paddy@39 | 270 website = fmt.Sprintf("website-%d", i) |
| paddy@39 | 271 change.Website = &website |
| paddy@39 | 272 expectation.Website = website |
| paddy@39 | 273 } |
| paddy@39 | 274 result.ApplyChange(change) |
| paddy@39 | 275 match, field, expected, got := compareClients(expectation, result) |
| paddy@39 | 276 if !match { |
| paddy@41 | 277 t.Fatalf("Expected field `%s` to be `%v`, got `%v`", field, expected, got) |
| paddy@39 | 278 } |
| paddy@39 | 279 for _, store := range clientStores { |
| paddy@116 | 280 context := Context{clients: store} |
| paddy@116 | 281 err := context.SaveClient(client) |
| paddy@39 | 282 if err != nil { |
| paddy@41 | 283 t.Fatalf("Error saving client in %T: %s", store, err) |
| paddy@39 | 284 } |
| paddy@116 | 285 err = context.UpdateClient(client.ID, change) |
| paddy@39 | 286 if err != nil { |
| paddy@41 | 287 t.Fatalf("Error updating client in %T: %s", store, err) |
| paddy@39 | 288 } |
| paddy@116 | 289 retrieved, err := context.GetClient(client.ID) |
| paddy@39 | 290 if err != nil { |
| paddy@116 | 291 t.Fatalf("Error getting client from %T: %s", store, err) |
| paddy@39 | 292 } |
| paddy@39 | 293 match, field, expected, got = compareClients(expectation, retrieved) |
| paddy@39 | 294 if !match { |
| paddy@41 | 295 t.Fatalf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) |
| paddy@39 | 296 } |
| paddy@151 | 297 deleted := true |
| paddy@151 | 298 err = context.UpdateClient(client.ID, ClientChange{Deleted: &deleted}) |
| paddy@39 | 299 if err != nil { |
| paddy@41 | 300 t.Fatalf("Error deleting client from %T: %s", store, err) |
| paddy@39 | 301 } |
| paddy@39 | 302 } |
| paddy@39 | 303 } |
| paddy@39 | 304 } |
| paddy@41 | 305 |
| paddy@41 | 306 func TestClientEndpointChecks(t *testing.T) { |
| paddy@41 | 307 t.Parallel() |
| paddy@41 | 308 client := Client{ |
| paddy@41 | 309 ID: uuid.NewID(), |
| paddy@41 | 310 Secret: "secret", |
| paddy@41 | 311 OwnerID: uuid.NewID(), |
| paddy@41 | 312 Name: "name", |
| paddy@41 | 313 Logo: "logo", |
| paddy@41 | 314 Website: "website", |
| paddy@41 | 315 } |
| paddy@41 | 316 endpoint1 := Endpoint{ |
| paddy@41 | 317 ID: uuid.NewID(), |
| paddy@41 | 318 ClientID: client.ID, |
| paddy@149 | 319 Added: time.Now().Round(time.Millisecond), |
| paddy@116 | 320 URI: "https://www.example.com/first", |
| paddy@41 | 321 } |
| paddy@41 | 322 endpoint2 := Endpoint{ |
| paddy@41 | 323 ID: uuid.NewID(), |
| paddy@41 | 324 ClientID: client.ID, |
| paddy@149 | 325 Added: time.Now().Round(time.Millisecond), |
| paddy@116 | 326 URI: "https://www.example.com/my/full/path", |
| paddy@41 | 327 } |
| paddy@41 | 328 candidates := map[string]bool{ |
| paddy@41 | 329 "https://www.example.com/": false, |
| paddy@41 | 330 "https://www.example.com/first": true, |
| paddy@58 | 331 "https://www.example.com/first/extra/path": false, |
| paddy@41 | 332 "https://www.example.com/my": false, |
| paddy@41 | 333 "https://www.example.com/my/full/path": true, |
| paddy@41 | 334 } |
| paddy@41 | 335 for _, store := range clientStores { |
| paddy@116 | 336 context := Context{clients: store} |
| paddy@116 | 337 err := context.SaveClient(client) |
| paddy@41 | 338 if err != nil { |
| paddy@41 | 339 t.Fatalf("Error saving client in %T: %s", store, err) |
| paddy@41 | 340 } |
| paddy@151 | 341 err = context.AddEndpoints([]Endpoint{endpoint1}) |
| paddy@41 | 342 if err != nil { |
| paddy@41 | 343 t.Fatalf("Error saving endpoint in %T: %s", store, err) |
| paddy@41 | 344 } |
| paddy@151 | 345 err = context.AddEndpoints([]Endpoint{endpoint2}) |
| paddy@41 | 346 if err != nil { |
| paddy@41 | 347 t.Fatalf("Error saving endpoint in %T: %s", store, err) |
| paddy@41 | 348 } |
| paddy@41 | 349 for candidate, expectation := range candidates { |
| paddy@116 | 350 result, err := context.CheckEndpoint(client.ID, candidate) |
| paddy@54 | 351 if err != nil { |
| paddy@54 | 352 t.Fatalf("Error checking endpoint %s in %T: %s", candidate, store, err) |
| paddy@54 | 353 } |
| paddy@54 | 354 if result != expectation { |
| paddy@54 | 355 expectStr := "no" |
| paddy@54 | 356 resultStr := "a" |
| paddy@54 | 357 if expectation { |
| paddy@54 | 358 expectStr = "a" |
| paddy@54 | 359 resultStr = "no" |
| paddy@54 | 360 } |
| paddy@54 | 361 t.Errorf("Expected %s match for %s in %T, got %s match", expectStr, candidate, store, resultStr) |
| paddy@54 | 362 } |
| paddy@54 | 363 } |
| paddy@54 | 364 } |
| paddy@54 | 365 } |
| paddy@54 | 366 |
| paddy@54 | 367 func TestClientEndpointChecksStrict(t *testing.T) { |
| paddy@54 | 368 t.Parallel() |
| paddy@54 | 369 client := Client{ |
| paddy@54 | 370 ID: uuid.NewID(), |
| paddy@54 | 371 Secret: "secret", |
| paddy@54 | 372 OwnerID: uuid.NewID(), |
| paddy@54 | 373 Name: "name", |
| paddy@54 | 374 Logo: "logo", |
| paddy@54 | 375 Website: "website", |
| paddy@54 | 376 } |
| paddy@54 | 377 endpoint1 := Endpoint{ |
| paddy@54 | 378 ID: uuid.NewID(), |
| paddy@54 | 379 ClientID: client.ID, |
| paddy@149 | 380 Added: time.Now().Round(time.Millisecond), |
| paddy@116 | 381 URI: "https://www.example.com/first", |
| paddy@54 | 382 } |
| paddy@54 | 383 endpoint2 := Endpoint{ |
| paddy@54 | 384 ID: uuid.NewID(), |
| paddy@54 | 385 ClientID: client.ID, |
| paddy@149 | 386 Added: time.Now().Round(time.Millisecond), |
| paddy@116 | 387 URI: "https://www.example.com/my/full/path", |
| paddy@54 | 388 } |
| paddy@54 | 389 candidates := map[string]bool{ |
| paddy@54 | 390 "https://www.example.com/": false, |
| paddy@54 | 391 "https://www.example.com/first": true, |
| paddy@54 | 392 "https://www.example.com/first/extra/path": false, |
| paddy@54 | 393 "https://www.example.com/my": false, |
| paddy@54 | 394 "https://www.example.com/my/full/path": true, |
| paddy@54 | 395 } |
| paddy@54 | 396 for _, store := range clientStores { |
| paddy@116 | 397 context := Context{clients: store} |
| paddy@116 | 398 err := context.SaveClient(client) |
| paddy@54 | 399 if err != nil { |
| paddy@54 | 400 t.Fatalf("Error saving client in %T: %s", store, err) |
| paddy@54 | 401 } |
| paddy@151 | 402 err = context.AddEndpoints([]Endpoint{endpoint1}) |
| paddy@54 | 403 if err != nil { |
| paddy@54 | 404 t.Fatalf("Error saving endpoint in %T: %s", store, err) |
| paddy@54 | 405 } |
| paddy@151 | 406 err = context.AddEndpoints([]Endpoint{endpoint2}) |
| paddy@54 | 407 if err != nil { |
| paddy@54 | 408 t.Fatalf("Error saving endpoint in %T: %s", store, err) |
| paddy@54 | 409 } |
| paddy@54 | 410 for candidate, expectation := range candidates { |
| paddy@116 | 411 result, err := context.CheckEndpoint(client.ID, candidate) |
| paddy@41 | 412 if err != nil { |
| paddy@41 | 413 t.Fatalf("Error checking endpoint %s in %T: %s", candidate, store, err) |
| paddy@41 | 414 } |
| paddy@41 | 415 if result != expectation { |
| paddy@41 | 416 expectStr := "no" |
| paddy@41 | 417 resultStr := "a" |
| paddy@41 | 418 if expectation { |
| paddy@41 | 419 expectStr = "a" |
| paddy@41 | 420 resultStr = "no" |
| paddy@41 | 421 } |
| paddy@41 | 422 t.Errorf("Expected %s match for %s in %T, got %s match", expectStr, candidate, store, resultStr) |
| paddy@41 | 423 } |
| paddy@41 | 424 } |
| paddy@41 | 425 } |
| paddy@41 | 426 } |
| paddy@43 | 427 |
| paddy@43 | 428 func TestClientChangeValidation(t *testing.T) { |
| paddy@43 | 429 t.Parallel() |
| paddy@43 | 430 change := ClientChange{} |
| paddy@133 | 431 if err := change.Validate(); err[0] != ErrEmptyChange { |
| paddy@43 | 432 t.Errorf("Expected %s to give an error of %s, gave %s", "empty change", ErrEmptyChange, err) |
| paddy@43 | 433 } |
| paddy@133 | 434 names := map[string][]error{ |
| paddy@133 | 435 "a": []error{ErrClientNameTooShort}, |
| paddy@133 | 436 "ab": []error{}, |
| paddy@133 | 437 "abc": []error{}, |
| paddy@133 | 438 "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopq": []error{ErrClientNameTooLong}, |
| paddy@43 | 439 } |
| paddy@43 | 440 for name, expectation := range names { |
| paddy@43 | 441 change = ClientChange{Name: &name} |
| paddy@133 | 442 errs := change.Validate() |
| paddy@133 | 443 if len(errs) != len(expectation) { |
| paddy@133 | 444 t.Errorf("Expected %s to give %d errors, gave %d", name, len(expectation), len(errs)) |
| paddy@133 | 445 t.Logf("%+v", errs) |
| paddy@133 | 446 } |
| paddy@133 | 447 for pos, err := range errs { |
| paddy@133 | 448 if err != expectation[pos] { |
| paddy@133 | 449 t.Errorf("Expected %s to give an error of %s in position %d, gave %s", name, expectation[pos], pos, err) |
| paddy@133 | 450 } |
| paddy@43 | 451 } |
| paddy@43 | 452 } |
| paddy@43 | 453 longPath := "" |
| paddy@43 | 454 for i := 0; i < 1025; i++ { |
| paddy@43 | 455 longPath = fmt.Sprintf("%s%d", longPath, i) |
| paddy@43 | 456 } |
| paddy@133 | 457 logos := map[string][]error{ |
| paddy@133 | 458 "https://www.example.com/" + longPath: []error{ErrClientLogoTooLong}, |
| paddy@133 | 459 "https://www.example.com/ab": []error{}, |
| paddy@133 | 460 "www.example.com/ab": []error{ErrClientLogoNotURL}, |
| paddy@133 | 461 "test": []error{ErrClientLogoNotURL}, |
| paddy@133 | 462 "": []error{}, |
| paddy@43 | 463 } |
| paddy@43 | 464 for logo, expectation := range logos { |
| paddy@43 | 465 change = ClientChange{Logo: &logo} |
| paddy@133 | 466 errs := change.Validate() |
| paddy@133 | 467 if len(errs) != len(expectation) { |
| paddy@133 | 468 t.Errorf("Expected %s to give %d errors, gave %d", logo, len(expectation), len(errs)) |
| paddy@133 | 469 } |
| paddy@133 | 470 for pos, err := range errs { |
| paddy@133 | 471 if err != expectation[pos] { |
| paddy@133 | 472 t.Errorf("Expected %s to give an error of %s in positiong %d, gave %s", logo, expectation[pos], pos, err) |
| paddy@133 | 473 } |
| paddy@43 | 474 } |
| paddy@43 | 475 } |
| paddy@133 | 476 websites := map[string][]error{ |
| paddy@133 | 477 "https://www.example.com/" + longPath: []error{ErrClientWebsiteTooLong}, |
| paddy@133 | 478 "https://www.example.com/ab": []error{}, |
| paddy@133 | 479 "www.example.com/ab": []error{ErrClientWebsiteNotURL}, |
| paddy@133 | 480 "test": []error{ErrClientWebsiteNotURL}, |
| paddy@133 | 481 "": []error{}, |
| paddy@43 | 482 } |
| paddy@43 | 483 for website, expectation := range websites { |
| paddy@43 | 484 change = ClientChange{Website: &website} |
| paddy@133 | 485 errs := change.Validate() |
| paddy@133 | 486 if len(errs) != len(expectation) { |
| paddy@133 | 487 t.Errorf("Expected %s to give %d errors, gave %d", website, len(expectation), len(errs)) |
| paddy@133 | 488 } |
| paddy@133 | 489 for pos, err := range errs { |
| paddy@133 | 490 if err != expectation[pos] { |
| paddy@133 | 491 t.Errorf("Expected %s to give an error of %s in position %d, gave %s", website, expectation[pos], pos, err) |
| paddy@133 | 492 } |
| paddy@43 | 493 } |
| paddy@43 | 494 } |
| paddy@43 | 495 } |
| paddy@113 | 496 |
| paddy@129 | 497 func TestGetClientAuth(t *testing.T) { |
| paddy@129 | 498 t.Parallel() |
| paddy@129 | 499 type clientAuthRequest struct { |
| paddy@129 | 500 username string |
| paddy@129 | 501 pass string |
| paddy@129 | 502 clientID string |
| paddy@129 | 503 allowPublic bool |
| paddy@129 | 504 expectedClientID uuid.ID |
| paddy@129 | 505 expectedClientSecret string |
| paddy@129 | 506 expectedValid bool |
| paddy@129 | 507 expectedCode int |
| paddy@129 | 508 expectedBody string |
| paddy@129 | 509 expectAuthenticateHeader bool |
| paddy@129 | 510 } |
| paddy@129 | 511 id := uuid.NewID() |
| paddy@129 | 512 tests := []clientAuthRequest{ |
| paddy@129 | 513 {"", "", "", false, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false}, |
| paddy@129 | 514 {"", "", "", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false}, |
| paddy@129 | 515 {"", "no clientID set", "", false, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 516 {"", "no clientID set", "", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 517 {"not an actual id", "invalid client ID set", "", false, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 518 {"not an actual id", "invalid client ID set", "", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 519 {"", "", "not an actual id", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false}, |
| paddy@129 | 520 {id.String(), "secret", "", true, id, "secret", true, http.StatusOK, "", false}, |
| paddy@129 | 521 {id.String(), "secret", "", false, id, "secret", true, http.StatusOK, "", false}, |
| paddy@129 | 522 {"", "", id.String(), true, id, "", true, http.StatusOK, "", false}, |
| paddy@129 | 523 {"", "", id.String(), false, nil, "", false, http.StatusBadRequest, `{"error":"unauthorized_client"}`, false}, |
| paddy@129 | 524 } |
| paddy@129 | 525 for pos, test := range tests { |
| paddy@129 | 526 t.Logf("Running test #%d, with request %+v", pos, test) |
| paddy@129 | 527 w := httptest.NewRecorder() |
| paddy@129 | 528 r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@129 | 529 if err != nil { |
| paddy@129 | 530 t.Fatal("Can't build request:", err) |
| paddy@129 | 531 } |
| paddy@129 | 532 if test.username != "" || test.pass != "" { |
| paddy@129 | 533 r.SetBasicAuth(test.username, test.pass) |
| paddy@129 | 534 } |
| paddy@129 | 535 r.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
| paddy@129 | 536 params := url.Values{} |
| paddy@129 | 537 params.Set("client_id", test.clientID) |
| paddy@129 | 538 body := bytes.NewBufferString(params.Encode()) |
| paddy@129 | 539 r.Body = ioutil.NopCloser(body) |
| paddy@129 | 540 respID, respSecret, success := getClientAuth(w, r, test.allowPublic) |
| paddy@129 | 541 if (respID == nil && test.expectedClientID != nil) || (respID != nil && test.expectedClientID == nil) || !respID.Equal(test.expectedClientID) { |
| paddy@129 | 542 t.Errorf("Expected response ID to be %v, got %v", test.expectedClientID, respID) |
| paddy@129 | 543 } |
| paddy@129 | 544 if test.expectedClientSecret != respSecret { |
| paddy@129 | 545 t.Errorf("Expected response secret to be '%s', got '%s'", test.expectedClientSecret, respSecret) |
| paddy@129 | 546 } |
| paddy@129 | 547 if test.expectedValid != success { |
| paddy@129 | 548 t.Errorf("Expected success result to be %v, got %v", test.expectedValid, success) |
| paddy@129 | 549 } |
| paddy@129 | 550 if test.expectedCode != w.Code { |
| paddy@129 | 551 t.Errorf("Expected response code to be %d, got %d", test.expectedCode, w.Code) |
| paddy@129 | 552 } |
| paddy@129 | 553 if test.expectedBody != strings.TrimSpace(w.Body.String()) { |
| paddy@129 | 554 t.Errorf("Expected body to be '%s', got '%s'", test.expectedBody, strings.TrimSpace(w.Body.String())) |
| paddy@129 | 555 } |
| paddy@129 | 556 if test.expectAuthenticateHeader && w.Header().Get("WWW-Authenticate") != "Basic" { |
| paddy@129 | 557 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) |
| paddy@129 | 558 } |
| paddy@129 | 559 } |
| paddy@129 | 560 } |
| paddy@129 | 561 |
| paddy@113 | 562 func TestVerifyClient(t *testing.T) { |
| paddy@113 | 563 t.Parallel() |
| paddy@129 | 564 type verifyClientRequest struct { |
| paddy@129 | 565 username string |
| paddy@129 | 566 pass string |
| paddy@129 | 567 clientID string |
| paddy@129 | 568 allowPublic bool |
| paddy@129 | 569 expectedClientID uuid.ID |
| paddy@129 | 570 expectedValid bool |
| paddy@129 | 571 expectedCode int |
| paddy@129 | 572 expectedBody string |
| paddy@129 | 573 expectAuthenticateHeader bool |
| paddy@129 | 574 } |
| paddy@113 | 575 memstore := NewMemstore() |
| paddy@113 | 576 context := Context{ |
| paddy@113 | 577 clients: memstore, |
| paddy@113 | 578 } |
| paddy@113 | 579 client := Client{ |
| paddy@113 | 580 ID: uuid.NewID(), |
| paddy@113 | 581 Secret: "super secret!", |
| paddy@113 | 582 OwnerID: uuid.NewID(), |
| paddy@113 | 583 Name: "My test client", |
| paddy@113 | 584 Logo: "https://secondbit.org/logo.png", |
| paddy@113 | 585 Website: "https://secondbit.org/", |
| paddy@113 | 586 Type: "confidential", |
| paddy@113 | 587 } |
| paddy@113 | 588 err := context.SaveClient(client) |
| paddy@113 | 589 if err != nil { |
| paddy@113 | 590 t.Fatal("Could not save client:", err) |
| paddy@113 | 591 } |
| paddy@113 | 592 publicClient := Client{ |
| paddy@113 | 593 ID: uuid.NewID(), |
| paddy@113 | 594 Secret: "", |
| paddy@113 | 595 OwnerID: uuid.NewID(), |
| paddy@113 | 596 Name: "A public client", |
| paddy@113 | 597 Logo: "https://secondbit.org/logo.png", |
| paddy@113 | 598 Website: "https://secondbit.org/", |
| paddy@113 | 599 Type: "public", |
| paddy@113 | 600 } |
| paddy@113 | 601 err = context.SaveClient(publicClient) |
| paddy@113 | 602 if err != nil { |
| paddy@113 | 603 t.Fatal("Could not save client:", err) |
| paddy@113 | 604 } |
| paddy@129 | 605 id := uuid.NewID() |
| paddy@129 | 606 tests := []verifyClientRequest{ |
| paddy@129 | 607 {"", "", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false}, |
| paddy@129 | 608 {"", "", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false}, |
| paddy@129 | 609 {"", "no clientID set", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 610 {"", "no clientID set", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 611 {"not an actual id", "invalid client ID set", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 612 {"not an actual id", "invalid client ID set", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 613 {id.String(), "unsaved client ID set", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 614 {id.String(), "unsaved client ID set", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 615 {client.ID.String(), "wrong secret", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 616 {client.ID.String(), "wrong secret", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 617 {"", "", "not an actual id", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false}, |
| paddy@129 | 618 {"", "", id.String(), true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false}, |
| paddy@129 | 619 {client.ID.String(), client.Secret, "", true, client.ID, true, http.StatusOK, "", false}, |
| paddy@129 | 620 {client.ID.String(), client.Secret, "", false, client.ID, true, http.StatusOK, "", false}, |
| paddy@129 | 621 {"", "", publicClient.ID.String(), true, publicClient.ID, true, http.StatusOK, "", false}, |
| paddy@129 | 622 {"", "", publicClient.ID.String(), false, nil, false, http.StatusBadRequest, `{"error":"unauthorized_client"}`, false}, |
| paddy@113 | 623 } |
| paddy@113 | 624 |
| paddy@129 | 625 for pos, test := range tests { |
| paddy@129 | 626 t.Logf("Running test #%d, with request %+v", pos, test) |
| paddy@129 | 627 w := httptest.NewRecorder() |
| paddy@129 | 628 r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@129 | 629 if err != nil { |
| paddy@129 | 630 t.Fatal("Can't build request:", err) |
| paddy@129 | 631 } |
| paddy@129 | 632 if test.username != "" || test.pass != "" { |
| paddy@129 | 633 r.SetBasicAuth(test.username, test.pass) |
| paddy@129 | 634 } |
| paddy@129 | 635 r.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
| paddy@129 | 636 params := url.Values{} |
| paddy@129 | 637 params.Set("client_id", test.clientID) |
| paddy@129 | 638 body := bytes.NewBufferString(params.Encode()) |
| paddy@129 | 639 r.Body = ioutil.NopCloser(body) |
| paddy@129 | 640 respID, success := verifyClient(w, r, test.allowPublic, context) |
| paddy@129 | 641 if (respID == nil && test.expectedClientID != nil) || (respID != nil && test.expectedClientID == nil) || !respID.Equal(test.expectedClientID) { |
| paddy@129 | 642 t.Errorf("Expected response ID to be %v, got %v", test.expectedClientID, respID) |
| paddy@129 | 643 } |
| paddy@129 | 644 if test.expectedValid != success { |
| paddy@129 | 645 t.Errorf("Expected success result to be %v, got %v", test.expectedValid, success) |
| paddy@129 | 646 } |
| paddy@129 | 647 if test.expectedCode != w.Code { |
| paddy@129 | 648 t.Errorf("Expected response code to be %d, got %d", test.expectedCode, w.Code) |
| paddy@129 | 649 } |
| paddy@129 | 650 if test.expectedBody != strings.TrimSpace(w.Body.String()) { |
| paddy@129 | 651 t.Errorf("Expected body to be '%s', got '%s'", test.expectedBody, strings.TrimSpace(w.Body.String())) |
| paddy@129 | 652 } |
| paddy@129 | 653 if test.expectAuthenticateHeader && w.Header().Get("WWW-Authenticate") != "Basic" { |
| paddy@129 | 654 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) |
| paddy@129 | 655 } |
| paddy@113 | 656 } |
| paddy@113 | 657 } |
| paddy@116 | 658 |
| paddy@116 | 659 func TestCreateClientHandler(t *testing.T) { |
| paddy@116 | 660 t.Parallel() |
| paddy@116 | 661 memstore := NewMemstore() |
| paddy@116 | 662 c := Context{ |
| paddy@116 | 663 clients: memstore, |
| paddy@116 | 664 profiles: memstore, |
| paddy@116 | 665 } |
| paddy@116 | 666 w := httptest.NewRecorder() |
| paddy@116 | 667 r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/clients", nil) |
| paddy@116 | 668 if err != nil { |
| paddy@116 | 669 t.Fatal("Can't build request:", err) |
| paddy@116 | 670 } |
| paddy@116 | 671 r.Header.Set("Content-Type", "application/json") |
| paddy@116 | 672 CreateClientHandler(w, r, c) |
| paddy@116 | 673 if w.Code != http.StatusUnauthorized { |
| paddy@116 | 674 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code) |
| paddy@116 | 675 } |
| paddy@116 | 676 expected := `{"errors":[{"error":"access_denied"}]}` |
| paddy@116 | 677 result := strings.TrimSpace(w.Body.String()) |
| paddy@116 | 678 if result != expected { |
| paddy@116 | 679 t.Errorf("Expected response to be `%s`, got `%s`", expected, result) |
| paddy@116 | 680 } |
| paddy@116 | 681 w = httptest.NewRecorder() |
| paddy@116 | 682 r.Header.Set("Authorization", "Not basic at all...") |
| paddy@116 | 683 CreateClientHandler(w, r, c) |
| paddy@116 | 684 if w.Code != http.StatusUnauthorized { |
| paddy@116 | 685 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code) |
| paddy@116 | 686 } |
| paddy@116 | 687 expected = `{"errors":[{"error":"access_denied"}]}` |
| paddy@116 | 688 result = strings.TrimSpace(w.Body.String()) |
| paddy@116 | 689 if result != expected { |
| paddy@116 | 690 t.Errorf("Expected response to be `%s`, got `%s`", expected, result) |
| paddy@116 | 691 } |
| paddy@116 | 692 w = httptest.NewRecorder() |
| paddy@116 | 693 r.Header.Set("Authorization", "Basic TotallyNotBase64Encoded") |
| paddy@116 | 694 CreateClientHandler(w, r, c) |
| paddy@116 | 695 if w.Code != http.StatusUnauthorized { |
| paddy@116 | 696 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code) |
| paddy@116 | 697 } |
| paddy@116 | 698 expected = `{"errors":[{"error":"access_denied"}]}` |
| paddy@116 | 699 result = strings.TrimSpace(w.Body.String()) |
| paddy@116 | 700 if result != expected { |
| paddy@116 | 701 t.Errorf("Expected response to be `%s`, got `%s`", expected, result) |
| paddy@116 | 702 } |
| paddy@116 | 703 w = httptest.NewRecorder() |
| paddy@116 | 704 r.Header.Set("Authorization", "Basic dGhpc2hhc25vY29sb24=") |
| paddy@116 | 705 CreateClientHandler(w, r, c) |
| paddy@116 | 706 if w.Code != http.StatusUnauthorized { |
| paddy@116 | 707 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code) |
| paddy@116 | 708 } |
| paddy@116 | 709 expected = `{"errors":[{"error":"access_denied"}]}` |
| paddy@116 | 710 result = strings.TrimSpace(w.Body.String()) |
| paddy@116 | 711 if result != expected { |
| paddy@116 | 712 t.Errorf("Expected response to be `%s`, got `%s`", expected, result) |
| paddy@116 | 713 } |
| paddy@116 | 714 profile := Profile{ |
| paddy@116 | 715 ID: uuid.NewID(), |
| paddy@116 | 716 Name: "Test User", |
| paddy@116 | 717 Passphrase: "f3a4ac4f1d657b2e6e776d24213e39406d50a87a52691a2a78891425af1271d0", |
| paddy@116 | 718 Iterations: 1, |
| paddy@116 | 719 Salt: "d82d92cfa8bfb5a08270ebbf39a3710d24b352b937fcc8959ebcb40384cc616b", |
| paddy@116 | 720 PassphraseScheme: 1, |
| paddy@116 | 721 Compromised: false, |
| paddy@116 | 722 LockedUntil: time.Time{}, |
| paddy@116 | 723 PassphraseReset: "", |
| paddy@116 | 724 PassphraseResetCreated: time.Time{}, |
| paddy@149 | 725 Created: time.Now().Round(time.Millisecond), |
| paddy@116 | 726 LastSeen: time.Time{}, |
| paddy@116 | 727 } |
| paddy@116 | 728 login := Login{ |
| paddy@116 | 729 Type: "email", |
| paddy@116 | 730 Value: "test@example.com", |
| paddy@116 | 731 ProfileID: profile.ID, |
| paddy@149 | 732 Created: time.Now().Round(time.Millisecond), |
| paddy@116 | 733 LastUsed: time.Time{}, |
| paddy@116 | 734 } |
| paddy@116 | 735 w = httptest.NewRecorder() |
| paddy@116 | 736 r.SetBasicAuth("test@example.com", "mysecurepassphrase") |
| paddy@116 | 737 CreateClientHandler(w, r, c) |
| paddy@116 | 738 if w.Code != http.StatusUnauthorized { |
| paddy@116 | 739 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code) |
| paddy@116 | 740 } |
| paddy@116 | 741 expected = `{"errors":[{"error":"access_denied"}]}` |
| paddy@116 | 742 result = strings.TrimSpace(w.Body.String()) |
| paddy@116 | 743 if result != expected { |
| paddy@116 | 744 t.Errorf("Expected response to be `%s`, got `%s`", expected, result) |
| paddy@116 | 745 } |
| paddy@116 | 746 err = c.SaveProfile(profile) |
| paddy@116 | 747 if err != nil { |
| paddy@116 | 748 t.Error("Error saving profile:", err) |
| paddy@116 | 749 } |
| paddy@116 | 750 err = c.AddLogin(login) |
| paddy@116 | 751 if err != nil { |
| paddy@116 | 752 t.Error("Error adding login:", err) |
| paddy@116 | 753 } |
| paddy@116 | 754 r.SetBasicAuth("test@example.com", "mysecurepassphrase") |
| paddy@116 | 755 type testStruct struct { |
| paddy@116 | 756 request string |
| paddy@116 | 757 code int |
| paddy@116 | 758 resp response |
| paddy@116 | 759 } |
| paddy@116 | 760 tests := []testStruct{ |
| paddy@116 | 761 {``, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidFormat, Field: "/"}}}}, |
| paddy@116 | 762 {`{}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/type"}, {Slug: requestErrMissing, Field: "/name"}}}}, |
| paddy@116 | 763 {`{"type":"notarealtype"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrMissing, Field: "/name"}}}}, |
| paddy@116 | 764 {`{"type":"notarealtype","name":"myreallylongnameislongerthatthemaximumnamelength"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrOverflow, Field: "/name"}}}}, |
| paddy@116 | 765 {`{"type":"notarealtype","name":"a"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrInsufficient, Field: "/name"}}}}, |
| paddy@116 | 766 {`{"type":"public"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/name"}}}}, |
| paddy@116 | 767 {`{"type":"public","name":"myreallylongnameislongerthatthemaximumnamelength"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrOverflow, Field: "/name"}}}}, |
| paddy@116 | 768 {`{"type":"public","name":"a"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInsufficient, Field: "/name"}}}}, |
| paddy@116 | 769 {`{"name":"My Client"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/type"}}}}, |
| paddy@116 | 770 {`{"type":"notarealtype","name":"My Client"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}}}}, |
| paddy@116 | 771 {`{"type":"public","name":"My Client"}`, http.StatusCreated, response{Clients: []Client{{Name: "My Client", OwnerID: profile.ID, Type: "public"}}}}, |
| paddy@116 | 772 {`{"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"}}}}, |
| paddy@116 | 773 {`{"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"}}}}, |
| paddy@116 | 774 {`{"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"}}}}, |
| paddy@126 | 775 {`{"type":"confidential","name":"Secret Client", "endpoints": ["https://secondbit.org"]}`, http.StatusCreated, response{Clients: []Client{{Name: "Secret Client", OwnerID: profile.ID, Type: "confidential"}}, Endpoints: []Endpoint{{URI: "https://secondbit.org"}}}}, |
| paddy@116 | 776 } |
| paddy@116 | 777 for pos, test := range tests { |
| paddy@116 | 778 t.Logf("Test #%d: `%s`", pos, test.request) |
| paddy@116 | 779 w = httptest.NewRecorder() |
| paddy@116 | 780 body := bytes.NewBufferString(test.request) |
| paddy@116 | 781 r.Body = ioutil.NopCloser(body) |
| paddy@116 | 782 CreateClientHandler(w, r, c) |
| paddy@116 | 783 if w.Code != test.code { |
| paddy@116 | 784 t.Errorf("Expected response code to be %d, got %d", test.code, w.Code) |
| paddy@116 | 785 } |
| paddy@116 | 786 t.Logf("Response: %s", w.Body.String()) |
| paddy@116 | 787 var res response |
| paddy@116 | 788 err = json.Unmarshal(w.Body.Bytes(), &res) |
| paddy@116 | 789 if err != nil { |
| paddy@116 | 790 t.Error("Unexpected error unmarshalling response:", err) |
| paddy@116 | 791 } |
| paddy@126 | 792 if len(res.Clients) > 0 { |
| paddy@126 | 793 if res.Clients[0].Type == "confidential" && res.Clients[0].Secret == "" { |
| paddy@126 | 794 t.Log("Client:", res.Clients[0]) |
| paddy@126 | 795 t.Error("Expected confidential client to have a secret, but does not.") |
| paddy@126 | 796 } else if res.Clients[0].Type == "public" && res.Clients[0].Secret != "" { |
| paddy@126 | 797 t.Log("Client:", res.Clients[0]) |
| paddy@126 | 798 t.Error("Expected public client to not have a secret, but it does.") |
| paddy@126 | 799 } |
| paddy@126 | 800 } |
| paddy@116 | 801 fillInServerGenerated(test.resp, res) |
| paddy@116 | 802 success, field, expectation, result := compareResponses(test.resp, res) |
| paddy@116 | 803 if !success { |
| paddy@116 | 804 t.Errorf("Unexpected result for %s in response: expected %v, got %v", field, expectation, result) |
| paddy@116 | 805 } |
| paddy@116 | 806 } |
| paddy@116 | 807 } |
| paddy@128 | 808 |
| paddy@139 | 809 func TestGetClientHandler(t *testing.T) { |
| paddy@139 | 810 t.Parallel() |
| paddy@139 | 811 memstore := NewMemstore() |
| paddy@139 | 812 c := Context{ |
| paddy@139 | 813 clients: memstore, |
| paddy@139 | 814 profiles: memstore, |
| paddy@139 | 815 } |
| paddy@139 | 816 client := Client{ |
| paddy@139 | 817 ID: uuid.NewID(), |
| paddy@139 | 818 Secret: "myawesomesecret", |
| paddy@139 | 819 OwnerID: uuid.NewID(), |
| paddy@139 | 820 Name: "Test Client", |
| paddy@139 | 821 Logo: "https://auth.secondbit.org/logo.png", |
| paddy@139 | 822 Website: "https://code.secondbit.org", |
| paddy@139 | 823 Type: clientTypeConfidential, |
| paddy@139 | 824 } |
| paddy@139 | 825 err := c.SaveClient(client) |
| paddy@139 | 826 if err != nil { |
| paddy@139 | 827 t.Fatal("Can't store client in memstore:", err) |
| paddy@139 | 828 } |
| paddy@139 | 829 profile := Profile{ |
| paddy@139 | 830 ID: uuid.NewID(), |
| paddy@139 | 831 Name: "Test User", |
| paddy@139 | 832 Passphrase: "f3a4ac4f1d657b2e6e776d24213e39406d50a87a52691a2a78891425af1271d0", |
| paddy@139 | 833 Iterations: 1, |
| paddy@139 | 834 Salt: "d82d92cfa8bfb5a08270ebbf39a3710d24b352b937fcc8959ebcb40384cc616b", |
| paddy@139 | 835 PassphraseScheme: 1, |
| paddy@139 | 836 Compromised: false, |
| paddy@139 | 837 LockedUntil: time.Time{}, |
| paddy@139 | 838 PassphraseReset: "", |
| paddy@139 | 839 PassphraseResetCreated: time.Time{}, |
| paddy@149 | 840 Created: time.Now().Round(time.Millisecond), |
| paddy@139 | 841 LastSeen: time.Time{}, |
| paddy@139 | 842 } |
| paddy@139 | 843 login := Login{ |
| paddy@139 | 844 Type: "email", |
| paddy@139 | 845 Value: "test@example.com", |
| paddy@139 | 846 ProfileID: profile.ID, |
| paddy@149 | 847 Created: time.Now().Round(time.Millisecond), |
| paddy@139 | 848 LastUsed: time.Time{}, |
| paddy@139 | 849 } |
| paddy@139 | 850 err = c.SaveProfile(profile) |
| paddy@139 | 851 if err != nil { |
| paddy@139 | 852 t.Error("Error saving profile:", err) |
| paddy@139 | 853 } |
| paddy@139 | 854 err = c.AddLogin(login) |
| paddy@139 | 855 if err != nil { |
| paddy@139 | 856 t.Error("Error adding login:", err) |
| paddy@139 | 857 } |
| paddy@139 | 858 router := mux.NewRouter() |
| paddy@139 | 859 RegisterClientHandlers(router, c) |
| paddy@139 | 860 w := httptest.NewRecorder() |
| paddy@139 | 861 u := "https://test.auth.secondbit.org/clients/" + client.ID.String() |
| paddy@139 | 862 r, err := http.NewRequest("GET", u, nil) |
| paddy@139 | 863 if err != nil { |
| paddy@139 | 864 t.Fatal("Can't build request:", err) |
| paddy@139 | 865 } |
| paddy@139 | 866 r.Header.Set("Content-Type", "application/json") |
| paddy@139 | 867 router.ServeHTTP(w, r) |
| paddy@139 | 868 if w.Code != http.StatusOK { |
| paddy@139 | 869 t.Errorf("Expected response code to be %d, got %d", http.StatusOK, w.Code) |
| paddy@139 | 870 } |
| paddy@139 | 871 t.Logf("Response: %s", w.Body.String()) |
| paddy@139 | 872 var res response |
| paddy@139 | 873 err = json.Unmarshal(w.Body.Bytes(), &res) |
| paddy@139 | 874 if err != nil { |
| paddy@139 | 875 t.Error("Unexpected error unmarshalling response:", err) |
| paddy@139 | 876 } |
| paddy@139 | 877 if len(res.Clients) != 1 { |
| paddy@139 | 878 t.Errorf("Expected %d results in response, got %d", 1, len(res.Clients)) |
| paddy@139 | 879 } |
| paddy@139 | 880 if res.Clients[0].Secret != "" { |
| paddy@139 | 881 t.Error("Expected secret not to be set, but was set to", res.Clients[0].Secret) |
| paddy@139 | 882 } |
| paddy@139 | 883 // fill in the secret, which was omitted in the response |
| paddy@139 | 884 res.Clients[0].Secret = client.Secret |
| paddy@139 | 885 success, field, expectation, result := compareClients(client, res.Clients[0]) |
| paddy@139 | 886 if !success { |
| paddy@139 | 887 t.Errorf("Unexpected result for %s in response: expected %v, got %v", field, expectation, result) |
| paddy@139 | 888 } |
| paddy@139 | 889 |
| paddy@139 | 890 // test for improperly formatted ID |
| paddy@139 | 891 u = "https://test.auth.secondbit.org/clients/notanID" |
| paddy@139 | 892 w = httptest.NewRecorder() |
| paddy@139 | 893 r, err = http.NewRequest("GET", u, nil) |
| paddy@139 | 894 if err != nil { |
| paddy@139 | 895 t.Fatal("Can't build request:", err) |
| paddy@139 | 896 } |
| paddy@139 | 897 r.Header.Set("Content-Type", "application/json") |
| paddy@139 | 898 router.ServeHTTP(w, r) |
| paddy@139 | 899 if w.Code != http.StatusBadRequest { |
| paddy@139 | 900 t.Errorf("Expected response code to be %d, got %d", http.StatusBadRequest, w.Code) |
| paddy@139 | 901 } |
| paddy@139 | 902 t.Logf("Response: %s", w.Body.String()) |
| paddy@139 | 903 res = response{} |
| paddy@139 | 904 err = json.Unmarshal(w.Body.Bytes(), &res) |
| paddy@139 | 905 if err != nil { |
| paddy@139 | 906 t.Error("Unexpected error unmarshalling response:", err) |
| paddy@139 | 907 } |
| paddy@139 | 908 if len(res.Errors) != 1 { |
| paddy@139 | 909 t.Errorf("Expected %d results in response, got %d", 1, len(res.Errors)) |
| paddy@139 | 910 } |
| paddy@139 | 911 e := requestError{Slug: requestErrInvalidFormat, Param: "id"} |
| paddy@139 | 912 success, field, expectation, result = compareErrors(e, res.Errors[0]) |
| paddy@139 | 913 if !success { |
| paddy@139 | 914 t.Errorf("Unexpected result for %s in error response: expected %v, got %v", field, expectation, result) |
| paddy@139 | 915 } |
| paddy@139 | 916 |
| paddy@139 | 917 // test for a non-existent client |
| paddy@139 | 918 u = "https://test.auth.secondbit.org/clients/" + uuid.NewID().String() |
| paddy@139 | 919 w = httptest.NewRecorder() |
| paddy@139 | 920 r, err = http.NewRequest("GET", u, nil) |
| paddy@139 | 921 if err != nil { |
| paddy@139 | 922 t.Fatal("Can't build request:", err) |
| paddy@139 | 923 } |
| paddy@139 | 924 r.Header.Set("Content-Type", "application/json") |
| paddy@139 | 925 router.ServeHTTP(w, r) |
| paddy@139 | 926 if w.Code != http.StatusNotFound { |
| paddy@139 | 927 t.Errorf("Expected response code to be %d, got %d", http.StatusNotFound, w.Code) |
| paddy@139 | 928 } |
| paddy@139 | 929 t.Logf("Response: %s", w.Body.String()) |
| paddy@139 | 930 res = response{} |
| paddy@139 | 931 err = json.Unmarshal(w.Body.Bytes(), &res) |
| paddy@139 | 932 if err != nil { |
| paddy@139 | 933 t.Error("Unexpected error unmarshalling response:", err) |
| paddy@139 | 934 } |
| paddy@139 | 935 if len(res.Errors) != 1 { |
| paddy@139 | 936 t.Errorf("Expected %d results in response, got %d", 1, len(res.Errors)) |
| paddy@139 | 937 } |
| paddy@139 | 938 e = requestError{Slug: requestErrNotFound, Param: "id"} |
| paddy@139 | 939 success, field, expectation, result = compareErrors(e, res.Errors[0]) |
| paddy@139 | 940 if !success { |
| paddy@139 | 941 t.Errorf("Unexpected result for %s in error response: expected %v, got %v", field, expectation, result) |
| paddy@139 | 942 } |
| paddy@139 | 943 } |
| paddy@139 | 944 |
| paddy@139 | 945 func TestAuthenticatedGetClientHandler(t *testing.T) { |
| paddy@139 | 946 t.Parallel() |
| paddy@139 | 947 memstore := NewMemstore() |
| paddy@139 | 948 c := Context{ |
| paddy@139 | 949 clients: memstore, |
| paddy@139 | 950 profiles: memstore, |
| paddy@139 | 951 } |
| paddy@139 | 952 client := Client{ |
| paddy@139 | 953 ID: uuid.NewID(), |
| paddy@139 | 954 Secret: "myawesomesecret", |
| paddy@139 | 955 OwnerID: uuid.NewID(), |
| paddy@139 | 956 Name: "Test Client", |
| paddy@139 | 957 Logo: "https://auth.secondbit.org/logo.png", |
| paddy@139 | 958 Website: "https://code.secondbit.org", |
| paddy@139 | 959 Type: clientTypeConfidential, |
| paddy@139 | 960 } |
| paddy@139 | 961 err := c.SaveClient(client) |
| paddy@139 | 962 if err != nil { |
| paddy@139 | 963 t.Fatal("Can't store client in memstore:", err) |
| paddy@139 | 964 } |
| paddy@139 | 965 profile := Profile{ |
| paddy@139 | 966 ID: client.OwnerID, |
| paddy@139 | 967 Name: "Test User", |
| paddy@139 | 968 Passphrase: "f3a4ac4f1d657b2e6e776d24213e39406d50a87a52691a2a78891425af1271d0", |
| paddy@139 | 969 Iterations: 1, |
| paddy@139 | 970 Salt: "d82d92cfa8bfb5a08270ebbf39a3710d24b352b937fcc8959ebcb40384cc616b", |
| paddy@139 | 971 PassphraseScheme: 1, |
| paddy@139 | 972 Compromised: false, |
| paddy@139 | 973 LockedUntil: time.Time{}, |
| paddy@139 | 974 PassphraseReset: "", |
| paddy@139 | 975 PassphraseResetCreated: time.Time{}, |
| paddy@149 | 976 Created: time.Now().Round(time.Millisecond), |
| paddy@139 | 977 LastSeen: time.Time{}, |
| paddy@139 | 978 } |
| paddy@139 | 979 login := Login{ |
| paddy@139 | 980 Type: "email", |
| paddy@139 | 981 Value: "test@example.com", |
| paddy@139 | 982 ProfileID: profile.ID, |
| paddy@149 | 983 Created: time.Now().Round(time.Millisecond), |
| paddy@139 | 984 LastUsed: time.Time{}, |
| paddy@139 | 985 } |
| paddy@139 | 986 err = c.SaveProfile(profile) |
| paddy@139 | 987 if err != nil { |
| paddy@139 | 988 t.Error("Error saving profile:", err) |
| paddy@139 | 989 } |
| paddy@139 | 990 err = c.AddLogin(login) |
| paddy@139 | 991 if err != nil { |
| paddy@139 | 992 t.Error("Error adding login:", err) |
| paddy@139 | 993 } |
| paddy@139 | 994 profile2 := Profile{ |
| paddy@139 | 995 ID: uuid.NewID(), |
| paddy@139 | 996 Name: "Test User", |
| paddy@139 | 997 Passphrase: "f3a4ac4f1d657b2e6e776d24213e39406d50a87a52691a2a78891425af1271d0", |
| paddy@139 | 998 Iterations: 1, |
| paddy@139 | 999 Salt: "d82d92cfa8bfb5a08270ebbf39a3710d24b352b937fcc8959ebcb40384cc616b", |
| paddy@139 | 1000 PassphraseScheme: 1, |
| paddy@139 | 1001 Compromised: false, |
| paddy@139 | 1002 LockedUntil: time.Time{}, |
| paddy@139 | 1003 PassphraseReset: "", |
| paddy@139 | 1004 PassphraseResetCreated: time.Time{}, |
| paddy@149 | 1005 Created: time.Now().Round(time.Millisecond), |
| paddy@139 | 1006 LastSeen: time.Time{}, |
| paddy@139 | 1007 } |
| paddy@139 | 1008 login2 := Login{ |
| paddy@139 | 1009 Type: "email", |
| paddy@139 | 1010 Value: "test2@example.com", |
| paddy@139 | 1011 ProfileID: profile2.ID, |
| paddy@149 | 1012 Created: time.Now().Round(time.Millisecond), |
| paddy@139 | 1013 LastUsed: time.Time{}, |
| paddy@139 | 1014 } |
| paddy@139 | 1015 err = c.SaveProfile(profile2) |
| paddy@139 | 1016 if err != nil { |
| paddy@139 | 1017 t.Error("Error saving profile:", err) |
| paddy@139 | 1018 } |
| paddy@139 | 1019 err = c.AddLogin(login2) |
| paddy@139 | 1020 if err != nil { |
| paddy@139 | 1021 t.Error("Error adding login:", err) |
| paddy@139 | 1022 } |
| paddy@139 | 1023 router := mux.NewRouter() |
| paddy@139 | 1024 RegisterClientHandlers(router, c) |
| paddy@139 | 1025 w := httptest.NewRecorder() |
| paddy@139 | 1026 u := "https://test.auth.secondbit.org/clients/" + client.ID.String() |
| paddy@139 | 1027 r, err := http.NewRequest("GET", u, nil) |
| paddy@139 | 1028 if err != nil { |
| paddy@139 | 1029 t.Fatal("Can't build request:", err) |
| paddy@139 | 1030 } |
| paddy@139 | 1031 r.Header.Set("Content-Type", "application/json") |
| paddy@139 | 1032 r.SetBasicAuth(login.Value, "mysecurepassphrase") |
| paddy@139 | 1033 router.ServeHTTP(w, r) |
| paddy@139 | 1034 if w.Code != http.StatusOK { |
| paddy@139 | 1035 t.Errorf("Expected response code to be %d, got %d", http.StatusOK, w.Code) |
| paddy@139 | 1036 } |
| paddy@139 | 1037 t.Logf("Response: %s", w.Body.String()) |
| paddy@139 | 1038 var res response |
| paddy@139 | 1039 err = json.Unmarshal(w.Body.Bytes(), &res) |
| paddy@139 | 1040 if err != nil { |
| paddy@139 | 1041 t.Error("Unexpected error unmarshalling response:", err) |
| paddy@139 | 1042 } |
| paddy@139 | 1043 if len(res.Clients) != 1 { |
| paddy@139 | 1044 t.Errorf("Expected %d results in response, got %d", 1, len(res.Clients)) |
| paddy@139 | 1045 } |
| paddy@139 | 1046 success, field, expectation, result := compareClients(client, res.Clients[0]) |
| paddy@139 | 1047 if !success { |
| paddy@139 | 1048 t.Errorf("Unexpected result for %s in response: expected %v, got %v", field, expectation, result) |
| paddy@139 | 1049 } |
| paddy@139 | 1050 |
| paddy@139 | 1051 // test for improperly formatted ID |
| paddy@139 | 1052 u = "https://test.auth.secondbit.org/clients/notanID" |
| paddy@139 | 1053 w = httptest.NewRecorder() |
| paddy@139 | 1054 r, err = http.NewRequest("GET", u, nil) |
| paddy@139 | 1055 if err != nil { |
| paddy@139 | 1056 t.Fatal("Can't build request:", err) |
| paddy@139 | 1057 } |
| paddy@139 | 1058 r.Header.Set("Content-Type", "application/json") |
| paddy@139 | 1059 r.SetBasicAuth(login.Value, "mysecurepassphrase") |
| paddy@139 | 1060 router.ServeHTTP(w, r) |
| paddy@139 | 1061 if w.Code != http.StatusBadRequest { |
| paddy@139 | 1062 t.Errorf("Expected response code to be %d, got %d", http.StatusBadRequest, w.Code) |
| paddy@139 | 1063 } |
| paddy@139 | 1064 t.Logf("Response: %s", w.Body.String()) |
| paddy@139 | 1065 res = response{} |
| paddy@139 | 1066 err = json.Unmarshal(w.Body.Bytes(), &res) |
| paddy@139 | 1067 if err != nil { |
| paddy@139 | 1068 t.Error("Unexpected error unmarshalling response:", err) |
| paddy@139 | 1069 } |
| paddy@139 | 1070 if len(res.Errors) != 1 { |
| paddy@139 | 1071 t.Errorf("Expected %d results in response, got %d", 1, len(res.Errors)) |
| paddy@139 | 1072 } |
| paddy@139 | 1073 e := requestError{Slug: requestErrInvalidFormat, Param: "id"} |
| paddy@139 | 1074 success, field, expectation, result = compareErrors(e, res.Errors[0]) |
| paddy@139 | 1075 if !success { |
| paddy@139 | 1076 t.Errorf("Unexpected result for %s in error response: expected %v, got %v", field, expectation, result) |
| paddy@139 | 1077 } |
| paddy@139 | 1078 |
| paddy@139 | 1079 // test for a non-existent client |
| paddy@139 | 1080 u = "https://test.auth.secondbit.org/clients/" + uuid.NewID().String() |
| paddy@139 | 1081 w = httptest.NewRecorder() |
| paddy@139 | 1082 r, err = http.NewRequest("GET", u, nil) |
| paddy@139 | 1083 if err != nil { |
| paddy@139 | 1084 t.Fatal("Can't build request:", err) |
| paddy@139 | 1085 } |
| paddy@139 | 1086 r.Header.Set("Content-Type", "application/json") |
| paddy@139 | 1087 r.SetBasicAuth(login.Value, "mysecurepassphrase") |
| paddy@139 | 1088 router.ServeHTTP(w, r) |
| paddy@139 | 1089 if w.Code != http.StatusNotFound { |
| paddy@139 | 1090 t.Errorf("Expected response code to be %d, got %d", http.StatusNotFound, w.Code) |
| paddy@139 | 1091 } |
| paddy@139 | 1092 t.Logf("Response: %s", w.Body.String()) |
| paddy@139 | 1093 res = response{} |
| paddy@139 | 1094 err = json.Unmarshal(w.Body.Bytes(), &res) |
| paddy@139 | 1095 if err != nil { |
| paddy@139 | 1096 t.Error("Unexpected error unmarshalling response:", err) |
| paddy@139 | 1097 } |
| paddy@139 | 1098 if len(res.Errors) != 1 { |
| paddy@139 | 1099 t.Errorf("Expected %d results in response, got %d", 1, len(res.Errors)) |
| paddy@139 | 1100 } |
| paddy@139 | 1101 e = requestError{Slug: requestErrNotFound, Param: "id"} |
| paddy@139 | 1102 success, field, expectation, result = compareErrors(e, res.Errors[0]) |
| paddy@139 | 1103 if !success { |
| paddy@139 | 1104 t.Errorf("Unexpected result for %s in error response: expected %v, got %v", field, expectation, result) |
| paddy@139 | 1105 } |
| paddy@139 | 1106 |
| paddy@139 | 1107 // test for a wrong password |
| paddy@139 | 1108 u = "https://test.auth.secondbit.org/clients/" + client.ID.String() |
| paddy@139 | 1109 w = httptest.NewRecorder() |
| paddy@139 | 1110 r, err = http.NewRequest("GET", u, nil) |
| paddy@139 | 1111 if err != nil { |
| paddy@139 | 1112 t.Fatal("Can't build request:", err) |
| paddy@139 | 1113 } |
| paddy@139 | 1114 r.Header.Set("Content-Type", "application/json") |
| paddy@139 | 1115 r.SetBasicAuth(login.Value, "notmypassphrase") |
| paddy@139 | 1116 router.ServeHTTP(w, r) |
| paddy@139 | 1117 if w.Code != http.StatusUnauthorized { |
| paddy@139 | 1118 t.Errorf("Expected response code to be %d, got %d", http.StatusUnauthorized, w.Code) |
| paddy@139 | 1119 } |
| paddy@139 | 1120 t.Logf("Response: %s", w.Body.String()) |
| paddy@139 | 1121 res = response{} |
| paddy@139 | 1122 err = json.Unmarshal(w.Body.Bytes(), &res) |
| paddy@139 | 1123 if err != nil { |
| paddy@139 | 1124 t.Error("Unexpected error unmarshalling response:", err) |
| paddy@139 | 1125 } |
| paddy@139 | 1126 if len(res.Errors) != 1 { |
| paddy@139 | 1127 t.Errorf("Expected %d results in response, got %d", 1, len(res.Errors)) |
| paddy@139 | 1128 } |
| paddy@139 | 1129 e = requestError{Slug: requestErrAccessDenied} |
| paddy@139 | 1130 success, field, expectation, result = compareErrors(e, res.Errors[0]) |
| paddy@139 | 1131 if !success { |
| paddy@139 | 1132 t.Errorf("Unexpected result for %s in error response: expected %v, got %v", field, expectation, result) |
| paddy@139 | 1133 } |
| paddy@139 | 1134 |
| paddy@139 | 1135 // test for a wrong account |
| paddy@139 | 1136 u = "https://test.auth.secondbit.org/clients/" + client.ID.String() |
| paddy@139 | 1137 w = httptest.NewRecorder() |
| paddy@139 | 1138 r, err = http.NewRequest("GET", u, nil) |
| paddy@139 | 1139 if err != nil { |
| paddy@139 | 1140 t.Fatal("Can't build request:", err) |
| paddy@139 | 1141 } |
| paddy@139 | 1142 r.Header.Set("Content-Type", "application/json") |
| paddy@139 | 1143 r.SetBasicAuth(login2.Value, "mysecurepassphrase") |
| paddy@139 | 1144 router.ServeHTTP(w, r) |
| paddy@139 | 1145 if w.Code != http.StatusOK { |
| paddy@139 | 1146 t.Errorf("Expected response code to be %d, got %d", http.StatusOK, w.Code) |
| paddy@139 | 1147 } |
| paddy@139 | 1148 t.Logf("Response: %s", w.Body.String()) |
| paddy@139 | 1149 res = response{} |
| paddy@139 | 1150 err = json.Unmarshal(w.Body.Bytes(), &res) |
| paddy@139 | 1151 if err != nil { |
| paddy@139 | 1152 t.Error("Unexpected error unmarshalling response:", err) |
| paddy@139 | 1153 } |
| paddy@139 | 1154 if len(res.Clients) != 1 { |
| paddy@139 | 1155 t.Errorf("Expected %d results in response, got %d", 1, len(res.Clients)) |
| paddy@139 | 1156 } |
| paddy@139 | 1157 if res.Clients[0].Secret != "" { |
| paddy@139 | 1158 t.Errorf("Expected client secret to be empty, got %s", res.Clients[0].Secret) |
| paddy@139 | 1159 } |
| paddy@139 | 1160 // fill the client's secret for comparison |
| paddy@139 | 1161 res.Clients[0].Secret = client.Secret |
| paddy@139 | 1162 success, field, expectation, result = compareClients(client, res.Clients[0]) |
| paddy@139 | 1163 if !success { |
| paddy@139 | 1164 t.Errorf("Unexpected result for %s in response: expected %v, got %v", field, expectation, result) |
| paddy@139 | 1165 } |
| paddy@139 | 1166 } |
| paddy@139 | 1167 |
| paddy@128 | 1168 // BUG(paddy): We need to test the clientCredentialsValidate function. |
| paddy@131 | 1169 // BUG(paddy): We need to test the ListClientsHandler. |