diff --git a/datastore/__init__.py b/datastore/__init__.py index f8c1477e..c9abfbca 100644 --- a/datastore/__init__.py +++ b/datastore/__init__.py @@ -3,6 +3,7 @@ and the user container in the blob storage. """ +from enum import Enum import datastore.db.queries.user as user import datastore.db.queries.group as group import datastore.db.queries.picture as picture @@ -62,6 +63,11 @@ class Container(BaseModel): folders: Optional[Dict[UUID, Folder]] = {} path: Optional[str] = None +class Permission(Enum): + READ = 1 + WRITE = 2 + OWNER = 3 + class ContainerController: def __init__(self, container_model: Container): @@ -75,6 +81,49 @@ def __init__(self, container_model: Container): # self.model: Optional[Container] = None # self.container_client: Optional[ContainerClient] = None + def __verify_user_can_manage(self, cursor: Cursor, user_id: UUID) ->bool: + """ + This function verifies if the user can manage the container. + Only the Owner of the container and the users with the Dev/Admin role can manage the container. + """ + if user.is_a_user_admin(cursor, user_id): + return True + elif container_db.has_user_access_to_container(cursor, user_id, self.id): + return container_db.get_user_permission_to_container(cursor, user_id, self.id) == Permission.OWNER + + def __verify_user_can_write(self, cursor: Cursor, user_id: UUID) ->bool: + """ + This function verifies if the user can write in the container. + """ + + if user.is_a_user_admin(cursor, user_id): + return True + elif container_db.has_user_access_to_container(cursor, user_id, self.id): + perm = container_db.get_user_permission_to_container(cursor, user_id, self.id) + if perm >= Permission.WRITE: + return True + if container_db.has_user_group_access_to_container(cursor,user_id,self.id): + perm = container_db.get_group_permission_to_container(cursor, user_id, self.id) + if perm >= Permission.WRITE: + return True + return False + + def __verify_user_can_read(self, cursor: Cursor, user_id: UUID) ->bool: + """ + This function verifies if the user can read in the container. + """ + if user.is_a_user_admin(cursor, user_id): + return True + elif container_db.has_user_access_to_container(cursor, user_id, self.id): + perm = container_db.get_user_permission_to_container(cursor, user_id, self.id) + if perm >= Permission.READ: + return True + if container_db.has_user_group_access_to_container(cursor,user_id,self.id): + perm = container_db.get_group_permission_to_container(cursor, user_id, self.id) + if perm >= Permission.READ: + return True + return False + # This could be achieved by fetching the storage and performing Azure API calls def fetch_all_folders_metadata(self, cursor: Cursor): """ @@ -166,7 +215,7 @@ async def get_container_client(self, connection_str: str, credentials: str): def get_id(self): return self.id - def add_user(self, cursor: Cursor, user_id: UUID, performed_by: UUID): + def add_user(self, cursor: Cursor, user_id: UUID, performed_by: UUID, permission: Permission = Permission.READ): """ This function adds a user to the container rights. @@ -174,17 +223,16 @@ def add_user(self, cursor: Cursor, user_id: UUID, performed_by: UUID): - cursor: The cursor object to interact with the database. - user_id (str): The UUID of the user. """ - if not container_db.has_user_access_to_container(cursor, user_id, self.id): - if not container_db.has_user_group_access_to_container( - cursor, user_id, self.id - ): - # Link the container to the user in the database - container_db.add_user_to_container( - cursor, user_id, self.id, performed_by - ) + # Make sure the user has the permission to add a user to the container + if not self.__verify_user_can_manage(cursor, performed_by): + raise ValueError("The user performing the action is not the owner of the container, they can't add other users to the container") + # Link the container to the user in the database + container_db.add_user_to_container( + cursor, user_id, self.id, performed_by, permission.value + ) self.model.user_ids.append(user_id) - def remove_user(self, cursor: Cursor, user_id: UUID): + def remove_user(self, cursor: Cursor, user_id: UUID,performed_by: UUID): """ This function removes a user from the container rights. @@ -192,18 +240,32 @@ def remove_user(self, cursor: Cursor, user_id: UUID): - cursor: The cursor object to interact with the database. - user_id (str): The UUID of the user. """ + # Check if the user to remove has access to the container if container_db.has_user_access_to_container(cursor, user_id, self.id): + # Make sure the user has the permission to add a user to the container + if not self.__verify_user_can_manage(cursor, performed_by): + raise ValueError("The user performing this action is not the owner of the container, they can't remove other users to the container") # Unlink the container to the user in the database container_db.delete_user_from_container(cursor, user_id, self.id) - if user_id in self.model.user_ids: - self.model.user_ids.remove(user_id) + if user_id in self.model.user_ids: + self.model.user_ids.remove(user_id) - def add_group(self, cursor: Cursor, group_id: UUID, performed_by: UUID): + def add_group(self, cursor: Cursor, group_id: UUID, performed_by: UUID, permission: Permission = Permission.READ): + """ + Function to add a group to the container rights. + """ + # Make sure the user has the permission to add a user to the container + if not self.__verify_user_can_manage(cursor, performed_by): + raise ValueError("The user performing this action is not the owner of the container, they can't add groups to the container") # Link the container to the user in the database - container_db.add_group_to_container(cursor, group_id, self.id, performed_by) + container_db.add_group_to_container(cursor, group_id, self.id, performed_by, permission.value) self.model.group_ids.append(group_id) - def remove_group(self, cursor: Cursor, group_id: UUID): + def remove_group(self, cursor: Cursor, group_id: UUID, performed_by: UUID): + # Make sure the user has the permission to add a user to the container + if not self.__verify_user_can_manage(cursor, performed_by): + raise ValueError("The user is not the owner of the container, they can't add other users to the container") + # Unlink the container to the user in the database container_db.delete_group_from_container(cursor, group_id, self.id) if group_id in self.model.group_ids: @@ -263,6 +325,9 @@ async def create_folder( raise user.UserNotFoundError( f"User not found based on the given id: {performed_by}" ) + # TODO : Check the user container permission if he has at least write permission + if not self.__verify_user_can_write(cursor, performed_by): + raise ValueError("The user does not have the permission to create a folder in the container: " + str(self.id)) # Check if folder exists in the database in the container & parent folder if folder_name is not None: # This is also a trigger in the db to create the folder @@ -340,6 +405,9 @@ async def upload_pictures( raise ContainerCreationError( "Error: container client does not exist or not set, please set the container client first" ) + # TODO : Check the user container permission if he has at least write permission + if not self.__verify_user_can_write(cursor, user_id): + raise ValueError("The user does not have the permission to upload pictures in the container: " + str(self.id)) # Create a folder if not provided if folder_id is None: folder_id = await self.create_folder( @@ -429,7 +497,7 @@ async def get_folder_pictures(self, cursor: Cursor, folder_id: UUID, user_id: UU f"Folder does not exist in the container: {folder_id}" ) # Check if user has access to the container - if user_id not in self.model.user_ids: + if user_id not in self.model.user_ids and not user.is_a_user_admin(cursor, user_id) and not self.model.is_public: raise UserNotOwnerError( f"User can't access this Container, user uuid :{user_id}, Container id : {self.id}" ) @@ -478,8 +546,9 @@ async def get_picture_blob(self, cursor: Cursor, picture_id: UUID, user_id: UUID raise user.UserNotFoundError( f"User not found based on the given id: {user_id}" ) - # Check if user has access to the container - # TODO + #TODO: Check if user has access to the container + if not self.__verify_user_can_read(cursor, user_id) and not self.model.is_public: + raise ValueError("The user does not have the permission to download pictures in the container: " + str(self.id)) # Check if picture exists if not picture.is_a_picture_id(cursor, picture_id): raise picture.PictureNotFoundError( @@ -519,6 +588,8 @@ async def delete_folder_permanently( f"Picture set not found based on the given id: {folder_id}" ) # TODO : Check user is owner of the picture set + if not self.__verify_user_can_write(cursor, user_id): + raise ValueError("The user does not have the permission to delete the folder in the container: " + str(self.id)) # if picture.get_picture_set_owner_id(cursor, folder_id) != user_id: # Delete the folder in the blob storage @@ -540,6 +611,12 @@ async def delete_picture_permanently(self, cursor: Cursor, user_id: UUID, pictur """ This function deletes a picture from the blob storage and the database. """ + if not user.is_a_user_id(cursor=cursor, user_id=user_id): + raise user.UserNotFoundError( + f"User not found based on the given id: {user_id}" + ) + if not self.__verify_user_can_write(cursor, user_id): + raise ValueError("The user does not have the permission to delete the picture in the container: " + str(self.id)) # Get the picture set id picture_set_id = picture.get_picture_picture_set_id(cursor, picture_id) @@ -606,8 +683,14 @@ async def create_container( def fetch_all_containers(): pass +class Role(Enum): + DEV = 1 + ADMIN = 2 + TEAM_LEADER = 3 + INSPECTOR = 4 + class User(ClientController): - def __init__(self, email: str, id: UUID = None, tier: str = "user"): + def __init__(self, email: str, id: UUID = None, tier: str = "user",role: Role = Role.INSPECTOR): if id is None: raise ValueError("The user id must be provided") client_model = Client( @@ -617,6 +700,7 @@ def __init__(self, email: str, id: UUID = None, tier: str = "user"): storage_prefix=tier, containers={}, ) + self.role = role super().__init__(client_model) async def fetch_all_containers( @@ -713,7 +797,7 @@ def fetch_all_containers(self, cursor: Cursor): container_obj.fetch_all_data(cursor) self.model.containers[container_obj] = container_obj.model - def add_user(self, cursor: Cursor, user_id: UUID, performed_by: UUID): + def add_user(self, cursor: Cursor, user_id: UUID, performed_by: UUID,permission: Permission=Permission.READ): """ This function adds a user to the group rights. @@ -721,12 +805,16 @@ def add_user(self, cursor: Cursor, user_id: UUID, performed_by: UUID): - cursor: The cursor object to interact with the database. - user_id (str): The UUID of the user. """ - # TODO: Check permission - #if not group.has_user_access_to_group(cursor, user_id, self.id): - # Link the group to the user in the database - group.add_user_to_group(cursor, user_id, self.model.id, performed_by) - - def remove_user(self, cursor: Cursor, user_id: UUID): + if not group.is_user_group_creator(cursor, performed_by, self.model.id) or not user.is_a_user_admin(cursor, performed_by): + raise ValueError("The user is not the creator of the group therefore does not have the right to manage its users") + if permission == Permission.OWNER and user_id == performed_by: + # This should only be the case when it's the creator of the group which should be the owner + pass + elif permission == Permission.OWNER: + raise ValueError("The user does not have the right to assign the owner permission") + group.add_user_to_group(cursor, user_id, self.id, performed_by, permission) + + def remove_user(self, cursor: Cursor, user_id: UUID,performed_by: UUID): """ This function removes a user from the group rights. @@ -734,7 +822,8 @@ def remove_user(self, cursor: Cursor, user_id: UUID): - cursor: The cursor object to interact with the database. - user_id (str): The UUID of the user. """ - # TODO : Check permission + if not group.is_user_group_creator(cursor, performed_by, self.model.id) or not user.is_a_user_admin(cursor, performed_by): + raise ValueError("The user is not the creator of the group therefore does not have the right to manage its users") group.remove_user_from_group(cursor, user_id, self.id) @@ -752,7 +841,7 @@ async def get_user(cursor: Cursor, email: str) -> User: return user_obj -async def new_user(cursor: Cursor, email: str, connection_string, tier="user") -> User: +async def new_user(cursor: Cursor, email: str, connection_string, tier="user",role:Role = Role.INSPECTOR) -> User: """ Create a new user in the database and creates its personal container in the blob storage. @@ -765,9 +854,9 @@ async def new_user(cursor: Cursor, email: str, connection_string, tier="user") - # Register the user in the database if user.is_user_registered(cursor, email): raise UserAlreadyExistsError("User already exists") - user_uuid = user.register_user(cursor, email) + user_uuid = user.register_user(cursor, email, role.value) # Create the user object - user_obj = User(email, user_uuid, tier) + user_obj = User(email = email, id=user_uuid, tier=tier,role=role) # Create the user container in the blob storage await user_obj.create_container( cursor=cursor, @@ -861,7 +950,7 @@ async def create_group( user_id=user_id, is_public=False, ) - group_obj.add_user(cursor, user_id, user_id) + group_obj.add_user(cursor, user_id, user_id,Permission.OWNER) return group_obj except Exception as e: raise Exception("Datastore Unhandled Error " + str(e)) @@ -921,7 +1010,7 @@ async def create_container( await container_obj.create_storage(connection_str=connection_str, credentials=None) if add_user_to_storage: - container_obj.add_user(cursor, user_id, user_id) + container_obj.add_user(cursor, user_id, user_id,Permission.OWNER) await container_obj.create_folder( cursor=cursor, performed_by=user_id, folder_name="General", nb_pictures=0 ) diff --git a/datastore/db/queries/container/__init__.py b/datastore/db/queries/container/__init__.py index 0414f1d0..790370cc 100644 --- a/datastore/db/queries/container/__init__.py +++ b/datastore/db/queries/container/__init__.py @@ -151,9 +151,54 @@ def has_user_access_to_container( f"Error: user {user_id} not found in container {container_id}\n" + str(e) ) +def get_user_permission_to_container( + cursor: Cursor, user_id: UUID, container_id: UUID) -> int: + """ + This function checks if a user has access to a container. + """ + + try: + query = """ + SELECT + permission_id + FROM + container_user + WHERE + user_id = %s AND container_id = %s; + """ + cursor.execute( + query, + (user_id, container_id), + ) + return cursor.fetchone()[0] + except Exception as e: + raise ContainerUserNotFoundError( + f"Error: user {user_id} not found in container {container_id}\n" + str(e) + ) + +def get_group_permission_to_container( + cursor: Cursor, group_id: UUID, container_id: UUID) -> int: + """ + This function checks if a group has access to a container. + """ + try: + query = """ + SELECT + permission_id + FROM + container_group + WHERE + group_id = %s AND container_id = %s; + """ + cursor.execute(group_id, container_id) + return cursor.fetchone()[0] + except Exception as e: + raise ContainerUserNotFoundError( + f"Error: group {group_id} not found in container {container_id}\n" + str(e) + ) def has_user_group_access_to_container( - cursor: Cursor, user_id: UUID, container_id: UUID + cursor: Cursor, user_id: UUID, container_id: UUID, ) -> bool: """ This function checks if a user has access to a container through a group. @@ -196,7 +241,7 @@ def has_user_group_access_to_container( def add_user_to_container( - cursor: Cursor, user_id: UUID, container_id: UUID, assigned_by_id: UUID + cursor: Cursor, user_id: UUID, container_id: UUID, assigned_by_id: UUID, permission_id: int ) -> None: """ This function adds a user to a container in the database. @@ -212,13 +257,15 @@ def add_user_to_container( assigned_by_id = user_id query = """ INSERT INTO - container_user (container_id,user_id, created_by_id,last_updated_by_id) + container_user (container_id,user_id, created_by_id,last_updated_by_id,permission_id) VALUES - (%s,%s,%s,%s); + (%s,%s,%s,%s,%s) + ON CONFLICT (container_id,user_id) + DO UPDATE SET last_updated_by_id = %s, permission_id = %s; """ cursor.execute( query, - (container_id, user_id, assigned_by_id, assigned_by_id), + (container_id, user_id, assigned_by_id, assigned_by_id, permission_id, assigned_by_id, permission_id), ) except Exception as e: raise ContainerAssignmentError( @@ -227,7 +274,7 @@ def add_user_to_container( def add_group_to_container( - cursor: Cursor, group_id: UUID, container_id: UUID, user_id: UUID + cursor: Cursor, group_id: UUID, container_id: UUID, user_id: UUID, permission_id: int ) -> None: """ This function adds a group to a container in the database. @@ -241,13 +288,15 @@ def add_group_to_container( try: query = """ INSERT INTO - container_group (container_id,group_id,created_by_id,last_updated_by_id) + container_group (container_id,group_id,created_by_id,last_updated_by_id,permission_id) VALUES (%s,%s,%s,%s); + ON CONFLICT (container_id,group_id) + DO UPDATE SET last_updated_by_id = %s, permission_id = %s; """ cursor.execute( query, - (container_id, group_id, user_id, user_id), + (container_id, group_id, user_id, user_id,), ) except Exception as e: raise ContainerAssignmentError( diff --git a/datastore/db/queries/group/__init__.py b/datastore/db/queries/group/__init__.py index d5155bb5..b2b6b252 100644 --- a/datastore/db/queries/group/__init__.py +++ b/datastore/db/queries/group/__init__.py @@ -49,7 +49,7 @@ def create_group(cursor: Cursor, name: str, user_id: UUID) -> str: def add_user_to_group( - cursor: Cursor, user_id: UUID, group_id: UUID, assigned_by_id: UUID + cursor: Cursor, user_id: UUID, group_id: UUID, assigned_by_id: UUID, permission_id: int ) -> None: """ This function adds a user to a group in the database. @@ -64,14 +64,16 @@ def add_user_to_group( assigned_by_id = user_id query = """ INSERT INTO - user_group (group_id,user_id,assigned_by_id) + user_group (group_id,user_id,assigned_by_id,permission_id) VALUES (%s,%s,%s) - RETURNING id + ON CONFLICT (group_id,user_id) + DO UPDATE SET permission_id = %s + RETURNING id; """ cursor.execute( query, - (group_id, user_id, assigned_by_id), + (group_id, user_id, assigned_by_id, permission_id,permission_id), ) if cursor.fetchone() is None: raise GroupAssignmentError @@ -288,3 +290,18 @@ def get_group_by_name(cursor: Cursor, group_name: str) -> dict: return cursor.fetchall() except Exception: raise GroupNotFoundError(f"Error: group {group_name} not found") + +def is_user_group_creator(cursor: Cursor, user_id: UUID,group_id:UUID) -> bool: + query = """ + SELECT + EXISTS( + SELECT + 1 + FROM + groups + WHERE + id = %s AND created_by_id = %s + ) + """ + cursor.execute(query,(group_id,user_id)) + return cursor.fetchone()[0] diff --git a/datastore/db/queries/user/__init__.py b/datastore/db/queries/user/__init__.py index e1a8e556..d10a3fd4 100644 --- a/datastore/db/queries/user/__init__.py +++ b/datastore/db/queries/user/__init__.py @@ -31,6 +31,7 @@ def is_user_registered(cursor : Cursor, email: str) -> bool: - True if the user is registered, False otherwise. """ try: + query = """ SELECT EXISTS( SELECT @@ -108,13 +109,14 @@ def get_user_id(cursor : Cursor, email: str) -> UUID: raise Exception("Unhandled Error") -def register_user(cursor : Cursor, email: str) -> UUID: +def register_user(cursor : Cursor, email: str, role_id:int) -> UUID: """ This function registers a user in the database. Parameters: - cursor (cursor): The cursor of the database. - email (str): Email of the user + - role_id (int): The role of the user Returns: - The UUID of the user. @@ -122,14 +124,14 @@ def register_user(cursor : Cursor, email: str) -> UUID: try: query = """ INSERT INTO - users (email) + users (email,role_id) VALUES - (%s) + (%s,%s) RETURNING id """ cursor.execute( query, - (email,), + (email,role_id), ) return cursor.fetchone()[0] except Exception as e: @@ -299,3 +301,17 @@ def delete_user(cursor : Cursor, user_id: UUID): cursor.execute(query, (user_id,)) except Exception: raise Exception("Error: could not delete user") + + +def is_a_user_admin(cursor:Cursor, user_id)->bool: + query = """ + SELECT + role_id + FROM + users + WHERE + id = %s + """ + cursor.execute(query,(user_id,)) + res = cursor.fetchone()[0] + return res < 3 \ No newline at end of file diff --git a/datastore/doc/Access-policies.md b/datastore/doc/Access-policies.md new file mode 100644 index 00000000..3839d686 --- /dev/null +++ b/datastore/doc/Access-policies.md @@ -0,0 +1,23 @@ +# Access Policies + + +## Overview + +Our application uses a combination of roles and permissions to manage access control. This system allows us to attribute specific roles to users, which define their access to various features of the application. Additionally, permissions are used to manage access to user-owned resources (Discretionary access control). + +## Roles + +Roles are used to define the overall access level of a user within the application. The roles are stored in the `roles` table (The name needs to be plural because role is a reserved word in PostgrSQL) and include the following: + +- **dev**: Developers with full access to all features. +- **admin**: Administrators with full access to all features except the dev menu. +- **team leader**: Users with elevated privileges to manage teams (groups). +- **inspector**: Users with limited access. + +## Permissions + +Permissions are used to manage access to user-owned resources called containers. A user can manage who can see and upload content into their container. The permissions are stored in the `permission` table and include the following: + +- **read**: Permission to view content in a container. +- **write**: Permission to upload/delete content to a container. +- **owner**: Full control over the container, including managing permissions for other users. \ No newline at end of file diff --git a/datastore/doc/datastore.md b/datastore/doc/datastore.md index bb54f4a2..686483d1 100644 --- a/datastore/doc/datastore.md +++ b/datastore/doc/datastore.md @@ -172,6 +172,7 @@ erDiagram TEXT email TIMESTAMP registration_date TIMESTAMP updated_at + UUID role_id } GROUPS { @@ -209,6 +210,7 @@ erDiagram UUID created_by_id FK UUID last_updated_by_id FK UUID container_id FK + int permission_id FK } CONTAINER_GROUP { @@ -219,15 +221,29 @@ erDiagram UUID created_by_id FK UUID last_updated_by_id FK UUID container_id FK + int permission_id FK } + ROLE{ + int id + text name + } + + PERMISSION{ + int id PK + text name + } + USERS ||--o{ GROUPS : "creates" + USERS ||--|| ROLE: is USERS ||--o{ USER_GROUP : "group access" GROUPS ||--o{ USER_GROUP : "members" USERS ||--o{ CONTAINER : "creates" USERS ||--o{ CONTAINER_USER : "individual access" CONTAINER ||--o{ CONTAINER_USER : "access to" GROUPS ||--o{ CONTAINER_GROUP : "access to" + PERMISSION ||--|| CONTAINER_GROUP: "allow operation" + PERMISSION ||--|| CONTAINER_USER: "allow operation" ``` For more detail on each app database architecture go check [Nachet diff --git a/nachet/db/bytebase/schema_0.1.1.sql b/nachet/db/bytebase/schema_0.1.1.sql index 24493efb..1fae82fe 100644 --- a/nachet/db/bytebase/schema_0.1.1.sql +++ b/nachet/db/bytebase/schema_0.1.1.sql @@ -9,12 +9,33 @@ create schema "nachet_0.1.1"; CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + CREATE TABLE "nachet_0.1.1".roles ( + "id" PRIMARY KEY, + "name" text NOT NULL + ); + + CREATE TABLE "nachet_0.1.1".permission ( + "id" PRIMARY KEY, + "name" text NOT NULL + ); + + INSERT INTO "nachet_0.1.1".roles (id, name) VALUES + (1, 'dev'), + (2, 'admin'), + (3, 'team leader'), + (4, 'inspector'); + + INSERT INTO "nachet_0.1.1".permission (id, name) VALUES + (1, 'read'), + (2, 'write'), + (3, 'owner'); CREATE TABLE "nachet_0.1.1"."users" ( "id" uuid PRIMARY KEY DEFAULT uuid_.uuid_generate_v4(), "email" text NOT NULL UNIQUE, "registration_date" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" timestamp + "updated_at" timestamp, + "role_id" INT NOT NULL REFERENCES "nachet_0.1.1".role(id), ); Create table "nachet_0.1.1"."groups" ( @@ -30,7 +51,10 @@ create schema "nachet_0.1.1"; "user_id" uuid NOT NULL REFERENCES "nachet_0.1.1".users(id) ON DELETE CASCADE, "group_id" uuid NOT NULL REFERENCES "nachet_0.1.1".groups(id) ON DELETE CASCADE, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - "assigned_by_id" uuid NOT NULL REFERENCES "nachet_0.1.1".users(id) + "assigned_by_id" uuid NOT NULL REFERENCES "nachet_0.1.1".users(id), + "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "permission_id" INT NOT NULL REFERENCES "nachet_0.1.1".permission(id), + UNIQUE ("user_id", "group_id") ); CREATE TABLE "nachet_0.1.1"."container" ( @@ -51,7 +75,9 @@ create schema "nachet_0.1.1"; "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "created_by_id" uuid NOT NULL REFERENCES "nachet_0.1.1".users(id) ON DELETE SET NULL, "last_updated_by_id" uuid NOT NULL REFERENCES "nachet_0.1.1".users(id) ON DELETE SET NULL, - "container_id" uuid NOT NULL REFERENCES "nachet_0.1.1".container(id) ON DELETE CASCADE + "container_id" uuid NOT NULL REFERENCES "nachet_0.1.1".container(id) ON DELETE CASCADE, + "permission_id" INT NOT NULL REFERENCES "nachet_0.1.1".permission(id) + UNIQUE ("user_id", "container_id") ); CREATE TABLE "nachet_0.1.1"."container_group" ( @@ -62,6 +88,8 @@ create schema "nachet_0.1.1"; "created_by_id" uuid NOT NULL REFERENCES "nachet_0.1.1".users(id) ON DELETE SET NULL, "last_updated_by_id" uuid NOT NULL REFERENCES "nachet_0.1.1".users(id) ON DELETE SET NULL, "container_id" uuid NOT NULL REFERENCES "nachet_0.1.1".container(id) ON DELETE CASCADE + "permission_id" INT NOT NULL REFERENCES "nachet_0.1.1".permission(id), + UNIQUE ("group_id", "container_id") ); CREATE TABLE "nachet_0.1.1"."picture_set" ( diff --git a/tests/test_datastore.py b/tests/test_datastore.py index c70a9eff..0e929e66 100644 --- a/tests/test_datastore.py +++ b/tests/test_datastore.py @@ -21,6 +21,8 @@ import datastore.db.__init__ as db import datastore.db.metadata.validator as validator +from datastore import Role, Permission + DB_CONNECTION_STRING = os.environ.get("NACHET_DB_URL") if DB_CONNECTION_STRING is None or DB_CONNECTION_STRING == "": raise ValueError("NACHET_DB_URL is not set") @@ -229,7 +231,10 @@ def test_create_folder(self): id=container_id, storage_prefix=self.prefix, name=container_name, public=False ) container_obj = datastore.ContainerController(container_model) - container_obj.add_user(self.cursor, self.user_id, self.user_id) + container_obj.add_user(cursor=self.cursor, + user_id=self.user_id, + performed_by=self.user_id, + permission=Permission.WRITE) asyncio.run(container_obj.create_storage(self.connection_str, None)) self.container_client = container_obj.container_client self.assertIsNotNone(container_obj.container_client) @@ -275,7 +280,10 @@ def test_create_unnamed_folder(self): id=container_id, storage_prefix=self.prefix, name=container_name, public=False ) container_obj = datastore.ContainerController(container_model) - container_obj.add_user(self.cursor, self.user_id, self.user_id) + container_obj.add_user(cursor=self.cursor, + user_id=self.user_id, + performed_by=self.user_id, + permission=Permission.WRITE) asyncio.run(container_obj.create_storage(self.connection_str, None)) self.container_client = container_obj.container_client self.assertTrue(self.container_client.exists()) @@ -315,7 +323,10 @@ def test_create_folder_within_folder(self): id=container_id, storage_prefix=self.prefix, name=container_name, public=False ) container_obj = datastore.ContainerController(container_model) - container_obj.add_user(self.cursor, self.user_id, self.user_id) + container_obj.add_user(cursor=self.cursor, + user_id=self.user_id, + performed_by=self.user_id, + permission=Permission.WRITE) asyncio.run(container_obj.create_storage(self.connection_str, None)) self.container_client = container_obj.container_client self.assertIsNotNone(container_obj.container_client) @@ -389,7 +400,10 @@ def test_create_duplicate_folder(self): id=container_id, storage_prefix=self.prefix, name=container_name, public=False ) container_obj = datastore.ContainerController(container_model) - container_obj.add_user(self.cursor, self.user_id, self.user_id) + container_obj.add_user(cursor=self.cursor, + user_id=self.user_id, + performed_by=self.user_id, + permission=Permission.WRITE) asyncio.run(container_obj.create_storage(self.connection_str, None)) self.container_client = container_obj.container_client self.assertIsNotNone(container_obj.container_client) @@ -446,7 +460,10 @@ def test_create_duplicate_folder_within_folder(self): id=container_id, storage_prefix=self.prefix, name=container_name, public=False ) container_obj = datastore.ContainerController(container_model) - container_obj.add_user(self.cursor, self.user_id, self.user_id) + container_obj.add_user(cursor=self.cursor, + user_id=self.user_id, + performed_by=self.user_id, + permission=Permission.WRITE) asyncio.run(container_obj.create_storage(self.connection_str, None)) self.container_client = container_obj.container_client self.assertIsNotNone(container_obj.container_client) @@ -532,7 +549,10 @@ def test_upload_pictures(self): id=container_id, storage_prefix=self.prefix, name=container_name, public=False ) container_obj = datastore.ContainerController(container_model) - container_obj.add_user(self.cursor, self.user_id, self.user_id) + container_obj.add_user(cursor=self.cursor, + user_id=self.user_id, + performed_by=self.user_id, + permission=Permission.WRITE) asyncio.run(container_obj.create_storage(self.connection_str, None)) self.container_client = container_obj.container_client self.assertIsNotNone(self.container_client) @@ -587,7 +607,10 @@ def test_get_container(self): id=container_id, storage_prefix=self.prefix, name=container_name, public=False ) container_obj = datastore.ContainerController(container_model) - container_obj.add_user(self.cursor, self.user_id, self.user_id) + container_obj.add_user(cursor=self.cursor, + user_id=self.user_id, + performed_by=self.user_id, + permission=Permission.WRITE) asyncio.run(container_obj.create_storage(self.connection_str, None)) self.container_client = container_obj.container_client self.assertIsNotNone(self.container_client) @@ -649,7 +672,10 @@ def test_delete_folder(self): id=container_id, storage_prefix=self.prefix, name=container_name, public=False ) container_obj = datastore.ContainerController(container_model) - container_obj.add_user(self.cursor, self.user_id, self.user_id) + container_obj.add_user(cursor=self.cursor, + user_id=self.user_id, + performed_by=self.user_id, + permission=Permission.WRITE) asyncio.run(container_obj.create_storage(self.connection_str, None)) self.container_client = container_obj.container_client self.assertIsNotNone(container_obj.container_client) @@ -716,6 +742,8 @@ def setUp(self): self.prefix = "test-user" self.connection_str = BLOB_CONNECTION_STRING + self.user_role = Role.INSPECTOR + def tearDown(self): self.con.rollback() if self.user_id is not None: @@ -780,6 +808,7 @@ def test_already_existing_user(self): email=self.user_email, connection_string=self.connection_str, tier=self.prefix, + role=self.user_role ) ) @@ -995,11 +1024,15 @@ def setUp(self): # We want to avoid creating a storage container for the user self.user_email = "tests-user-class@email" self.user_prefix = "test-user" + # We need the user to be a TL to create a group + self.user_role = Role.TEAM_LEADER + self.user_id = user_db.register_user(self.cursor, self.user_email) self.user_obj = datastore.User( id=self.user_id, email=self.user_email, - tier=self.user_prefix + tier=self.user_prefix, + role=self.user_role ) # Group data @@ -1186,14 +1219,13 @@ def test_delete_group(self): )) # Check if the container is deleted in the storage self.assertTrue(container_controller.container_client.exists()) - - - def test_add_user_to_group(self): # Create a second user user_email1 = "tests-user-class-1@email" + user_role = Role.INSPECTOR + team_leader_role = Role.TEAM_LEADER user_email2 = "tests-user-class-2@email" user_id1 = user_db.register_user(self.cursor, user_email1) user_id2 = user_db.register_user(self.cursor, user_email2) @@ -1201,12 +1233,14 @@ def test_add_user_to_group(self): user_obj1 = datastore.User( id=user_id1, email=user_email1, - tier=self.user_prefix + tier=self.user_prefix, + role=user_role ) user_obj2 = datastore.User( id=user_id2, email=user_email2, - tier=self.user_prefix + tier=self.user_prefix, + role=team_leader_role ) asyncio.run(user_obj1.fetch_all_containers( cursor=self.cursor,