summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/hashserv/tests.py
diff options
context:
space:
mode:
authorJoshua Watt <JPEWhacker@gmail.com>2023-11-03 08:26:31 -0600
committerRichard Purdie <richard.purdie@linuxfoundation.org>2023-11-09 17:33:03 +0000
commit1af725b2eca63fa113cedb6d77eb5c5f1de6e2f0 (patch)
treeadf200a0b03b8ee1f1a56c55e2ec657dcc7ed04a /bitbake/lib/hashserv/tests.py
parent6e67b000efb89c4e3121fd907a47dc7042c07bed (diff)
downloadpoky-1af725b2eca63fa113cedb6d77eb5c5f1de6e2f0.tar.gz
bitbake: hashserv: Add user permissions
Adds support for the hashserver to have per-user permissions. User management is done via a new "auth" RPC API where a client can authenticate itself with the server using a randomly generated token. The user can then be given permissions to read, report, manage the database, or manage other users. In addition to explicit user logins, the server supports anonymous users which is what all users start as before they make the "auth" RPC call. Anonymous users can be assigned a set of permissions by the server, making it unnecessary for users to authenticate to use the server. The set of Anonymous permissions defines the default behavior of the server, for example if set to "@read", Anonymous users are unable to report equivalent hashes with authenticating. Similarly, setting the Anonymous permissions to "@none" would require authentication for users to perform any action. User creation and management is entirely manual (although bitbake-hashclient is very useful as a front end). There are many different mechanisms that could be implemented to allow user self-registration (e.g. OAuth, LDAP, etc.), and implementing these is outside the scope of the server. Instead, it is recommended to implement a registration service that validates users against the necessary service, then adds them as a user in the hash equivalence server. (Bitbake rev: 69e5417413ee2414fffaa7dd38057573bac56e35) Signed-off-by: Joshua Watt <JPEWhacker@gmail.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/hashserv/tests.py')
-rw-r--r--bitbake/lib/hashserv/tests.py276
1 files changed, 267 insertions, 9 deletions
diff --git a/bitbake/lib/hashserv/tests.py b/bitbake/lib/hashserv/tests.py
index e9a361dc4b..f92f37c459 100644
--- a/bitbake/lib/hashserv/tests.py
+++ b/bitbake/lib/hashserv/tests.py
@@ -6,6 +6,8 @@
6# 6#
7 7
8from . import create_server, create_client 8from . import create_server, create_client
9from .server import DEFAULT_ANON_PERMS, ALL_PERMISSIONS
10from bb.asyncrpc import InvokeError
9import hashlib 11import hashlib
10import logging 12import logging
11import multiprocessing 13import multiprocessing
@@ -29,8 +31,9 @@ class HashEquivalenceTestSetup(object):
29 METHOD = 'TestMethod' 31 METHOD = 'TestMethod'
30 32
31 server_index = 0 33 server_index = 0
34 client_index = 0
32 35
33 def start_server(self, dbpath=None, upstream=None, read_only=False, prefunc=server_prefunc): 36 def start_server(self, dbpath=None, upstream=None, read_only=False, prefunc=server_prefunc, anon_perms=DEFAULT_ANON_PERMS, admin_username=None, admin_password=None):
34 self.server_index += 1 37 self.server_index += 1
35 if dbpath is None: 38 if dbpath is None:
36 dbpath = self.make_dbpath() 39 dbpath = self.make_dbpath()
@@ -45,7 +48,10 @@ class HashEquivalenceTestSetup(object):
45 server = create_server(self.get_server_addr(self.server_index), 48 server = create_server(self.get_server_addr(self.server_index),
46 dbpath, 49 dbpath,
47 upstream=upstream, 50 upstream=upstream,
48 read_only=read_only) 51 read_only=read_only,
52 anon_perms=anon_perms,
53 admin_username=admin_username,
54 admin_password=admin_password)
49 server.dbpath = dbpath 55 server.dbpath = dbpath
50 56
51 server.serve_as_process(prefunc=prefunc, args=(self.server_index,)) 57 server.serve_as_process(prefunc=prefunc, args=(self.server_index,))
@@ -56,18 +62,31 @@ class HashEquivalenceTestSetup(object):
56 def make_dbpath(self): 62 def make_dbpath(self):
57 return os.path.join(self.temp_dir.name, "db%d.sqlite" % self.server_index) 63 return os.path.join(self.temp_dir.name, "db%d.sqlite" % self.server_index)
58 64
59 def start_client(self, server_address): 65 def start_client(self, server_address, username=None, password=None):
60 def cleanup_client(client): 66 def cleanup_client(client):
61 client.close() 67 client.close()
62 68
63 client = create_client(server_address) 69 client = create_client(server_address, username=username, password=password)
64 self.addCleanup(cleanup_client, client) 70 self.addCleanup(cleanup_client, client)
65 71
66 return client 72 return client
67 73
68 def start_test_server(self): 74 def start_test_server(self):
69 server = self.start_server() 75 self.server = self.start_server()
70 return server.address 76 return self.server.address
77
78 def start_auth_server(self):
79 self.auth_server = self.start_server(self.server.dbpath, anon_perms=[], admin_username="admin", admin_password="password")
80 self.admin_client = self.start_client(self.auth_server.address, username="admin", password="password")
81 return self.admin_client
82
83 def auth_client(self, user):
84 return self.start_client(self.auth_server.address, user["username"], user["token"])
85
86 def auth_perms(self, *permissions):
87 self.client_index += 1
88 user = self.admin_client.new_user(f"user-{self.client_index}", permissions)
89 return self.auth_client(user)
71 90
72 def setUp(self): 91 def setUp(self):
73 if sys.version_info < (3, 5, 0): 92 if sys.version_info < (3, 5, 0):
@@ -86,18 +105,21 @@ class HashEquivalenceTestSetup(object):
86 105
87 106
88class HashEquivalenceCommonTests(object): 107class HashEquivalenceCommonTests(object):
89 def test_create_hash(self): 108 def create_test_hash(self, client):
90 # Simple test that hashes can be created 109 # Simple test that hashes can be created
91 taskhash = '35788efcb8dfb0a02659d81cf2bfd695fb30faf9' 110 taskhash = '35788efcb8dfb0a02659d81cf2bfd695fb30faf9'
92 outhash = '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f' 111 outhash = '2765d4a5884be49b28601445c2760c5f21e7e5c0ee2b7e3fce98fd7e5970796f'
93 unihash = 'f46d3fbb439bd9b921095da657a4de906510d2cd' 112 unihash = 'f46d3fbb439bd9b921095da657a4de906510d2cd'
94 113
95 self.assertClientGetHash(self.client, taskhash, None) 114 self.assertClientGetHash(client, taskhash, None)
96 115
97 result = self.client.report_unihash(taskhash, self.METHOD, outhash, unihash) 116 result = client.report_unihash(taskhash, self.METHOD, outhash, unihash)
98 self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash') 117 self.assertEqual(result['unihash'], unihash, 'Server returned bad unihash')
99 return taskhash, outhash, unihash 118 return taskhash, outhash, unihash
100 119
120 def test_create_hash(self):
121 return self.create_test_hash(self.client)
122
101 def test_create_equivalent(self): 123 def test_create_equivalent(self):
102 # Tests that a second reported task with the same outhash will be 124 # Tests that a second reported task with the same outhash will be
103 # assigned the same unihash 125 # assigned the same unihash
@@ -471,6 +493,242 @@ class HashEquivalenceCommonTests(object):
471 # shares a taskhash with Task 2 493 # shares a taskhash with Task 2
472 self.assertClientGetHash(self.client, taskhash2, unihash2) 494 self.assertClientGetHash(self.client, taskhash2, unihash2)
473 495
496 def test_auth_read_perms(self):
497 admin_client = self.start_auth_server()
498
499 # Create hashes with non-authenticated server
500 taskhash, outhash, unihash = self.test_create_hash()
501
502 # Validate hash can be retrieved using authenticated client
503 with self.auth_perms("@read") as client:
504 self.assertClientGetHash(client, taskhash, unihash)
505
506 with self.auth_perms() as client, self.assertRaises(InvokeError):
507 self.assertClientGetHash(client, taskhash, unihash)
508
509 def test_auth_report_perms(self):
510 admin_client = self.start_auth_server()
511
512 # Without read permission, the user is completely denied
513 with self.auth_perms() as client, self.assertRaises(InvokeError):
514 self.create_test_hash(client)
515
516 # Read permission allows the call to succeed, but it doesn't record
517 # anythin in the database
518 with self.auth_perms("@read") as client:
519 taskhash, outhash, unihash = self.create_test_hash(client)
520 self.assertClientGetHash(client, taskhash, None)
521
522 # Report permission alone is insufficient
523 with self.auth_perms("@report") as client, self.assertRaises(InvokeError):
524 self.create_test_hash(client)
525
526 # Read and report permission actually modify the database
527 with self.auth_perms("@read", "@report") as client:
528 taskhash, outhash, unihash = self.create_test_hash(client)
529 self.assertClientGetHash(client, taskhash, unihash)
530
531 def test_auth_no_token_refresh_from_anon_user(self):
532 self.start_auth_server()
533
534 with self.start_client(self.auth_server.address) as client, self.assertRaises(InvokeError):
535 client.refresh_token()
536
537 def assertUserCanAuth(self, user):
538 with self.start_client(self.auth_server.address) as client:
539 client.auth(user["username"], user["token"])
540
541 def assertUserCannotAuth(self, user):
542 with self.start_client(self.auth_server.address) as client, self.assertRaises(InvokeError):
543 client.auth(user["username"], user["token"])
544
545 def test_auth_self_token_refresh(self):
546 admin_client = self.start_auth_server()
547
548 # Create a new user with no permissions
549 user = admin_client.new_user("test-user", [])
550
551 with self.auth_client(user) as client:
552 new_user = client.refresh_token()
553
554 self.assertEqual(user["username"], new_user["username"])
555 self.assertNotEqual(user["token"], new_user["token"])
556 self.assertUserCanAuth(new_user)
557 self.assertUserCannotAuth(user)
558
559 # Explicitly specifying with your own username is fine also
560 with self.auth_client(new_user) as client:
561 new_user2 = client.refresh_token(user["username"])
562
563 self.assertEqual(user["username"], new_user2["username"])
564 self.assertNotEqual(user["token"], new_user2["token"])
565 self.assertUserCanAuth(new_user2)
566 self.assertUserCannotAuth(new_user)
567 self.assertUserCannotAuth(user)
568
569 def test_auth_token_refresh(self):
570 admin_client = self.start_auth_server()
571
572 user = admin_client.new_user("test-user", [])
573
574 with self.auth_perms() as client, self.assertRaises(InvokeError):
575 client.refresh_token(user["username"])
576
577 with self.auth_perms("@user-admin") as client:
578 new_user = client.refresh_token(user["username"])
579
580 self.assertEqual(user["username"], new_user["username"])
581 self.assertNotEqual(user["token"], new_user["token"])
582 self.assertUserCanAuth(new_user)
583 self.assertUserCannotAuth(user)
584
585 def test_auth_self_get_user(self):
586 admin_client = self.start_auth_server()
587
588 user = admin_client.new_user("test-user", [])
589 user_info = user.copy()
590 del user_info["token"]
591
592 with self.auth_client(user) as client:
593 info = client.get_user()
594 self.assertEqual(info, user_info)
595
596 # Explicitly asking for your own username is fine also
597 info = client.get_user(user["username"])
598 self.assertEqual(info, user_info)
599
600 def test_auth_get_user(self):
601 admin_client = self.start_auth_server()
602
603 user = admin_client.new_user("test-user", [])
604 user_info = user.copy()
605 del user_info["token"]
606
607 with self.auth_perms() as client, self.assertRaises(InvokeError):
608 client.get_user(user["username"])
609
610 with self.auth_perms("@user-admin") as client:
611 info = client.get_user(user["username"])
612 self.assertEqual(info, user_info)
613
614 info = client.get_user("nonexist-user")
615 self.assertIsNone(info)
616
617 def test_auth_reconnect(self):
618 admin_client = self.start_auth_server()
619
620 user = admin_client.new_user("test-user", [])
621 user_info = user.copy()
622 del user_info["token"]
623
624 with self.auth_client(user) as client:
625 info = client.get_user()
626 self.assertEqual(info, user_info)
627
628 client.disconnect()
629
630 info = client.get_user()
631 self.assertEqual(info, user_info)
632
633 def test_auth_delete_user(self):
634 admin_client = self.start_auth_server()
635
636 user = admin_client.new_user("test-user", [])
637
638 # No self service
639 with self.auth_client(user) as client, self.assertRaises(InvokeError):
640 client.delete_user(user["username"])
641
642 with self.auth_perms() as client, self.assertRaises(InvokeError):
643 client.delete_user(user["username"])
644
645 with self.auth_perms("@user-admin") as client:
646 client.delete_user(user["username"])
647
648 # User doesn't exist, so even though the permission is correct, it's an
649 # error
650 with self.auth_perms("@user-admin") as client, self.assertRaises(InvokeError):
651 client.delete_user(user["username"])
652
653 def assertUserPerms(self, user, permissions):
654 with self.auth_client(user) as client:
655 info = client.get_user()
656 self.assertEqual(info, {
657 "username": user["username"],
658 "permissions": permissions,
659 })
660
661 def test_auth_set_user_perms(self):
662 admin_client = self.start_auth_server()
663
664 user = admin_client.new_user("test-user", [])
665
666 self.assertUserPerms(user, [])
667
668 # No self service to change permissions
669 with self.auth_client(user) as client, self.assertRaises(InvokeError):
670 client.set_user_perms(user["username"], ["@all"])
671 self.assertUserPerms(user, [])
672
673 with self.auth_perms() as client, self.assertRaises(InvokeError):
674 client.set_user_perms(user["username"], ["@all"])
675 self.assertUserPerms(user, [])
676
677 with self.auth_perms("@user-admin") as client:
678 client.set_user_perms(user["username"], ["@all"])
679 self.assertUserPerms(user, sorted(list(ALL_PERMISSIONS)))
680
681 # Bad permissions
682 with self.auth_perms("@user-admin") as client, self.assertRaises(InvokeError):
683 client.set_user_perms(user["username"], ["@this-is-not-a-permission"])
684 self.assertUserPerms(user, sorted(list(ALL_PERMISSIONS)))
685
686 def test_auth_get_all_users(self):
687 admin_client = self.start_auth_server()
688
689 user = admin_client.new_user("test-user", [])
690
691 with self.auth_client(user) as client, self.assertRaises(InvokeError):
692 client.get_all_users()
693
694 # Give the test user the correct permission
695 admin_client.set_user_perms(user["username"], ["@user-admin"])
696
697 with self.auth_client(user) as client:
698 all_users = client.get_all_users()
699
700 # Convert to a dictionary for easier comparison
701 all_users = {u["username"]: u for u in all_users}
702
703 self.assertEqual(all_users,
704 {
705 "admin": {
706 "username": "admin",
707 "permissions": sorted(list(ALL_PERMISSIONS)),
708 },
709 "test-user": {
710 "username": "test-user",
711 "permissions": ["@user-admin"],
712 }
713 }
714 )
715
716 def test_auth_new_user(self):
717 self.start_auth_server()
718
719 permissions = ["@read", "@report", "@db-admin", "@user-admin"]
720 permissions.sort()
721
722 with self.auth_perms() as client, self.assertRaises(InvokeError):
723 client.new_user("test-user", permissions)
724
725 with self.auth_perms("@user-admin") as client:
726 user = client.new_user("test-user", permissions)
727 self.assertIn("token", user)
728 self.assertEqual(user["username"], "test-user")
729 self.assertEqual(user["permissions"], permissions)
730
731
474class TestHashEquivalenceUnixServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase): 732class TestHashEquivalenceUnixServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase):
475 def get_server_addr(self, server_idx): 733 def get_server_addr(self, server_idx):
476 return "unix://" + os.path.join(self.temp_dir.name, 'sock%d' % server_idx) 734 return "unix://" + os.path.join(self.temp_dir.name, 'sock%d' % server_idx)