From 6c1a73fddc3c97993343036ade0b4e706f0250e5 Mon Sep 17 00:00:00 2001 From: Daniel McKnight Date: Wed, 30 Oct 2024 12:39:02 -0700 Subject: [PATCH] Update PermissionsConfig to dump to int for JSON serialization Accept `access_token` in `MQRequest` model as an alternate auth method --- neon_users_service/models.py | 4 ++++ neon_users_service/mq_connector.py | 7 +++--- neon_users_service/service.py | 34 ++++++++++++++++++++++-------- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/neon_users_service/models.py b/neon_users_service/models.py index 5e2a437..c234ac5 100644 --- a/neon_users_service/models.py +++ b/neon_users_service/models.py @@ -110,6 +110,9 @@ class PermissionsConfig(BaseModel): hub: AccessRoles = AccessRoles.NONE llm: AccessRoles = AccessRoles.NONE + class Config: + use_enum_values = True + class TokenConfig(BaseModel): username: str @@ -143,4 +146,5 @@ class MQRequest(BaseModel): operation: Literal["create", "read", "update", "delete"] username: str password: Optional[str] = None + access_token: Optional[str] = None user: Optional[User] = None diff --git a/neon_users_service/mq_connector.py b/neon_users_service/mq_connector.py index 2a3e97f..c85fa20 100644 --- a/neon_users_service/mq_connector.py +++ b/neon_users_service/mq_connector.py @@ -1,7 +1,7 @@ from typing import Optional import pika.channel -from ovos_utils import LOG, wait_for_exit_signal +from ovos_utils import LOG from ovos_config.config import Configuration from neon_mq_connector.connector import MQConnector from neon_mq_connector.utils.network_utils import b64_to_dict, dict_to_b64 @@ -39,9 +39,10 @@ def parse_mq_request(self, mq_req: dict) -> dict: mq_req.user.password_hash = mq_req.password user = self.service.create_user(mq_req.user) elif mq_req.operation == "read": - if mq_req.password: + if mq_req.password or mq_req.access_token: user = self.service.read_authenticated_user(mq_req.username, - mq_req.password) + mq_req.password, + mq_req.access_token) else: user = self.service.read_unauthenticated_user( mq_req.username) diff --git a/neon_users_service/service.py b/neon_users_service/service.py index f5a79d3..c6c8666 100644 --- a/neon_users_service/service.py +++ b/neon_users_service/service.py @@ -54,6 +54,19 @@ def create_user(self, user: User) -> User: user.password_hash = self._ensure_hashed(user.password_hash) return self.database.create_user(user) + def _read_user(self, user_spec: str, password: Optional[str] = None, + auth_token: Optional[str] = None) -> User: + user = self.database.read_user(user_spec) + if password and self._ensure_hashed(password) == user.password_hash: + return user + elif auth_token and any((tok.access_token == auth_token + for tok in user.tokens)): + return user + else: + user.password_hash = None + user.tokens = [] + return user + def read_unauthenticated_user(self, user_spec: str) -> User: """ Helper to get a user from the database with sensitive data removed. @@ -62,24 +75,27 @@ def read_unauthenticated_user(self, user_spec: str) -> User: @param user_spec: username or user_id to retrieve @returns: Redacted User object with sensitive information removed """ - user = self.database.read_user(user_spec) - user.password_hash = None - user.tokens = [] - return user + return self._read_user(user_spec) - def read_authenticated_user(self, username: str, password: str) -> User: + def read_authenticated_user(self, username: str, + password: Optional[str] = None, + auth_token: Optional[str] = None) -> User: """ Helper to get a user from the database, only if the requested username and password match a database entry. @param username: The username or user ID to retrieve @param password: The hashed or plaintext password for the username + @param auth_token: A valid authentication token for the username @returns: User object from the database if authentication was successful """ # This will raise a `UserNotFound` exception if the user doesn't exist - user = self.database.read_user(username) - - hashed_password = self._ensure_hashed(password) - if hashed_password != user.password_hash: + if password: + user = self._read_user(username, password) + elif auth_token: + user = self._read_user(username, auth_token=auth_token) + else: + raise AuthenticationError("No password or token provided") + if user.password_hash is None: raise AuthenticationError(f"Invalid password for {username}") return user