github.com/greenpau/go-identity@v1.1.6/database_test.go (about) 1 // Copyright 2020 Paul Greenberg greenpau@outlook.com 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package identity 16 17 import ( 18 "fmt" 19 "github.com/google/go-cmp/cmp" 20 "github.com/greenpau/go-identity/internal/tests" 21 "github.com/greenpau/go-identity/pkg/errors" 22 "github.com/greenpau/go-identity/pkg/requests" 23 "path" 24 "path/filepath" 25 "testing" 26 "time" 27 ) 28 29 var ( 30 testUser1 = "jsmith" 31 testEmail1 = "jsmith@gmail.com" 32 testPwd1 = NewRandomString(12) 33 testFullName1 = "Smith, John" 34 testRoles1 = []string{"viewer", "editor", "admin"} 35 testUser2 = "bjones" 36 testEmail2 = "bjones@gmail.com" 37 testPwd2 = NewRandomString(16) 38 testFullName2 = "" 39 testRoles2 = []string{"viewer"} 40 ) 41 42 func createTestDatabase(s string) (*Database, error) { 43 tmpDir, err := tests.TempDir(s) 44 if err != nil { 45 return nil, err 46 } 47 reqs := []*requests.Request{ 48 { 49 User: requests.User{ 50 Username: testUser1, 51 Password: testPwd1, 52 Email: testEmail1, 53 FullName: testFullName1, 54 Roles: testRoles1, 55 }, 56 }, 57 { 58 User: requests.User{ 59 Username: testUser2, 60 Password: testPwd2, 61 Email: testEmail2, 62 FullName: testFullName2, 63 Roles: testRoles2, 64 }, 65 }, 66 } 67 68 db, err := NewDatabase(filepath.Join(tmpDir, "user_db.json")) 69 if err != nil { 70 return nil, err 71 } 72 73 for _, req := range reqs { 74 if err := db.AddUser(req); err != nil { 75 return nil, err 76 } 77 user, err := db.getUser(req.User.Username) 78 if err != nil { 79 return nil, err 80 } 81 user.PublicKeys = append(user.PublicKeys, &PublicKey{}) 82 } 83 return db, nil 84 } 85 86 func TestNewDatabase(t *testing.T) { 87 tmpDir, err := tests.TempDir("TestNewDatabase") 88 if err != nil { 89 t.Fatalf("failed to create temp dir: %v", err) 90 } 91 t.Logf("%v", tmpDir) 92 passwd := NewRandomString(12) 93 testcases := []struct { 94 name string 95 path string 96 req *requests.Request 97 backup string 98 want map[string]interface{} 99 shouldErr bool 100 err error 101 }{ 102 { 103 name: "test create new database", 104 path: filepath.Join(tmpDir, "user_db.json"), 105 req: &requests.Request{ 106 User: requests.User{ 107 Username: "jsmith", 108 Password: passwd, 109 Email: "jsmith@gmail.com", 110 FullName: "Smith, John", 111 Roles: []string{"viewer", "editor", "admin"}, 112 }, 113 }, 114 backup: filepath.Join(tmpDir, "user_db_backup.json"), 115 want: map[string]interface{}{ 116 "path": filepath.Join(tmpDir, "user_db.json"), 117 "user_count": 0, 118 }, 119 }, 120 { 121 name: "test new database is directory", 122 path: tmpDir, 123 want: map[string]interface{}{ 124 "path": tmpDir, 125 }, 126 shouldErr: true, 127 err: errors.ErrNewDatabase.WithArgs(tmpDir, "path points to a directory"), 128 }, 129 { 130 name: "test load new database", 131 path: filepath.Join(tmpDir, "user_db.json"), 132 want: map[string]interface{}{ 133 "path": filepath.Join(tmpDir, "user_db.json"), 134 "user_count": 1, 135 }, 136 }, 137 } 138 for _, tc := range testcases { 139 t.Run(tc.name, func(t *testing.T) { 140 msgs := []string{fmt.Sprintf("test name: %s", tc.name)} 141 msgs = append(msgs, fmt.Sprintf("temporary directory: %s", tmpDir)) 142 db, err := NewDatabase(tc.path) 143 if tests.EvalErrWithLog(t, err, "new database", tc.shouldErr, tc.err, msgs) { 144 return 145 } 146 got := make(map[string]interface{}) 147 got["path"] = db.GetPath() 148 got["user_count"] = db.GetUserCount() 149 tests.EvalObjectsWithLog(t, "eval", tc.want, got, msgs) 150 if tc.req != nil { 151 if err := db.AddUser(tc.req); err != nil { 152 tests.EvalErrWithLog(t, err, "add user", tc.shouldErr, tc.err, msgs) 153 } 154 } 155 if err := db.Save(); err != nil { 156 t.Fatal(err) 157 } 158 if tc.backup != "" { 159 if err := db.Copy(tc.backup); err != nil { 160 t.Fatal(err) 161 } 162 } 163 }) 164 } 165 } 166 167 func TestDatabaseAuthentication(t *testing.T) { 168 db, err := createTestDatabase("TestDatabaseAuthentication") 169 if err != nil { 170 t.Fatalf("failed to create temp dir: %v", err) 171 } 172 // t.Logf("%v", db.path) 173 174 testcases := []struct { 175 name string 176 req *requests.Request 177 want map[string]interface{} 178 shouldErr bool 179 err error 180 }{ 181 { 182 name: "authenticate user1 with username", 183 req: &requests.Request{ 184 User: requests.User{ 185 Username: testUser1, 186 Password: testPwd1, 187 }, 188 Upstream: requests.Upstream{ 189 BaseURL: "https://localhost", 190 BasePath: "/auth", 191 Method: "local", 192 Realm: "local", 193 }, 194 }, 195 want: map[string]interface{}{ 196 "code": 200, 197 "email": "jsmith@gmail.com", 198 "name": "Smith, John", 199 "roles": []string{"viewer", "editor", "admin"}, 200 "sub": "jsmith", 201 "challenges": []string{"password"}, 202 }, 203 }, 204 { 205 name: "authenticate user2 with username", 206 req: &requests.Request{ 207 User: requests.User{ 208 Username: testUser2, 209 Password: testPwd2, 210 }, 211 Flags: requests.Flags{ 212 Enabled: true, 213 }, 214 Upstream: requests.Upstream{ 215 BaseURL: "https://localhost", 216 BasePath: "/auth", 217 Method: "local", 218 Realm: "local", 219 }, 220 }, 221 want: map[string]interface{}{ 222 "code": 200, 223 "email": "bjones@gmail.com", 224 "roles": []string{"viewer"}, 225 "sub": "bjones", 226 "challenges": []string{"password"}, 227 }, 228 }, 229 { 230 name: "authenticate user1 with email address", 231 req: &requests.Request{ 232 User: requests.User{ 233 Username: testEmail1, 234 Password: testPwd1, 235 }, 236 Upstream: requests.Upstream{ 237 BaseURL: "https://localhost", 238 BasePath: "/auth", 239 Method: "local", 240 Realm: "local", 241 }, 242 }, 243 want: map[string]interface{}{ 244 "code": 200, 245 "email": "jsmith@gmail.com", 246 "name": "Smith, John", 247 "roles": []string{"viewer", "editor", "admin"}, 248 "sub": "jsmith", 249 "challenges": []string{"password"}, 250 }, 251 }, 252 { 253 name: "authenticate user2 with email address", 254 req: &requests.Request{ 255 User: requests.User{ 256 Username: testEmail2, 257 Password: testPwd2, 258 }, 259 Upstream: requests.Upstream{ 260 BaseURL: "https://localhost", 261 BasePath: "/auth", 262 Method: "local", 263 Realm: "local", 264 }, 265 }, 266 want: map[string]interface{}{ 267 "code": 200, 268 "email": "bjones@gmail.com", 269 "roles": []string{"viewer"}, 270 "sub": "bjones", 271 "challenges": []string{"password"}, 272 }, 273 }, 274 { 275 name: "authenticate user1 with username and invalid password", 276 req: &requests.Request{ 277 User: requests.User{ 278 Username: testUser1, 279 Password: testPwd2, 280 }, 281 Upstream: requests.Upstream{ 282 BaseURL: "https://localhost", 283 BasePath: "/auth", 284 Method: "local", 285 Realm: "local", 286 }, 287 }, 288 shouldErr: true, 289 err: errors.ErrAuthFailed.WithArgs(errors.ErrUserPasswordInvalid), 290 }, 291 { 292 name: "authenticate user1 with email address and invalid password", 293 req: &requests.Request{ 294 User: requests.User{ 295 Username: testEmail1, 296 Password: testPwd2, 297 }, 298 }, 299 shouldErr: true, 300 err: errors.ErrAuthFailed.WithArgs(errors.ErrUserPasswordInvalid), 301 }, 302 { 303 name: "authenticate with invalid username", 304 req: &requests.Request{ 305 User: requests.User{ 306 Username: "foobar", 307 Password: "barfoo", 308 }, 309 }, 310 shouldErr: true, 311 err: errors.ErrAuthFailed.WithArgs(errors.ErrDatabaseUserNotFound), 312 }, 313 { 314 name: "perform dummy authentication", 315 req: &requests.Request{ 316 User: requests.User{ 317 Username: "foobar", 318 Password: "barfoo", 319 }, 320 }, 321 shouldErr: true, 322 err: errors.ErrAuthFailed.WithArgs(errors.ErrDatabaseUserNotFound), 323 }, 324 } 325 for _, tc := range testcases { 326 t.Run(tc.name, func(t *testing.T) { 327 var err error 328 got := make(map[string]interface{}) 329 msgs := []string{fmt.Sprintf("test name: %s", tc.name)} 330 msgs = append(msgs, fmt.Sprintf("database path: %s", db.path)) 331 err = db.IdentifyUser(tc.req) 332 333 got["sub"] = tc.req.User.Username 334 got["email"] = tc.req.User.Email 335 if tc.req.User.FullName != "" { 336 got["name"] = tc.req.User.FullName 337 } 338 got["roles"] = tc.req.User.Roles 339 got["challenges"] = tc.req.User.Challenges 340 341 err = db.AuthenticateUser(tc.req) 342 if tests.EvalErrWithLog(t, err, "authenticate", tc.shouldErr, tc.err, msgs) { 343 return 344 } 345 got["code"] = tc.req.Response.Code 346 tests.EvalObjectsWithLog(t, "eval", tc.want, got, msgs) 347 348 user, err := db.getUser(tc.req.User.Username) 349 if err != nil { 350 t.Fatal(err) 351 } 352 userByID, err := db.getUserByID(user.ID) 353 if err != nil { 354 t.Fatal(err) 355 } 356 if diff := cmp.Diff(user, userByID, cmp.AllowUnexported(User{}, EmailAddress{})); diff != "" { 357 tests.WriteLog(t, msgs) 358 t.Fatalf("user by username and id mismatch (-want +got):\n%s", diff) 359 } 360 361 _, err = db.getUserByID("foobar") 362 if tests.EvalErrWithLog(t, err, "authenticate", true, errors.ErrDatabaseUserNotFound, msgs) { 363 return 364 } 365 }) 366 } 367 } 368 369 func TestDatabaseAddUser(t *testing.T) { 370 var databasePath string 371 db, err := createTestDatabase("TestDatabaseAddUser") 372 if err != nil { 373 t.Fatalf("failed to create temp dir: %v", err) 374 } 375 databasePath = db.path 376 // t.Logf("%v", db.path) 377 testcases := []struct { 378 name string 379 req *requests.Request 380 overwritePath string 381 want map[string]interface{} 382 shouldErr bool 383 err error 384 }{ 385 { 386 name: "add user with used username", 387 req: &requests.Request{ 388 User: requests.User{ 389 Username: testUser1, 390 Password: testPwd1, 391 Email: testEmail1, 392 FullName: testFullName1, 393 Roles: testRoles1, 394 }, 395 }, 396 shouldErr: true, 397 err: errors.ErrAddUser.WithArgs(testUser1, "username already in use"), 398 }, 399 { 400 name: "add user with empty username", 401 req: &requests.Request{ 402 User: requests.User{ 403 Username: "", 404 Email: "foobar@barfoo", 405 }, 406 }, 407 shouldErr: true, 408 err: errors.ErrAddUser.WithArgs("", errors.ErrUserPolicyCompliance), 409 }, 410 { 411 name: "add user with used email", 412 req: &requests.Request{ 413 User: requests.User{ 414 Username: "foobar", 415 Password: testPwd1, 416 Email: testEmail1, 417 FullName: testFullName1, 418 Roles: testRoles1, 419 }, 420 }, 421 shouldErr: true, 422 err: errors.ErrAddUser.WithArgs(testEmail1, "email address already in use"), 423 }, 424 425 { 426 name: "fail committing after adding new user", 427 req: &requests.Request{ 428 User: requests.User{ 429 Username: "foobar", 430 Password: NewRandomString(16), 431 Email: "foobar@barfoo", 432 }, 433 }, 434 overwritePath: path.Dir(databasePath), 435 shouldErr: true, 436 err: errors.ErrAddUser.WithArgs("foobar", 437 errors.ErrDatabaseCommit.WithArgs(path.Dir(databasePath), "open "+path.Dir(databasePath)+": is a directory"), 438 ), 439 }, 440 } 441 for _, tc := range testcases { 442 t.Run(tc.name, func(t *testing.T) { 443 var err error 444 db.path = databasePath 445 if tc.overwritePath != "" { 446 db.path = tc.overwritePath 447 } 448 msgs := []string{fmt.Sprintf("test name: %s", tc.name)} 449 msgs = append(msgs, fmt.Sprintf("database path: %s", db.path)) 450 err = db.AddUser(tc.req) 451 if tests.EvalErrWithLog(t, err, "add user", tc.shouldErr, tc.err, msgs) { 452 return 453 } 454 got := make(map[string]interface{}) 455 got["user_count"] = len(db.Users) 456 tests.EvalObjectsWithLog(t, "user passwords", tc.want, got, msgs) 457 }) 458 } 459 } 460 461 func TestDatabaseChangeUserPassword(t *testing.T) { 462 var databasePath string 463 db, err := createTestDatabase("TestDatabaseChangeUserPassword") 464 if err != nil { 465 t.Fatalf("failed to create temp dir: %v", err) 466 } 467 databasePath = db.path 468 // t.Logf("%v", db.path) 469 testcases := []struct { 470 name string 471 req *requests.Request 472 overwritePath string 473 want map[string]interface{} 474 shouldErr bool 475 err error 476 }{ 477 { 478 name: "change user1 password with invalid current password", 479 req: &requests.Request{ 480 User: requests.User{ 481 Username: testUser1, 482 Email: testEmail1, 483 OldPassword: "foobar", 484 Password: NewRandomString(16), 485 }, 486 }, 487 shouldErr: true, 488 err: errors.ErrChangeUserPassword.WithArgs(errors.ErrUserPasswordInvalid), 489 }, 490 { 491 name: "change user1 password", 492 req: &requests.Request{ 493 User: requests.User{ 494 Username: testUser1, 495 Email: testEmail1, 496 OldPassword: testPwd1, 497 Password: NewRandomString(16), 498 }, 499 }, 500 want: map[string]interface{}{ 501 "password_count": 2, 502 }, 503 }, 504 { 505 name: "change password of invalid user", 506 req: &requests.Request{ 507 User: requests.User{ 508 Username: "foobar", 509 Email: "foobar@barfoo", 510 OldPassword: "foobar", 511 Password: NewRandomString(16), 512 }, 513 }, 514 shouldErr: true, 515 err: errors.ErrChangeUserPassword.WithArgs(errors.ErrDatabaseUserNotFound), 516 }, 517 { 518 name: "fail committing after change user2 password", 519 req: &requests.Request{ 520 User: requests.User{ 521 Username: testUser2, 522 Email: testEmail2, 523 OldPassword: testPwd2, 524 Password: NewRandomString(16), 525 }, 526 }, 527 overwritePath: path.Dir(databasePath), 528 shouldErr: true, 529 err: errors.ErrChangeUserPassword.WithArgs( 530 errors.ErrDatabaseCommit.WithArgs(path.Dir(databasePath), "open "+path.Dir(databasePath)+": is a directory"), 531 ), 532 }, 533 } 534 for _, tc := range testcases { 535 t.Run(tc.name, func(t *testing.T) { 536 var err error 537 db.path = databasePath 538 if tc.overwritePath != "" { 539 db.path = tc.overwritePath 540 } 541 msgs := []string{fmt.Sprintf("test name: %s", tc.name)} 542 msgs = append(msgs, fmt.Sprintf("database path: %s", db.path)) 543 544 err = db.ChangeUserPassword(tc.req) 545 if tests.EvalErrWithLog(t, err, "change password", tc.shouldErr, tc.err, msgs) { 546 return 547 } 548 549 req := &requests.Request{User: requests.User{Username: tc.req.User.Username, Password: tc.req.User.Password}} 550 if err := db.AuthenticateUser(req); err != nil { 551 t.Fatalf("expected authentication success, but got failure: %v", err) 552 } 553 554 user, err := db.getUser(tc.req.User.Username) 555 if err != nil { 556 t.Fatal(err) 557 } 558 got := make(map[string]interface{}) 559 got["password_count"] = len(user.Passwords) 560 tests.EvalObjectsWithLog(t, "user passwords", tc.want, got, msgs) 561 }) 562 } 563 } 564 565 func TestDatabaseUserPublicKey(t *testing.T) { 566 var databasePath string 567 db, err := createTestDatabase("TestDatabaseUserPublicKey") 568 if err != nil { 569 t.Fatalf("failed to create temp dir: %v", err) 570 } 571 databasePath = db.path 572 testcases := []struct { 573 name string 574 operation string 575 usage string 576 overwriteUsage string 577 overwritePath string 578 keyID string 579 keyAlgorithm string 580 pubKeyType string 581 comment string 582 username string 583 email string 584 want map[string]interface{} 585 shouldErr bool 586 err error 587 }{ 588 { 589 name: "add disabled openssh public key", 590 operation: "add", 591 usage: "ssh", 592 keyAlgorithm: "rsa", 593 pubKeyType: "openssh", 594 comment: testEmail1, 595 username: testUser1, 596 email: testEmail1, 597 want: map[string]interface{}{ 598 "key_count": 1, 599 }, 600 }, 601 602 { 603 name: "add openssh public key", 604 operation: "add", 605 usage: "ssh", 606 keyAlgorithm: "rsa", 607 pubKeyType: "openssh", 608 comment: testEmail1, 609 username: testUser1, 610 email: testEmail1, 611 want: map[string]interface{}{ 612 "key_count": 2, 613 }, 614 }, 615 { 616 name: "add rsa public key", 617 operation: "add", 618 usage: "ssh", 619 keyAlgorithm: "rsa", 620 pubKeyType: "openssh", 621 comment: testEmail1, 622 username: testUser1, 623 email: testEmail1, 624 want: map[string]interface{}{ 625 "key_count": 3, 626 }, 627 }, 628 { 629 name: "delete all public keys", 630 operation: "delete", 631 username: testUser1, 632 email: testEmail1, 633 usage: "ssh", 634 want: map[string]interface{}{ 635 "key_count": 0, 636 }, 637 }, 638 { 639 name: "readd rsa public key", 640 operation: "add", 641 usage: "ssh", 642 keyAlgorithm: "rsa", 643 pubKeyType: "openssh", 644 comment: testEmail1, 645 username: testUser1, 646 email: testEmail1, 647 want: map[string]interface{}{ 648 "key_count": 1, 649 }, 650 }, 651 { 652 name: "delete non-existing public key", 653 operation: "delete", 654 username: testUser1, 655 email: testEmail1, 656 usage: "ssh", 657 keyID: "foobar", 658 shouldErr: true, 659 err: errors.ErrDeletePublicKey.WithArgs("foobar", "not found"), 660 }, 661 { 662 name: "add rsa public key with non-existing username", 663 operation: "add", 664 username: "foobar", 665 email: "foobar@barfoo", 666 usage: "ssh", 667 keyAlgorithm: "rsa", 668 pubKeyType: "openssh", 669 shouldErr: true, 670 err: errors.ErrAddPublicKey.WithArgs("ssh", errors.ErrDatabaseUserNotFound), 671 }, 672 { 673 name: "add rsa public key with mismatch user and email", 674 operation: "add", 675 username: testUser1, 676 email: testEmail2, 677 usage: "ssh", 678 keyAlgorithm: "rsa", 679 pubKeyType: "openssh", 680 shouldErr: true, 681 err: errors.ErrAddPublicKey.WithArgs("ssh", errors.ErrDatabaseInvalidUser), 682 }, 683 { 684 name: "add rsa public key with non-existing email address", 685 operation: "add", 686 username: testUser1, 687 email: "foobar@barfoo", 688 usage: "ssh", 689 keyAlgorithm: "rsa", 690 pubKeyType: "openssh", 691 shouldErr: true, 692 err: errors.ErrAddPublicKey.WithArgs("ssh", errors.ErrDatabaseUserNotFound), 693 }, 694 { 695 name: "get public keys with non-existing email address", 696 operation: "get", 697 username: testUser1, 698 email: "foobar@barfoo", 699 usage: "ssh", 700 keyAlgorithm: "rsa", 701 pubKeyType: "openssh", 702 shouldErr: true, 703 err: errors.ErrGetPublicKeys.WithArgs("ssh", errors.ErrDatabaseUserNotFound), 704 }, 705 706 { 707 name: "delete public key with non-existing email address", 708 operation: "delete", 709 username: testUser1, 710 email: "foobar@barfoo", 711 usage: "ssh", 712 keyAlgorithm: "rsa", 713 shouldErr: true, 714 keyID: "barfoo", 715 err: errors.ErrDeletePublicKey.WithArgs("barfoo", errors.ErrDatabaseUserNotFound), 716 }, 717 { 718 name: "add invalid public key usage", 719 operation: "add", 720 username: testUser1, 721 email: testEmail1, 722 usage: "ssh", 723 overwriteUsage: "foobar", 724 keyAlgorithm: "rsa", 725 pubKeyType: "openssh", 726 shouldErr: true, 727 err: errors.ErrAddPublicKey.WithArgs("foobar", errors.ErrPublicKeyInvalidUsage.WithArgs("foobar")), 728 }, 729 { 730 name: "fail to commit when adding rsa public key", 731 operation: "add", 732 usage: "ssh", 733 keyAlgorithm: "rsa", 734 pubKeyType: "openssh", 735 comment: testEmail1, 736 username: testUser1, 737 email: testEmail1, 738 overwritePath: path.Dir(databasePath), 739 shouldErr: true, 740 err: errors.ErrAddPublicKey.WithArgs("ssh", 741 errors.ErrDatabaseCommit.WithArgs(path.Dir(databasePath), "open "+path.Dir(databasePath)+": is a directory"), 742 ), 743 }, 744 { 745 name: "fail to commit when deleting rsa public key", 746 operation: "delete", 747 username: testUser1, 748 email: testEmail1, 749 usage: "ssh", 750 overwritePath: path.Dir(databasePath), 751 shouldErr: true, 752 err: errors.ErrDeletePublicKey.WithArgs("ssh", 753 errors.ErrDatabaseCommit.WithArgs(path.Dir(databasePath), "open "+path.Dir(databasePath)+": is a directory"), 754 ), 755 }, 756 } 757 for _, tc := range testcases { 758 t.Run(tc.name, func(t *testing.T) { 759 var err error 760 db.path = databasePath 761 msgs := []string{fmt.Sprintf("test name: %s", tc.name)} 762 msgs = append(msgs, fmt.Sprintf("database path: %s", db.path)) 763 switch tc.operation { 764 case "add": 765 r := requests.NewRequest() 766 r.User.Username = tc.username 767 r.User.Email = tc.email 768 r.Key.Usage = tc.usage 769 r.Key.Comment = tc.comment 770 _, publicKey := tests.GetCryptoKeyPair(t, tc.keyAlgorithm, tc.pubKeyType) 771 r.Key.Payload = publicKey 772 if tc.overwriteUsage != "" { 773 r.Key.Usage = tc.overwriteUsage 774 } 775 if tc.overwritePath != "" { 776 db.path = tc.overwritePath 777 } 778 err = db.AddPublicKey(r) 779 if tests.EvalErrWithLog(t, err, "add public key", tc.shouldErr, tc.err, msgs) { 780 return 781 } 782 case "get": 783 case "delete": 784 r := requests.NewRequest() 785 r.User.Username = tc.username 786 r.User.Email = tc.email 787 r.Key.Usage = tc.usage 788 err = db.GetPublicKeys(r) 789 if tc.keyID != "" { 790 // Delete specific key. 791 r.Key.ID = tc.keyID 792 err = db.DeletePublicKey(r) 793 if tests.EvalErrWithLog(t, err, "delete public key by id", tc.shouldErr, tc.err, msgs) { 794 return 795 } 796 break 797 } 798 // Delete all keys. 799 if tc.overwritePath != "" { 800 db.path = tc.overwritePath 801 } 802 bundle := r.Response.Payload.(*PublicKeyBundle) 803 var arr []string 804 for _, k := range bundle.Get() { 805 arr = append(arr, k.ID) 806 } 807 for _, k := range arr { 808 r.Key.ID = k 809 err = db.DeletePublicKey(r) 810 if tests.EvalErrWithLog(t, err, "delete public key", tc.shouldErr, tc.err, msgs) { 811 return 812 } 813 } 814 case "": 815 t.Fatal("empty test operation") 816 default: 817 t.Fatalf("unsupported test operation: %s", tc.operation) 818 } 819 820 r := requests.NewRequest() 821 r.User.Username = tc.username 822 r.User.Email = tc.email 823 r.Key.Usage = tc.usage 824 err = db.GetPublicKeys(r) 825 if tests.EvalErrWithLog(t, err, "get public keys", tc.shouldErr, tc.err, msgs) { 826 return 827 } 828 bundle := r.Response.Payload.(*PublicKeyBundle) 829 got := make(map[string]interface{}) 830 got["key_count"] = bundle.Size() 831 tests.EvalObjectsWithLog(t, "user", tc.want, got, msgs) 832 }) 833 } 834 } 835 836 func TestDatabaseUserMfaToken(t *testing.T) { 837 var databasePath string 838 db, err := createTestDatabase("TestDatabaseUserMfaToken") 839 if err != nil { 840 t.Fatalf("failed to create temp dir: %v", err) 841 } 842 databasePath = db.path 843 /* 844 t.Logf("%v", db.path) 845 for _, u := range db.Users { 846 for _, n := range u.Names { 847 t.Logf("user %s, name: %v", u.Username, n) 848 } 849 for _, p := range u.Passwords { 850 t.Logf("user %s, password: %v", u.Username, p) 851 } 852 } 853 */ 854 855 testcases := []struct { 856 name string 857 operation string 858 req *requests.Request 859 overwritePath string 860 want map[string]interface{} 861 shouldErr bool 862 err error 863 }{ 864 { 865 name: "add disabled totp app token with sha1", 866 operation: "add", 867 req: &requests.Request{ 868 User: requests.User{ 869 Username: testUser1, 870 Email: testEmail1, 871 }, 872 MfaToken: requests.MfaToken{ 873 Comment: "ms auth app 30", 874 Type: "totp", 875 Secret: "c71ca4c68bc14ec5b4ab8d3c3be02ddd2c", 876 Algorithm: "sha1", 877 Period: 30, 878 Digits: 6, 879 Disabled: true, 880 }, 881 }, 882 want: map[string]interface{}{ 883 "token_count": 0, 884 }, 885 }, 886 { 887 name: "add totp app token with sha1", 888 operation: "add", 889 req: &requests.Request{ 890 User: requests.User{ 891 Username: testUser1, 892 Email: testEmail1, 893 }, 894 MfaToken: requests.MfaToken{ 895 Comment: "ms auth app", 896 Type: "totp", 897 Secret: "c71ca4c68bc14ec5b4ab8d3c3b63802c", 898 Algorithm: "sha1", 899 Period: 30, 900 Digits: 6, 901 }, 902 }, 903 want: map[string]interface{}{ 904 "token_count": 1, 905 }, 906 }, 907 { 908 name: "remove all mfa tokens", 909 operation: "delete", 910 req: &requests.Request{ 911 User: requests.User{ 912 Username: testUser1, 913 Email: testEmail1, 914 }, 915 MfaToken: requests.MfaToken{}, 916 }, 917 want: map[string]interface{}{ 918 "token_count": 0, 919 }, 920 }, 921 { 922 name: "readd totp app token with sha1", 923 operation: "add", 924 req: &requests.Request{ 925 User: requests.User{ 926 Username: testUser1, 927 Email: testEmail1, 928 }, 929 MfaToken: requests.MfaToken{ 930 Comment: "ms auth app", 931 Type: "totp", 932 Secret: "c71ca4c68bc14ec5b4ab8d3c3b63802c", 933 Algorithm: "sha1", 934 Period: 30, 935 Digits: 6, 936 }, 937 }, 938 want: map[string]interface{}{ 939 "token_count": 1, 940 }, 941 }, 942 { 943 name: "delete non-existing mfa token", 944 operation: "delete", 945 req: &requests.Request{ 946 User: requests.User{ 947 Username: testUser1, 948 Email: testEmail1, 949 }, 950 MfaToken: requests.MfaToken{ 951 ID: "foobar", 952 }, 953 }, 954 shouldErr: true, 955 err: errors.ErrDeleteMfaToken.WithArgs("foobar", "not found"), 956 }, 957 { 958 name: "add token with non-existing username", 959 operation: "add", 960 req: &requests.Request{ 961 User: requests.User{ 962 Username: "foobar", 963 Email: "foobar@barfoo", 964 }, 965 }, 966 shouldErr: true, 967 err: errors.ErrAddMfaToken.WithArgs(errors.ErrDatabaseUserNotFound), 968 }, 969 { 970 name: "add token with duplicate secret", 971 operation: "add", 972 req: &requests.Request{ 973 User: requests.User{ 974 Username: testUser1, 975 Email: testEmail1, 976 }, 977 MfaToken: requests.MfaToken{ 978 Comment: "ms auth app", 979 Type: "totp", 980 Secret: "c71ca4c68bc14ec5b4ab8d3c3b63802c", 981 Algorithm: "sha1", 982 Period: 30, 983 Digits: 6, 984 }, 985 }, 986 shouldErr: true, 987 err: errors.ErrAddMfaToken.WithArgs(errors.ErrDuplicateMfaTokenSecret), 988 }, 989 { 990 name: "add token with duplicate comment", 991 operation: "add", 992 req: &requests.Request{ 993 User: requests.User{ 994 Username: testUser1, 995 Email: testEmail1, 996 }, 997 MfaToken: requests.MfaToken{ 998 Comment: "ms auth app", 999 Type: "totp", 1000 Secret: "d71ca4c68bc14ec5b4ab8d3c3b63802c1", 1001 Algorithm: "sha1", 1002 Period: 30, 1003 Digits: 6, 1004 }, 1005 }, 1006 shouldErr: true, 1007 err: errors.ErrAddMfaToken.WithArgs(errors.ErrDuplicateMfaTokenComment), 1008 }, 1009 1010 { 1011 name: "get tokens with mismatch user and email", 1012 operation: "get", 1013 req: &requests.Request{ 1014 User: requests.User{ 1015 Username: testUser1, 1016 Email: testEmail2, 1017 }, 1018 }, 1019 shouldErr: true, 1020 err: errors.ErrGetMfaTokens.WithArgs(errors.ErrDatabaseInvalidUser), 1021 }, 1022 1023 { 1024 name: "delete mfa token with mismatch user and email", 1025 operation: "delete", 1026 req: &requests.Request{ 1027 User: requests.User{ 1028 Username: testUser1, 1029 Email: testEmail2, 1030 }, 1031 MfaToken: requests.MfaToken{ 1032 ID: "foobar", 1033 }, 1034 }, 1035 shouldErr: true, 1036 err: errors.ErrDeleteMfaToken.WithArgs("foobar", errors.ErrDatabaseInvalidUser), 1037 }, 1038 { 1039 name: "fail to commit when adding mfa token", 1040 operation: "add", 1041 req: &requests.Request{ 1042 User: requests.User{ 1043 Username: testUser1, 1044 Email: testEmail1, 1045 }, 1046 MfaToken: requests.MfaToken{ 1047 Comment: "ms auth app 20", 1048 Type: "totp", 1049 Secret: "dd71ca4c68bc14ec5b4ab8d3c3b63802c", 1050 Algorithm: "sha1", 1051 Period: 30, 1052 Digits: 6, 1053 }, 1054 }, 1055 overwritePath: path.Dir(databasePath), 1056 shouldErr: true, 1057 err: errors.ErrAddMfaToken.WithArgs( 1058 errors.ErrDatabaseCommit.WithArgs(path.Dir(databasePath), "open "+path.Dir(databasePath)+": is a directory"), 1059 ), 1060 }, 1061 { 1062 name: "fail to commit when deleting mfa token", 1063 operation: "delete", 1064 req: &requests.Request{ 1065 User: requests.User{ 1066 Username: testUser1, 1067 Email: testEmail1, 1068 }, 1069 MfaToken: requests.MfaToken{ 1070 ID: "zzzzzzzzzzzzzzzzzzzzzzzzzz5h3s765Tpx5Laa", 1071 }, 1072 }, 1073 overwritePath: path.Dir(databasePath), 1074 shouldErr: true, 1075 err: errors.ErrDeleteMfaToken.WithArgs("zzzzzzzzzzzzzzzzzzzzzzzzzz5h3s765Tpx5Laa", 1076 errors.ErrDatabaseCommit.WithArgs(path.Dir(databasePath), "open "+path.Dir(databasePath)+": is a directory"), 1077 ), 1078 }, 1079 } 1080 for _, tc := range testcases { 1081 t.Run(tc.name, func(t *testing.T) { 1082 var err error 1083 db.path = databasePath 1084 msgs := []string{fmt.Sprintf("test name: %s", tc.name)} 1085 msgs = append(msgs, fmt.Sprintf("database path: %s", db.path)) 1086 switch tc.operation { 1087 case "add": 1088 if tc.overwritePath != "" { 1089 db.path = tc.overwritePath 1090 } 1091 if tc.req.MfaToken.Type == "totp" && tc.req.MfaToken.Passcode == "" { 1092 if err := generateTestPasscode(tc.req, true); err != nil { 1093 t.Fatalf("unexpected failure during passcode generation: %v", err) 1094 } 1095 } 1096 err = db.AddMfaToken(tc.req) 1097 if tests.EvalErrWithLog(t, err, "add mfa token", tc.shouldErr, tc.err, msgs) { 1098 return 1099 } 1100 case "get": 1101 case "delete": 1102 if tc.overwritePath != "" { 1103 db.path = tc.overwritePath 1104 } 1105 err = db.GetMfaTokens(tc.req) 1106 if tc.req.MfaToken.ID != "" { 1107 // Delete specific key. 1108 if tc.req.MfaToken.ID == "zzzzzzzzzzzzzzzzzzzzzzzzzz5h3s765Tpx5Laa" { 1109 user, err := db.getUser(tc.req.User.Username) 1110 if err != nil { 1111 t.Fatal(err) 1112 } 1113 token := user.MfaTokens[0] 1114 token.ID = tc.req.MfaToken.ID 1115 } 1116 err = db.DeleteMfaToken(tc.req) 1117 if tests.EvalErrWithLog(t, err, "delete mfa token by id", tc.shouldErr, tc.err, msgs) { 1118 return 1119 } 1120 break 1121 } 1122 // Delete all keys. 1123 bundle := tc.req.Response.Payload.(*MfaTokenBundle) 1124 var arr []string 1125 for _, k := range bundle.Get() { 1126 arr = append(arr, k.ID) 1127 } 1128 for _, k := range arr { 1129 tc.req.MfaToken.ID = k 1130 err = db.DeleteMfaToken(tc.req) 1131 if tests.EvalErrWithLog(t, err, "delete mfa token", tc.shouldErr, tc.err, msgs) { 1132 return 1133 } 1134 } 1135 case "": 1136 t.Fatal("empty test operation") 1137 default: 1138 t.Fatalf("unsupported test operation: %s", tc.operation) 1139 } 1140 1141 err = db.GetMfaTokens(tc.req) 1142 if tests.EvalErrWithLog(t, err, "get mfa tokens", tc.shouldErr, tc.err, msgs) { 1143 return 1144 } 1145 bundle := tc.req.Response.Payload.(*MfaTokenBundle) 1146 got := make(map[string]interface{}) 1147 got["token_count"] = bundle.Size() 1148 tests.EvalObjectsWithLog(t, "output", tc.want, got, msgs) 1149 }) 1150 } 1151 } 1152 1153 func TestDatabaseGetUsers(t *testing.T) { 1154 var databasePath string 1155 db, err := createTestDatabase("TestDatabaseGetUsers") 1156 if err != nil { 1157 t.Fatalf("failed to create temp dir: %v", err) 1158 } 1159 ts := time.Now() 1160 databasePath = db.path 1161 testcases := []struct { 1162 name string 1163 operation string 1164 req *requests.Request 1165 overwritePath string 1166 want map[string]interface{} 1167 shouldErr bool 1168 err error 1169 }{ 1170 { 1171 name: "get users", 1172 req: &requests.Request{ 1173 User: requests.User{ 1174 Username: testUser1, 1175 Email: testEmail1, 1176 }, 1177 }, 1178 want: map[string]interface{}{ 1179 "user_count": 2, 1180 "users": []*UserMetadata{ 1181 { 1182 ID: "000000000000000000000000000000000001", 1183 Username: "jsmith", 1184 Name: "Smith, John", 1185 Email: "jsmith@gmail.com", 1186 LastModified: ts, 1187 Created: ts, 1188 }, 1189 { 1190 ID: "000000000000000000000000000000000002", 1191 Username: "bjones", 1192 Email: "bjones@gmail.com", 1193 LastModified: ts, 1194 Created: ts, 1195 }, 1196 }, 1197 }, 1198 }, 1199 } 1200 for _, tc := range testcases { 1201 t.Run(tc.name, func(t *testing.T) { 1202 var err error 1203 db.path = databasePath 1204 msgs := []string{fmt.Sprintf("test name: %s", tc.name)} 1205 msgs = append(msgs, fmt.Sprintf("database path: %s", db.path)) 1206 1207 err = db.GetUsers(tc.req) 1208 if tests.EvalErrWithLog(t, err, "get users", tc.shouldErr, tc.err, msgs) { 1209 return 1210 } 1211 bundle := tc.req.Response.Payload.(*UserMetadataBundle) 1212 got := make(map[string]interface{}) 1213 got["user_count"] = bundle.Size() 1214 users := bundle.Get() 1215 for i, user := range users { 1216 user.ID = fmt.Sprintf("%036d", i+1) 1217 user.LastModified = ts 1218 user.Created = ts 1219 } 1220 got["users"] = bundle.Get() 1221 tests.EvalObjectsWithLog(t, "output", tc.want, got, msgs) 1222 }) 1223 } 1224 } 1225 1226 func TestDatabasePolicy(t *testing.T) { 1227 var databasePath string 1228 db, err := createTestDatabase("TestDatabasePolicy") 1229 if err != nil { 1230 t.Fatalf("failed to create temp dir: %v", err) 1231 } 1232 databasePath = db.path 1233 testcases := []struct { 1234 name string 1235 operation string 1236 req *requests.Request 1237 overwritePath string 1238 want map[string]interface{} 1239 shouldErr bool 1240 err error 1241 }{ 1242 { 1243 name: "get username and password policies", 1244 want: map[string]interface{}{ 1245 "username_policy": UserPolicy{ 1246 MinLength: 3, 1247 MaxLength: 50, 1248 AllowNonAlphaNumeric: false, 1249 AllowUppercase: false, 1250 }, 1251 "password_policy": PasswordPolicy{ 1252 KeepVersions: 10, 1253 MinLength: 8, 1254 MaxLength: 128, 1255 RequireUppercase: false, 1256 RequireLowercase: false, 1257 RequireNumber: false, 1258 RequireNonAlphaNumeric: false, 1259 BlockReuse: false, 1260 BlockPasswordChange: false, 1261 }, 1262 "username_policy_summary": "A username should be 3-50 character long string with lowercase, alpha-numeric characters", 1263 "username_policy_regex": "^[a-z][a-z0-9]{2,49}$", 1264 "password_policy_summary": "A password should be 8-128 character long string", 1265 "password_policy_regex": "^.{8,128}$", 1266 }, 1267 }, 1268 } 1269 for _, tc := range testcases { 1270 t.Run(tc.name, func(t *testing.T) { 1271 db.path = databasePath 1272 msgs := []string{fmt.Sprintf("test name: %s", tc.name)} 1273 msgs = append(msgs, fmt.Sprintf("database path: %s", db.path)) 1274 got := make(map[string]interface{}) 1275 got["username_policy"] = db.Policy.User 1276 got["password_policy"] = db.Policy.Password 1277 got["username_policy_regex"] = db.GetUsernamePolicyRegex() 1278 got["password_policy_regex"] = db.GetPasswordPolicyRegex() 1279 got["username_policy_summary"] = db.GetUsernamePolicySummary() 1280 got["password_policy_summary"] = db.GetPasswordPolicySummary() 1281 tests.EvalObjectsWithLog(t, "output", tc.want, got, msgs) 1282 }) 1283 } 1284 }