aboutsummaryrefslogtreecommitdiffstats
path: root/lib/hashserv
diff options
context:
space:
mode:
authorJoshua Watt <JPEWhacker@gmail.com>2023-11-03 08:26:32 -0600
committerRichard Purdie <richard.purdie@linuxfoundation.org>2023-11-09 17:21:15 +0000
commit71e2f5b52b686f34df364ae1f2fc058f45cd5e18 (patch)
treeb0c3bd0b6b70f6207100d9d921c5253e16e61a2b /lib/hashserv
parent69e5417413ee2414fffaa7dd38057573bac56e35 (diff)
downloadbitbake-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.py42
-rw-r--r--lib/hashserv/server.py18
-rw-r--r--lib/hashserv/tests.py39
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):