diff options
author | Joshua Watt <JPEWhacker@gmail.com> | 2023-11-03 08:26:32 -0600 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2023-11-09 17:21:15 +0000 |
commit | 71e2f5b52b686f34df364ae1f2fc058f45cd5e18 (patch) | |
tree | b0c3bd0b6b70f6207100d9d921c5253e16e61a2b /lib/hashserv | |
parent | 69e5417413ee2414fffaa7dd38057573bac56e35 (diff) | |
download | bitbake-71e2f5b52b686f34df364ae1f2fc058f45cd5e18.tar.gz |
hashserv: Add become-user API
Adds API that allows a user admin to impersonate another user in the
system. This makes it easier to write external services that have
external authentication, since they can use a common user account to
access the server, then impersonate the logged in user.
Signed-off-by: Joshua Watt <JPEWhacker@gmail.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'lib/hashserv')
-rw-r--r-- | lib/hashserv/client.py | 42 | ||||
-rw-r--r-- | lib/hashserv/server.py | 18 | ||||
-rw-r--r-- | lib/hashserv/tests.py | 39 |
3 files changed, 94 insertions, 5 deletions
diff --git a/lib/hashserv/client.py b/lib/hashserv/client.py index 82400fe5a..4457f8e50 100644 --- a/lib/hashserv/client.py +++ b/lib/hashserv/client.py @@ -18,10 +18,11 @@ class AsyncClient(bb.asyncrpc.AsyncClient): MODE_GET_STREAM = 1 def __init__(self, username=None, password=None): - super().__init__('OEHASHEQUIV', '1.1', logger) + super().__init__("OEHASHEQUIV", "1.1", logger) self.mode = self.MODE_NORMAL self.username = username self.password = password + self.saved_become_user = None async def setup_connection(self): await super().setup_connection() @@ -29,8 +30,13 @@ class AsyncClient(bb.asyncrpc.AsyncClient): self.mode = self.MODE_NORMAL await self._set_mode(cur_mode) if self.username: + # Save off become user temporarily because auth() resets it + become = self.saved_become_user await self.auth(self.username, self.password) + if become: + await self.become_user(become) + async def send_stream(self, msg): async def proc(): await self.socket.send(msg) @@ -92,7 +98,14 @@ class AsyncClient(bb.asyncrpc.AsyncClient): async def get_outhash(self, method, outhash, taskhash, with_unihash=True): await self._set_mode(self.MODE_NORMAL) return await self.invoke( - {"get-outhash": {"outhash": outhash, "taskhash": taskhash, "method": method, "with_unihash": with_unihash}} + { + "get-outhash": { + "outhash": outhash, + "taskhash": taskhash, + "method": method, + "with_unihash": with_unihash, + } + } ) async def get_stats(self): @@ -120,6 +133,7 @@ class AsyncClient(bb.asyncrpc.AsyncClient): result = await self.invoke({"auth": {"username": username, "token": token}}) self.username = username self.password = token + self.saved_become_user = None return result async def refresh_token(self, username=None): @@ -128,13 +142,19 @@ class AsyncClient(bb.asyncrpc.AsyncClient): if username: m["username"] = username result = await self.invoke({"refresh-token": m}) - if self.username and result["username"] == self.username: + if ( + self.username + and not self.saved_become_user + and result["username"] == self.username + ): self.password = result["token"] return result async def set_user_perms(self, username, permissions): await self._set_mode(self.MODE_NORMAL) - return await self.invoke({"set-user-perms": {"username": username, "permissions": permissions}}) + return await self.invoke( + {"set-user-perms": {"username": username, "permissions": permissions}} + ) async def get_user(self, username=None): await self._set_mode(self.MODE_NORMAL) @@ -149,12 +169,23 @@ class AsyncClient(bb.asyncrpc.AsyncClient): async def new_user(self, username, permissions): await self._set_mode(self.MODE_NORMAL) - return await self.invoke({"new-user": {"username": username, "permissions": permissions}}) + return await self.invoke( + {"new-user": {"username": username, "permissions": permissions}} + ) async def delete_user(self, username): await self._set_mode(self.MODE_NORMAL) return await self.invoke({"delete-user": {"username": username}}) + async def become_user(self, username): + await self._set_mode(self.MODE_NORMAL) + result = await self.invoke({"become-user": {"username": username}}) + if username == self.username: + self.saved_become_user = None + else: + self.saved_become_user = username + return result + class Client(bb.asyncrpc.Client): def __init__(self, username=None, password=None): @@ -182,6 +213,7 @@ class Client(bb.asyncrpc.Client): "get_all_users", "new_user", "delete_user", + "become_user", ) def _get_async_client(self): diff --git a/lib/hashserv/server.py b/lib/hashserv/server.py index f5baa6be7..ca419a1ab 100644 --- a/lib/hashserv/server.py +++ b/lib/hashserv/server.py @@ -255,6 +255,7 @@ class ServerClient(bb.asyncrpc.AsyncServerConnection): "auth": self.handle_auth, "get-user": self.handle_get_user, "get-all-users": self.handle_get_all_users, + "become-user": self.handle_become_user, } ) @@ -707,6 +708,23 @@ class ServerClient(bb.asyncrpc.AsyncServerConnection): return {"username": username} + @permissions(USER_ADMIN_PERM, allow_anon=False) + async def handle_become_user(self, request): + username = str(request["username"]) + + user = await self.db.lookup_user(username) + if user is None: + raise bb.asyncrpc.InvokeError(f"User {username} doesn't exist") + + self.user = user + + self.logger.info("Became user %s", username) + + return { + "username": self.user.username, + "permissions": self.return_perms(self.user.permissions), + } + class Server(bb.asyncrpc.AsyncServer): def __init__( diff --git a/lib/hashserv/tests.py b/lib/hashserv/tests.py index f92f37c45..311b7b777 100644 --- a/lib/hashserv/tests.py +++ b/lib/hashserv/tests.py @@ -728,6 +728,45 @@ class HashEquivalenceCommonTests(object): self.assertEqual(user["username"], "test-user") self.assertEqual(user["permissions"], permissions) + def test_auth_become_user(self): + admin_client = self.start_auth_server() + + user = admin_client.new_user("test-user", ["@read", "@report"]) + user_info = user.copy() + del user_info["token"] + + with self.auth_perms() as client, self.assertRaises(InvokeError): + client.become_user(user["username"]) + + with self.auth_perms("@user-admin") as client: + become = client.become_user(user["username"]) + self.assertEqual(become, user_info) + + info = client.get_user() + self.assertEqual(info, user_info) + + # Verify become user is preserved across disconnect + client.disconnect() + + info = client.get_user() + self.assertEqual(info, user_info) + + # test-user doesn't have become_user permissions, so this should + # not work + with self.assertRaises(InvokeError): + client.become_user(user["username"]) + + # No self-service of become + with self.auth_client(user) as client, self.assertRaises(InvokeError): + client.become_user(user["username"]) + + # Give test user permissions to become + admin_client.set_user_perms(user["username"], ["@user-admin"]) + + # It's possible to become yourself (effectively a noop) + with self.auth_perms("@user-admin") as client: + become = client.become_user(client.username) + class TestHashEquivalenceUnixServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase): def get_server_addr(self, server_idx): |