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