auth

Paddy 2015-01-18 Parent:e000b1c24fc0 Child:34de07217709

123:0a1e16b9c141 Go to Latest

auth/client_test.go

Refactor verifyClient, implement refresh tokens. Refactor verifyClient into verifyClient and getClientAuth. We moved verifyClient out of each of the GrantType's validation functions and into the access token endpoint, where it will be called before the GrantType's validation function. Yay, less code repetition. And seeing as we always want to verify the client, that seems like a good way to prevent things like 118a69954621 from happening. This did, however, force us to add an AllowsPublic property to the GrantType, so the token endpoint knows whether or not a public Client is valid for any given GrantType. We also implemented the refresh token grant type, which required adding ClientID and RefreshRevoked as properties on the Token type. We need ClientID because we need to constrain refresh tokens to the client that issued them. We also should probably keep track of which tokens belong to which clients, just as a general rule of thumb. RefreshRevoked had to be created, next to Revoked, because the AccessToken could be revoked and the RefreshToken still valid, or vice versa. Notably, when you issue a new refresh token, the old one is revoked, but the access token is still valid. It remains to be seen whether this is a good way to track things or not. The number of duplicated properties lead me to believe our type is not a great representation of the underlying concepts.

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@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@116 983 }
paddy@116 984 for pos, test := range tests {
paddy@116 985 t.Logf("Test #%d: `%s`", pos, test.request)
paddy@116 986 w = httptest.NewRecorder()
paddy@116 987 body := bytes.NewBufferString(test.request)
paddy@116 988 r.Body = ioutil.NopCloser(body)
paddy@116 989 CreateClientHandler(w, r, c)
paddy@116 990 if w.Code != test.code {
paddy@116 991 t.Errorf("Expected response code to be %d, got %d", test.code, w.Code)
paddy@116 992 }
paddy@116 993 t.Logf("Response: %s", w.Body.String())
paddy@116 994 var res response
paddy@116 995 err = json.Unmarshal(w.Body.Bytes(), &res)
paddy@116 996 if err != nil {
paddy@116 997 t.Error("Unexpected error unmarshalling response:", err)
paddy@116 998 }
paddy@116 999 fillInServerGenerated(test.resp, res)
paddy@116 1000 success, field, expectation, result := compareResponses(test.resp, res)
paddy@116 1001 if !success {
paddy@116 1002 t.Errorf("Unexpected result for %s in response: expected %v, got %v", field, expectation, result)
paddy@116 1003 }
paddy@116 1004 }
paddy@116 1005 }