auth

Paddy 2015-01-24 Parent:4f5d13d2f7c7 Child:f474ce964dcf

130:6c755b23ec80 Go to Latest

auth/client_test.go

Change normalization flags to a constant. Let's use a constant so we can ensure we're using the same flags everywhere. Otherwise, we can get weird data corruption because we use the wrong flags.

History
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@129 472 func TestGetClientAuth(t *testing.T) {
paddy@129 473 t.Parallel()
paddy@129 474 type clientAuthRequest struct {
paddy@129 475 username string
paddy@129 476 pass string
paddy@129 477 clientID string
paddy@129 478 allowPublic bool
paddy@129 479 expectedClientID uuid.ID
paddy@129 480 expectedClientSecret string
paddy@129 481 expectedValid bool
paddy@129 482 expectedCode int
paddy@129 483 expectedBody string
paddy@129 484 expectAuthenticateHeader bool
paddy@129 485 }
paddy@129 486 id := uuid.NewID()
paddy@129 487 tests := []clientAuthRequest{
paddy@129 488 {"", "", "", false, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false},
paddy@129 489 {"", "", "", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false},
paddy@129 490 {"", "no clientID set", "", false, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 491 {"", "no clientID set", "", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 492 {"not an actual id", "invalid client ID set", "", false, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 493 {"not an actual id", "invalid client ID set", "", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 494 {"", "", "not an actual id", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false},
paddy@129 495 {id.String(), "secret", "", true, id, "secret", true, http.StatusOK, "", false},
paddy@129 496 {id.String(), "secret", "", false, id, "secret", true, http.StatusOK, "", false},
paddy@129 497 {"", "", id.String(), true, id, "", true, http.StatusOK, "", false},
paddy@129 498 {"", "", id.String(), false, nil, "", false, http.StatusBadRequest, `{"error":"unauthorized_client"}`, false},
paddy@129 499 }
paddy@129 500 for pos, test := range tests {
paddy@129 501 t.Logf("Running test #%d, with request %+v", pos, test)
paddy@129 502 w := httptest.NewRecorder()
paddy@129 503 r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
paddy@129 504 if err != nil {
paddy@129 505 t.Fatal("Can't build request:", err)
paddy@129 506 }
paddy@129 507 if test.username != "" || test.pass != "" {
paddy@129 508 r.SetBasicAuth(test.username, test.pass)
paddy@129 509 }
paddy@129 510 r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
paddy@129 511 params := url.Values{}
paddy@129 512 params.Set("client_id", test.clientID)
paddy@129 513 body := bytes.NewBufferString(params.Encode())
paddy@129 514 r.Body = ioutil.NopCloser(body)
paddy@129 515 respID, respSecret, success := getClientAuth(w, r, test.allowPublic)
paddy@129 516 if (respID == nil && test.expectedClientID != nil) || (respID != nil && test.expectedClientID == nil) || !respID.Equal(test.expectedClientID) {
paddy@129 517 t.Errorf("Expected response ID to be %v, got %v", test.expectedClientID, respID)
paddy@129 518 }
paddy@129 519 if test.expectedClientSecret != respSecret {
paddy@129 520 t.Errorf("Expected response secret to be '%s', got '%s'", test.expectedClientSecret, respSecret)
paddy@129 521 }
paddy@129 522 if test.expectedValid != success {
paddy@129 523 t.Errorf("Expected success result to be %v, got %v", test.expectedValid, success)
paddy@129 524 }
paddy@129 525 if test.expectedCode != w.Code {
paddy@129 526 t.Errorf("Expected response code to be %d, got %d", test.expectedCode, w.Code)
paddy@129 527 }
paddy@129 528 if test.expectedBody != strings.TrimSpace(w.Body.String()) {
paddy@129 529 t.Errorf("Expected body to be '%s', got '%s'", test.expectedBody, strings.TrimSpace(w.Body.String()))
paddy@129 530 }
paddy@129 531 if test.expectAuthenticateHeader && w.Header().Get("WWW-Authenticate") != "Basic" {
paddy@129 532 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate"))
paddy@129 533 }
paddy@129 534 }
paddy@129 535 }
paddy@129 536
paddy@113 537 func TestVerifyClient(t *testing.T) {
paddy@113 538 t.Parallel()
paddy@129 539 type verifyClientRequest struct {
paddy@129 540 username string
paddy@129 541 pass string
paddy@129 542 clientID string
paddy@129 543 allowPublic bool
paddy@129 544 expectedClientID uuid.ID
paddy@129 545 expectedValid bool
paddy@129 546 expectedCode int
paddy@129 547 expectedBody string
paddy@129 548 expectAuthenticateHeader bool
paddy@129 549 }
paddy@113 550 memstore := NewMemstore()
paddy@113 551 context := Context{
paddy@113 552 clients: memstore,
paddy@113 553 }
paddy@113 554 client := Client{
paddy@113 555 ID: uuid.NewID(),
paddy@113 556 Secret: "super secret!",
paddy@113 557 OwnerID: uuid.NewID(),
paddy@113 558 Name: "My test client",
paddy@113 559 Logo: "https://secondbit.org/logo.png",
paddy@113 560 Website: "https://secondbit.org/",
paddy@113 561 Type: "confidential",
paddy@113 562 }
paddy@113 563 err := context.SaveClient(client)
paddy@113 564 if err != nil {
paddy@113 565 t.Fatal("Could not save client:", err)
paddy@113 566 }
paddy@113 567 publicClient := Client{
paddy@113 568 ID: uuid.NewID(),
paddy@113 569 Secret: "",
paddy@113 570 OwnerID: uuid.NewID(),
paddy@113 571 Name: "A public client",
paddy@113 572 Logo: "https://secondbit.org/logo.png",
paddy@113 573 Website: "https://secondbit.org/",
paddy@113 574 Type: "public",
paddy@113 575 }
paddy@113 576 err = context.SaveClient(publicClient)
paddy@113 577 if err != nil {
paddy@113 578 t.Fatal("Could not save client:", err)
paddy@113 579 }
paddy@129 580 id := uuid.NewID()
paddy@129 581 tests := []verifyClientRequest{
paddy@129 582 {"", "", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false},
paddy@129 583 {"", "", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false},
paddy@129 584 {"", "no clientID set", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 585 {"", "no clientID set", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 586 {"not an actual id", "invalid client ID set", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 587 {"not an actual id", "invalid client ID set", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 588 {id.String(), "unsaved client ID set", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 589 {id.String(), "unsaved client ID set", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 590 {client.ID.String(), "wrong secret", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 591 {client.ID.String(), "wrong secret", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 592 {"", "", "not an actual id", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false},
paddy@129 593 {"", "", id.String(), true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false},
paddy@129 594 {client.ID.String(), client.Secret, "", true, client.ID, true, http.StatusOK, "", false},
paddy@129 595 {client.ID.String(), client.Secret, "", false, client.ID, true, http.StatusOK, "", false},
paddy@129 596 {"", "", publicClient.ID.String(), true, publicClient.ID, true, http.StatusOK, "", false},
paddy@129 597 {"", "", publicClient.ID.String(), false, nil, false, http.StatusBadRequest, `{"error":"unauthorized_client"}`, false},
paddy@113 598 }
paddy@113 599
paddy@129 600 for pos, test := range tests {
paddy@129 601 t.Logf("Running test #%d, with request %+v", pos, test)
paddy@129 602 w := httptest.NewRecorder()
paddy@129 603 r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
paddy@129 604 if err != nil {
paddy@129 605 t.Fatal("Can't build request:", err)
paddy@129 606 }
paddy@129 607 if test.username != "" || test.pass != "" {
paddy@129 608 r.SetBasicAuth(test.username, test.pass)
paddy@129 609 }
paddy@129 610 r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
paddy@129 611 params := url.Values{}
paddy@129 612 params.Set("client_id", test.clientID)
paddy@129 613 body := bytes.NewBufferString(params.Encode())
paddy@129 614 r.Body = ioutil.NopCloser(body)
paddy@129 615 respID, success := verifyClient(w, r, test.allowPublic, context)
paddy@129 616 if (respID == nil && test.expectedClientID != nil) || (respID != nil && test.expectedClientID == nil) || !respID.Equal(test.expectedClientID) {
paddy@129 617 t.Errorf("Expected response ID to be %v, got %v", test.expectedClientID, respID)
paddy@129 618 }
paddy@129 619 if test.expectedValid != success {
paddy@129 620 t.Errorf("Expected success result to be %v, got %v", test.expectedValid, success)
paddy@129 621 }
paddy@129 622 if test.expectedCode != w.Code {
paddy@129 623 t.Errorf("Expected response code to be %d, got %d", test.expectedCode, w.Code)
paddy@129 624 }
paddy@129 625 if test.expectedBody != strings.TrimSpace(w.Body.String()) {
paddy@129 626 t.Errorf("Expected body to be '%s', got '%s'", test.expectedBody, strings.TrimSpace(w.Body.String()))
paddy@129 627 }
paddy@129 628 if test.expectAuthenticateHeader && w.Header().Get("WWW-Authenticate") != "Basic" {
paddy@129 629 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate"))
paddy@129 630 }
paddy@113 631 }
paddy@113 632 }
paddy@116 633
paddy@116 634 func TestCreateClientHandler(t *testing.T) {
paddy@116 635 t.Parallel()
paddy@116 636 memstore := NewMemstore()
paddy@116 637 c := Context{
paddy@116 638 clients: memstore,
paddy@116 639 profiles: memstore,
paddy@116 640 }
paddy@116 641 w := httptest.NewRecorder()
paddy@116 642 r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/clients", nil)
paddy@116 643 if err != nil {
paddy@116 644 t.Fatal("Can't build request:", err)
paddy@116 645 }
paddy@116 646 r.Header.Set("Content-Type", "application/json")
paddy@116 647 CreateClientHandler(w, r, c)
paddy@116 648 if w.Code != http.StatusUnauthorized {
paddy@116 649 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
paddy@116 650 }
paddy@116 651 expected := `{"errors":[{"error":"access_denied"}]}`
paddy@116 652 result := strings.TrimSpace(w.Body.String())
paddy@116 653 if result != expected {
paddy@116 654 t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
paddy@116 655 }
paddy@116 656 w = httptest.NewRecorder()
paddy@116 657 r.Header.Set("Authorization", "Not basic at all...")
paddy@116 658 CreateClientHandler(w, r, c)
paddy@116 659 if w.Code != http.StatusUnauthorized {
paddy@116 660 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
paddy@116 661 }
paddy@116 662 expected = `{"errors":[{"error":"access_denied"}]}`
paddy@116 663 result = strings.TrimSpace(w.Body.String())
paddy@116 664 if result != expected {
paddy@116 665 t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
paddy@116 666 }
paddy@116 667 w = httptest.NewRecorder()
paddy@116 668 r.Header.Set("Authorization", "Basic TotallyNotBase64Encoded")
paddy@116 669 CreateClientHandler(w, r, c)
paddy@116 670 if w.Code != http.StatusUnauthorized {
paddy@116 671 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
paddy@116 672 }
paddy@116 673 expected = `{"errors":[{"error":"access_denied"}]}`
paddy@116 674 result = strings.TrimSpace(w.Body.String())
paddy@116 675 if result != expected {
paddy@116 676 t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
paddy@116 677 }
paddy@116 678 w = httptest.NewRecorder()
paddy@116 679 r.Header.Set("Authorization", "Basic dGhpc2hhc25vY29sb24=")
paddy@116 680 CreateClientHandler(w, r, c)
paddy@116 681 if w.Code != http.StatusUnauthorized {
paddy@116 682 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
paddy@116 683 }
paddy@116 684 expected = `{"errors":[{"error":"access_denied"}]}`
paddy@116 685 result = strings.TrimSpace(w.Body.String())
paddy@116 686 if result != expected {
paddy@116 687 t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
paddy@116 688 }
paddy@116 689 profile := Profile{
paddy@116 690 ID: uuid.NewID(),
paddy@116 691 Name: "Test User",
paddy@116 692 Passphrase: "f3a4ac4f1d657b2e6e776d24213e39406d50a87a52691a2a78891425af1271d0",
paddy@116 693 Iterations: 1,
paddy@116 694 Salt: "d82d92cfa8bfb5a08270ebbf39a3710d24b352b937fcc8959ebcb40384cc616b",
paddy@116 695 PassphraseScheme: 1,
paddy@116 696 Compromised: false,
paddy@116 697 LockedUntil: time.Time{},
paddy@116 698 PassphraseReset: "",
paddy@116 699 PassphraseResetCreated: time.Time{},
paddy@116 700 Created: time.Now(),
paddy@116 701 LastSeen: time.Time{},
paddy@116 702 }
paddy@116 703 login := Login{
paddy@116 704 Type: "email",
paddy@116 705 Value: "test@example.com",
paddy@116 706 ProfileID: profile.ID,
paddy@116 707 Created: time.Now(),
paddy@116 708 LastUsed: time.Time{},
paddy@116 709 }
paddy@116 710 w = httptest.NewRecorder()
paddy@116 711 r.SetBasicAuth("test@example.com", "mysecurepassphrase")
paddy@116 712 CreateClientHandler(w, r, c)
paddy@116 713 if w.Code != http.StatusUnauthorized {
paddy@116 714 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
paddy@116 715 }
paddy@116 716 expected = `{"errors":[{"error":"access_denied"}]}`
paddy@116 717 result = strings.TrimSpace(w.Body.String())
paddy@116 718 if result != expected {
paddy@116 719 t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
paddy@116 720 }
paddy@116 721 err = c.SaveProfile(profile)
paddy@116 722 if err != nil {
paddy@116 723 t.Error("Error saving profile:", err)
paddy@116 724 }
paddy@116 725 err = c.AddLogin(login)
paddy@116 726 if err != nil {
paddy@116 727 t.Error("Error adding login:", err)
paddy@116 728 }
paddy@116 729 r.SetBasicAuth("test@example.com", "mysecurepassphrase")
paddy@116 730 type testStruct struct {
paddy@116 731 request string
paddy@116 732 code int
paddy@116 733 resp response
paddy@116 734 }
paddy@116 735 tests := []testStruct{
paddy@116 736 {``, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidFormat, Field: "/"}}}},
paddy@116 737 {`{}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/type"}, {Slug: requestErrMissing, Field: "/name"}}}},
paddy@116 738 {`{"type":"notarealtype"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrMissing, Field: "/name"}}}},
paddy@116 739 {`{"type":"notarealtype","name":"myreallylongnameislongerthatthemaximumnamelength"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrOverflow, Field: "/name"}}}},
paddy@116 740 {`{"type":"notarealtype","name":"a"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrInsufficient, Field: "/name"}}}},
paddy@116 741 {`{"type":"public"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/name"}}}},
paddy@116 742 {`{"type":"public","name":"myreallylongnameislongerthatthemaximumnamelength"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrOverflow, Field: "/name"}}}},
paddy@116 743 {`{"type":"public","name":"a"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInsufficient, Field: "/name"}}}},
paddy@116 744 {`{"name":"My Client"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/type"}}}},
paddy@116 745 {`{"type":"notarealtype","name":"My Client"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}}}},
paddy@116 746 {`{"type":"public","name":"My Client"}`, http.StatusCreated, response{Clients: []Client{{Name: "My Client", OwnerID: profile.ID, Type: "public"}}}},
paddy@116 747 {`{"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 748 {`{"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 749 {`{"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 750 {`{"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 751 }
paddy@116 752 for pos, test := range tests {
paddy@116 753 t.Logf("Test #%d: `%s`", pos, test.request)
paddy@116 754 w = httptest.NewRecorder()
paddy@116 755 body := bytes.NewBufferString(test.request)
paddy@116 756 r.Body = ioutil.NopCloser(body)
paddy@116 757 CreateClientHandler(w, r, c)
paddy@116 758 if w.Code != test.code {
paddy@116 759 t.Errorf("Expected response code to be %d, got %d", test.code, w.Code)
paddy@116 760 }
paddy@116 761 t.Logf("Response: %s", w.Body.String())
paddy@116 762 var res response
paddy@116 763 err = json.Unmarshal(w.Body.Bytes(), &res)
paddy@116 764 if err != nil {
paddy@116 765 t.Error("Unexpected error unmarshalling response:", err)
paddy@116 766 }
paddy@126 767 if len(res.Clients) > 0 {
paddy@126 768 if res.Clients[0].Type == "confidential" && res.Clients[0].Secret == "" {
paddy@126 769 t.Log("Client:", res.Clients[0])
paddy@126 770 t.Error("Expected confidential client to have a secret, but does not.")
paddy@126 771 } else if res.Clients[0].Type == "public" && res.Clients[0].Secret != "" {
paddy@126 772 t.Log("Client:", res.Clients[0])
paddy@126 773 t.Error("Expected public client to not have a secret, but it does.")
paddy@126 774 }
paddy@126 775 }
paddy@116 776 fillInServerGenerated(test.resp, res)
paddy@116 777 success, field, expectation, result := compareResponses(test.resp, res)
paddy@116 778 if !success {
paddy@116 779 t.Errorf("Unexpected result for %s in response: expected %v, got %v", field, expectation, result)
paddy@116 780 }
paddy@116 781 }
paddy@116 782 }
paddy@128 783
paddy@128 784 // BUG(paddy): We need to test the clientCredentialsValidate function.