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