From 0a9d3ce909d657d710fe604f87ce22238623d24a Mon Sep 17 00:00:00 2001 From: Thonyk Date: Fri, 19 Dec 2025 21:31:45 +0100 Subject: [PATCH 01/14] feat: modular permissions ^ Conflicts: ^ app/core/groups/groups_type.py ^ app/core/memberships/endpoints_memberships.py ^ app/dependencies.py ^ app/module.py ^ app/modules/cdr/coredata_cdr.py ^ app/modules/cdr/dependencies_cdr.py ^ app/modules/cdr/endpoints_cdr.py ^ app/modules/cdr/utils_cdr.py ^ app/modules/centralassociation/endpoints_centralassociation.py ^ app/modules/raid/endpoints_raid.py ^ app/modules/sport_competition/dependencies_sport_competition.py ^ app/modules/sport_competition/endpoints_sport_competition.py ^ app/modules/sport_competition/utils/schemas_converters.py ^ app/modules/sport_competition/utils_sport_competition.py ^ app/utils/auth/providers.py ^ tests/config.test.yaml ^ tests/core/test_core.py ^ tests/core/test_memberships.py ^ tests/modules/cdr/test_cdr.py ^ tests/modules/sport_competition/test_purchases.py ^ tests/modules/sport_competition/test_sport_inscription.py ^ tests/modules/sport_competition/test_validation.py ^ tests/modules/test_flappybird.py ^ tests/modules/test_raid.py --- app/app.py | 73 +-- app/core/auth/endpoints_auth.py | 29 +- app/core/core_endpoints/cruds_core.py | 123 ----- app/core/core_endpoints/endpoints_core.py | 147 +----- app/core/core_endpoints/models_core.py | 15 - app/core/core_endpoints/schemas_core.py | 18 +- app/core/groups/factory_groups.py | 2 +- app/core/groups/groups_type.py | 30 +- app/core/memberships/endpoints_memberships.py | 11 +- app/core/memberships/factory_memberships.py | 8 +- app/core/permissions/__init__.py | 0 app/core/permissions/cruds_permissions.py | 235 +++++++++ app/core/permissions/endpoints_permissions.py | 186 ++++++++ app/core/permissions/models_permissions.py | 26 + app/core/permissions/schemas_permissions.py | 18 + app/core/permissions/type_permissions.py | 5 + app/dependencies.py | 64 ++- app/module.py | 45 ++ app/modules/advert/endpoints_advert.py | 67 ++- app/modules/advert/factory_advert.py | 8 +- app/modules/amap/endpoints_amap.py | 169 +++++-- app/modules/booking/endpoints_booking.py | 82 +++- app/modules/booking/factory_booking.py | 6 +- app/modules/calendar/endpoints_calendar.py | 75 ++- app/modules/campaign/cruds_campaign.py | 50 +- app/modules/campaign/endpoints_campaign.py | 325 +++++++------ app/modules/campaign/models_campaign.py | 10 - app/modules/cdr/endpoints_cdr.py | 328 +++++++++---- app/modules/cdr/utils_cdr.py | 23 +- .../endpoints_centralassociation.py | 7 + .../endpoints_centralisation.py | 7 + app/modules/cinema/endpoints_cinema.py | 42 +- .../flappybird/endpoints_flappybird.py | 28 +- app/modules/home/endpoints_home.py | 7 + app/modules/loan/endpoints_loan.py | 76 ++- app/modules/myeclpay/endpoints_myeclpay.py | 7 + app/modules/ph/endpoints_ph.py | 46 +- app/modules/phonebook/endpoints_phonebook.py | 139 +++--- app/modules/purchases/endpoints_purchases.py | 9 +- app/modules/raffle/endpoints_raffle.py | 149 ++++-- app/modules/raid/endpoints_raid.py | 147 ++++-- .../endpoints_recommendation.py | 36 +- .../seed_library/endpoints_seed_library.py | 90 +++- .../dependencies_sport_competition.py | 25 +- .../endpoints_sport_competition.py | 447 ++++++++++++------ .../permissions_sport_competition.py | 7 + app/types/module.py | 7 + app/utils/auth/providers.py | 83 ++-- app/utils/initialization.py | 72 +-- app/utils/tools.py | 28 +- .../versions/25-hardcode-eclair-group.py | 6 +- migrations/versions/29-hardcode-BDS-group.py | 20 +- migrations/versions/30-membership.py | 19 +- migrations/versions/48-permissions.py | 114 +++++ tests/commons.py | 70 ++- tests/config.test.yaml | 32 +- tests/core/test_auth.py | 68 ++- tests/core/test_core.py | 89 ---- tests/core/test_dependencies.py | 44 +- tests/core/test_memberships.py | 43 +- tests/core/test_myeclpay.py | 14 +- tests/core/test_payment.py | 2 + tests/core/test_user_fusion.py | 28 +- tests/core/test_users.py | 14 +- tests/modules/cdr/test_cdr.py | 302 ++++++------ tests/modules/cdr/test_cdr_result.py | 28 +- .../sport_competition/test_purchases.py | 12 +- .../test_sport_inscription.py | 23 +- .../sport_competition/test_validation.py | 16 +- tests/modules/test_advert.py | 25 +- tests/modules/test_amap.py | 63 +-- tests/modules/test_booking.py | 33 +- tests/modules/test_calendar.py | 45 +- tests/modules/test_campaign.py | 139 +++--- tests/modules/test_cinema.py | 14 +- tests/modules/test_flappybird.py | 12 +- tests/modules/test_loan.py | 46 +- tests/modules/test_ph.py | 13 +- tests/modules/test_phonebook.py | 13 +- tests/modules/test_raffle.py | 30 +- tests/modules/test_raid.py | 18 +- tests/modules/test_recommendation.py | 15 +- tests/modules/test_seed_library.py | 16 +- tests/test_permissions.py | 110 +++++ 84 files changed, 3459 insertions(+), 1714 deletions(-) create mode 100644 app/core/permissions/__init__.py create mode 100644 app/core/permissions/cruds_permissions.py create mode 100644 app/core/permissions/endpoints_permissions.py create mode 100644 app/core/permissions/models_permissions.py create mode 100644 app/core/permissions/schemas_permissions.py create mode 100644 app/core/permissions/type_permissions.py create mode 100644 app/modules/sport_competition/permissions_sport_competition.py create mode 100644 migrations/versions/48-permissions.py create mode 100644 tests/test_permissions.py diff --git a/app/app.py b/app/app.py index 1db5045e1f..8f681a2e94 100644 --- a/app/app.py +++ b/app/app.py @@ -25,7 +25,7 @@ from sqlalchemy.orm import Session from app import api -from app.core.core_endpoints import coredata_core, models_core +from app.core.core_endpoints import coredata_core from app.core.google_api.google_api import GoogleAPI from app.core.groups import models_groups from app.core.groups.groups_type import GroupType @@ -41,7 +41,7 @@ get_redis_client, init_state, ) -from app.module import all_modules, module_list +from app.module import all_modules, module_list, permissions_list from app.types.exceptions import ( ContentHTTPException, GoogleAPIInvalidCredentialsError, @@ -299,7 +299,6 @@ def initialize_module_visibility( coredata_core.ModuleVisibilityAwareness, db, ) - new_modules = [ module for module in module_list @@ -308,41 +307,41 @@ def initialize_module_visibility( # Is run to create default module visibilities or when the table is empty if new_modules: hyperion_error_logger.info( - f"Startup: Some modules visibility settings are empty, initializing them ({[module.root for module in new_modules]})", + f"Startup: Some modules visibility settings are empty, initializing them : ({[module.root for module in new_modules]})", ) for module in new_modules: - if module.default_allowed_groups_ids is not None: - for group_id in module.default_allowed_groups_ids: - module_group_visibility = models_core.ModuleGroupVisibility( - root=module.root, - allowed_group_id=group_id, - ) - try: - initialization.create_module_group_visibility_sync( - module_visibility=module_group_visibility, - db=db, - ) - except ValueError as error: - hyperion_error_logger.fatal( - f"Startup: Could not add module visibility {module.root} in the database: {error}", - ) - if module.default_allowed_account_types is not None: - for account_type in module.default_allowed_account_types: - module_account_type_visibility = ( - models_core.ModuleAccountTypeVisibility( - root=module.root, - allowed_account_type=account_type, - ) - ) - try: - initialization.create_module_account_type_visibility_sync( - module_visibility=module_account_type_visibility, - db=db, - ) - except ValueError as error: - hyperion_error_logger.fatal( - f"Startup: Could not add module visibility {module.root} in the database: {error}", - ) + module_permissions = ( + list(module.permissions) if module.permissions else [] + ) + access_permission = next( + (p for p in module_permissions if p.startswith("access_")), + None, + ) + if access_permission: + if module.default_allowed_groups_ids is not None: + for group_id in module.default_allowed_groups_ids: + try: + initialization.create_group_permission_sync( + group_id=group_id, + permission_name=access_permission, + db=db, + ) + except ValueError as error: + hyperion_error_logger.fatal( + f"Startup: Could not add module visibility {module.root} in the database: {error}", + ) + if module.default_allowed_account_types is not None: + for account_type in module.default_allowed_account_types: + try: + initialization.create_account_type_permission_sync( + account_type=account_type, + permission_name=access_permission, + db=db, + ) + except ValueError as error: + hyperion_error_logger.fatal( + f"Startup: Could not add module visibility {module.root} in the database: {error}", + ) initialization.set_core_data_sync( coredata_core.ModuleVisibilityAwareness( roots=[module.root for module in module_list], @@ -438,6 +437,8 @@ def init_db( sync_engine=sync_engine, hyperion_error_logger=hyperion_error_logger, ) + with Session(sync_engine) as db: + initialization.clean_permissions_sync(db, permissions_list) async def init_google_API( diff --git a/app/core/auth/endpoints_auth.py b/app/core/auth/endpoints_auth.py index 6dcdfb97d7..df63af2336 100644 --- a/app/core/auth/endpoints_auth.py +++ b/app/core/auth/endpoints_auth.py @@ -41,7 +41,7 @@ from app.types.module import CoreModule from app.types.scopes_type import ScopeType from app.utils.auth.providers import BaseAuthClient -from app.utils.tools import is_user_member_of_any_group +from app.utils.tools import has_user_permission router = APIRouter(tags=["Auth"]) @@ -314,12 +314,14 @@ async def authorize_validation( status_code=status.HTTP_302_FOUND, ) - # The auth_client may restrict the usage of the client to specific Hyperion groups. - # For example, only ECLAIR members may be allowed to access the wiki - if auth_client.allowed_groups is not None: - if not is_user_member_of_any_group( - user=user, - allowed_groups=auth_client.allowed_groups, + # The auth_client may restrict the usage of the client to specific Hyperion permissions + if auth_client.permission is not None: + if not ( + await has_user_permission( + user=user, + permission_name=auth_client.permission, + db=db, + ) ): hyperion_access_logger.warning( f"Authorize-validation: user is not member of an allowed group {authorizereq.email} ({request_id})", @@ -331,18 +333,7 @@ async def authorize_validation( ), status_code=status.HTTP_302_FOUND, ) - if auth_client.allowed_account_types is not None: - if user.account_type not in auth_client.allowed_account_types: - hyperion_access_logger.warning( - f"Authorize-validation: user account type is not allowed {authorizereq.email} ({request_id})", - ) - return RedirectResponse( - settings.CLIENT_URL - + calypsso.get_message_relative_url( - message_type=calypsso.TypeMessage.user_account_type_not_allowed, - ), - status_code=status.HTTP_302_FOUND, - ) + # We generate a new authorization_code # The authorization code MUST expire # shortly after it is issued to mitigate the risk of leaks. A diff --git a/app/core/core_endpoints/cruds_core.py b/app/core/core_endpoints/cruds_core.py index 77e5942ada..c03e496f73 100644 --- a/app/core/core_endpoints/cruds_core.py +++ b/app/core/core_endpoints/cruds_core.py @@ -1,132 +1,9 @@ -from collections.abc import Sequence from datetime import datetime from sqlalchemy import delete, func, select, text from sqlalchemy.ext.asyncio import AsyncSession from app.core.core_endpoints import models_core -from app.core.groups.groups_type import AccountType -from app.core.users import models_users - - -async def get_modules_by_user( - user: models_users.CoreUser, - db: AsyncSession, -) -> list[str]: - """Return the modules a user has access to""" - - userGroupIds = [group.id for group in user.groups] - - result_group = list( - ( - await db.execute( - select(models_core.ModuleGroupVisibility.root) - .where( - models_core.ModuleGroupVisibility.allowed_group_id.in_( - userGroupIds, - ), - ) - .group_by(models_core.ModuleGroupVisibility.root), - ) - ) - .unique() - .scalars() - .all(), - ) - result_account_type = list( - ( - await db.execute( - select(models_core.ModuleAccountTypeVisibility.root).where( - models_core.ModuleAccountTypeVisibility.allowed_account_type - == user.account_type, - ), - ) - ) - .unique() - .scalars() - .all(), - ) - - return result_group + result_account_type - - -async def get_allowed_groups_by_root( - root: str, - db: AsyncSession, -) -> Sequence[str]: - """Return the groups allowed to access to a specific root""" - - result = await db.execute( - select( - models_core.ModuleGroupVisibility.allowed_group_id, - ).where(models_core.ModuleGroupVisibility.root == root), - ) - - return result.unique().scalars().all() - - -async def get_allowed_account_types_by_root( - root: str, - db: AsyncSession, -) -> Sequence[str]: - """Return the groups allowed to access to a specific root""" - - result = await db.execute( - select( - models_core.ModuleAccountTypeVisibility.allowed_account_type, - ).where(models_core.ModuleAccountTypeVisibility.root == root), - ) - - return result.unique().scalars().all() - - -async def create_module_group_visibility( - module_visibility: models_core.ModuleGroupVisibility, - db: AsyncSession, -) -> None: - """Create a new module visibility in database and return it""" - - db.add(module_visibility) - await db.flush() - - -async def create_module_account_type_visibility( - module_visibility: models_core.ModuleAccountTypeVisibility, - db: AsyncSession, -) -> None: - """Create a new module visibility in database and return it""" - - db.add(module_visibility) - await db.flush() - - -async def delete_module_group_visibility( - root: str, - allowed_group_id: str, - db: AsyncSession, -): - await db.execute( - delete(models_core.ModuleGroupVisibility).where( - models_core.ModuleGroupVisibility.root == root, - models_core.ModuleGroupVisibility.allowed_group_id == allowed_group_id, - ), - ) - await db.flush() - - -async def delete_module_account_type_visibility( - root: str, - allowed_account_type: AccountType, - db: AsyncSession, -): - await db.execute( - delete(models_core.ModuleAccountTypeVisibility).where( - models_core.ModuleAccountTypeVisibility.root == root, - models_core.ModuleAccountTypeVisibility.allowed_account_type - == allowed_account_type, - ), - ) - await db.flush() async def get_core_data_crud( diff --git a/app/core/core_endpoints/endpoints_core.py b/app/core/core_endpoints/endpoints_core.py index d31b44a493..5af8b4e546 100644 --- a/app/core/core_endpoints/endpoints_core.py +++ b/app/core/core_endpoints/endpoints_core.py @@ -3,21 +3,13 @@ from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.responses import FileResponse -from sqlalchemy.ext.asyncio import AsyncSession -from app.core.core_endpoints import cruds_core, models_core, schemas_core -from app.core.groups.groups_type import AccountType, GroupType -from app.core.users import models_users +from app.core.core_endpoints import schemas_core from app.core.utils.config import Settings from app.dependencies import ( - get_db, get_settings, - is_user, - is_user_in, ) -from app.module import module_list from app.types.module import CoreModule -from app.utils.tools import is_group_id_valid router = APIRouter(tags=["Core"]) @@ -172,140 +164,3 @@ async def get_style_file( ) async def get_favicon(): return FileResponse("assets/images/favicon.ico") - - -@router.get( - "/module-visibility/", - response_model=list[schemas_core.ModuleVisibility], - status_code=200, -) -async def get_module_visibility( - db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), -): - """ - Get all existing module_visibility. - - **This endpoint is only usable by administrators** - """ - - return_module_visibilities = [] - for module in module_list: - allowed_group_ids = await cruds_core.get_allowed_groups_by_root( - root=module.root, - db=db, - ) - allowed_account_types = await cruds_core.get_allowed_account_types_by_root( - root=module.root, - db=db, - ) - return_module_visibilities.append( - schemas_core.ModuleVisibility( - root=module.root, - allowed_group_ids=allowed_group_ids, - allowed_account_types=allowed_account_types, - ), - ) - - return return_module_visibilities - - -@router.get( - "/module-visibility/me", - response_model=list[str], - status_code=200, -) -async def get_user_modules_visibility( - db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), -): - """ - Get group user accessible root - - **This endpoint is only usable by everyone** - """ - - return await cruds_core.get_modules_by_user(user=user, db=db) - - -@router.post( - "/module-visibility/", - status_code=201, -) -async def add_module_visibility( - module_visibility: schemas_core.ModuleVisibilityCreate, - db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), -): - """ - Add a new group or account type to a module - - **This endpoint is only usable by administrators** - """ - if ( - module_visibility.allowed_group_id is None - and module_visibility.allowed_account_type is None - ): - raise HTTPException( - status_code=400, - detail="allowed_group_id or allowed_account_type must be set", - ) - - if module_visibility.allowed_group_id is not None: - # We need to check that loaner.group_manager_id is a valid group - if not await is_group_id_valid(module_visibility.allowed_group_id, db=db): - raise HTTPException( - status_code=400, - detail="Invalid id, group_id must be a valid group id", - ) - module_group_visibility_db = models_core.ModuleGroupVisibility( - root=module_visibility.root, - allowed_group_id=module_visibility.allowed_group_id, - ) - - await cruds_core.create_module_group_visibility( - module_visibility=module_group_visibility_db, - db=db, - ) - - if module_visibility.allowed_account_type is not None: - module_account_visibility_db = models_core.ModuleAccountTypeVisibility( - root=module_visibility.root, - allowed_account_type=module_visibility.allowed_account_type, - ) - - await cruds_core.create_module_account_type_visibility( - module_visibility=module_account_visibility_db, - db=db, - ) - - -@router.delete("/module-visibility/{root}/groups/{group_id}", status_code=204) -async def delete_module_group_visibility( - root: str, - group_id: str, - db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), -): - await cruds_core.delete_module_group_visibility( - root=root, - allowed_group_id=group_id, - db=db, - ) - - -@router.delete( - "/module-visibility/{root}/account-types/{account_type}", - status_code=204, -) -async def delete_module_account_type_visibility( - root: str, - account_type: AccountType, - db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), -): - await cruds_core.delete_module_account_type_visibility( - root=root, - allowed_account_type=account_type, - db=db, - ) diff --git a/app/core/core_endpoints/models_core.py b/app/core/core_endpoints/models_core.py index 07d3353447..6405910d36 100644 --- a/app/core/core_endpoints/models_core.py +++ b/app/core/core_endpoints/models_core.py @@ -2,7 +2,6 @@ from sqlalchemy.orm import Mapped, mapped_column -from app.core.groups.groups_type import AccountType from app.types.sqlalchemy import Base @@ -22,20 +21,6 @@ class CoreData(Base): data: Mapped[str] -class ModuleGroupVisibility(Base): - __tablename__ = "module_group_visibility" - - root: Mapped[str] = mapped_column(primary_key=True) - allowed_group_id: Mapped[str] = mapped_column(primary_key=True) - - -class ModuleAccountTypeVisibility(Base): - __tablename__ = "module_account_type_visibility" - - root: Mapped[str] = mapped_column(primary_key=True) - allowed_account_type: Mapped[AccountType] = mapped_column(primary_key=True) - - class AlembicVersion(Base): """ A table managed exclusively by Alembic, used to keep track of the database schema version. diff --git a/app/core/core_endpoints/schemas_core.py b/app/core/core_endpoints/schemas_core.py index eb132d4cef..e771f9cf88 100644 --- a/app/core/core_endpoints/schemas_core.py +++ b/app/core/core_endpoints/schemas_core.py @@ -1,8 +1,6 @@ """Common schemas file for endpoint /users et /groups because it would cause circular import""" -from pydantic import BaseModel, ConfigDict - -from app.core.groups.groups_type import AccountType +from pydantic import BaseModel class CoreInformation(BaseModel): @@ -11,17 +9,3 @@ class CoreInformation(BaseModel): ready: bool version: str minimal_titan_version_code: int - - -class ModuleVisibility(BaseModel): - root: str - allowed_group_ids: list[str] - allowed_account_types: list[AccountType] - model_config = ConfigDict(from_attributes=True) - - -class ModuleVisibilityCreate(BaseModel): - root: str - allowed_group_id: str | None = None - allowed_account_type: AccountType | None = None - model_config = ConfigDict(from_attributes=True) diff --git a/app/core/groups/factory_groups.py b/app/core/groups/factory_groups.py index 914712ccf9..169de5c639 100644 --- a/app/core/groups/factory_groups.py +++ b/app/core/groups/factory_groups.py @@ -21,7 +21,7 @@ class CoreGroupsFactory(Factory): @classmethod async def create_core_groups(cls, db: AsyncSession): - groups = ["Oui", "Pixels"] + groups = ["AEECL", "USEECL"] descriptions = ["Groupe de test", "Groupe de test 2"] for i in range(len(groups)): await cruds_groups.create_group( diff --git a/app/core/groups/groups_type.py b/app/core/groups/groups_type.py index ba5f64cb48..cf2652a18d 100644 --- a/app/core/groups/groups_type.py +++ b/app/core/groups/groups_type.py @@ -3,41 +3,21 @@ class GroupType(str, Enum): """ - In Hyperion, each user may have multiple groups. Belonging to a group gives access to a set of specific endpoints. - Usually, one or a few groups are associated to some rights over their corresponding module. For example a member of amap group is allowed to administrate the amap module + In Hyperion, each user may have multiple groups. Belonging to a group gives access to a set of specific permissions. - A group may also allow using Hyperion OAuth/Openid connect capabilities to sign in to a specific external platform. + The only hardcoded group is the admin group. + Being member of admin gives rights over all endpoints except identity based enpoints (i.e an admin won't be able to act in place of an association or a person) - Being member of admin only gives rights over admin specific endpoints. For example, an admin won't be able to administrate amap module + Other groups are created by the admin and can be associated with a set of permissions. """ - # Core groups admin = "0a25cb76-4b63-4fd3-b939-da6d9feabf28" - AE = "45649735-866a-49df-b04b-a13c74fd5886" - - # Module related groups - amap = "70db65ee-d533-4f6b-9ffa-a4d70a17b7ef" - BDE = "53a669d6-84b1-4352-8d7c-421c1fbd9c6a" - CAA = "6c6d7e88-fdb8-4e42-b2b5-3d3cfd12e7d6" - cinema = "ce5f36e6-5377-489f-9696-de70e2477300" - raid_admin = "e9e6e3d3-9f5f-4e9b-8e5f-9f5f4e9b8e5f" - ph = "4ec5ae77-f955-4309-96a5-19cc3c8be71c" - admin_cdr = "c1275229-46b2-4e53-a7c4-305513bb1a2a" - eclair = "1f841bd9-00be-41a7-96e1-860a18a46105" - BDS = "61af3e52-7ef9-4608-823a-39d51e83d1db" - seed_library = "09153d2a-14f4-49a4-be57-5d0f265261b9" - competition_admin = "2b1fc736-1288-4043-b293-14bc23adae68" - - # Auth related groups - - def __str__(self): - return f"{self.name}<{self.value}>" class AccountType(str, Enum): """ Various account types that can be created in Hyperion. - These values should match GroupType's. They are the lower level groups in Hyperion + Each account type is associated with a set of permissions. """ student = "student" diff --git a/app/core/memberships/endpoints_memberships.py b/app/core/memberships/endpoints_memberships.py index fb530c4754..a9d9c32940 100644 --- a/app/core/memberships/endpoints_memberships.py +++ b/app/core/memberships/endpoints_memberships.py @@ -84,7 +84,6 @@ async def read_association_membership( [ db_association_membership.manager_group_id, GroupType.admin, - GroupType.admin_cdr, ], ): raise HTTPException( @@ -239,7 +238,7 @@ async def read_user_memberships( # filter the query to get the managed memberships from user's groups. if user_id != user.id and not is_user_member_of_any_group( user, - [GroupType.admin, GroupType.admin_cdr], + [GroupType.admin], ): return await cruds_memberships.get_user_memberships_by_user_id( db, @@ -278,7 +277,7 @@ async def read_user_association_membership_history( if not is_user_member_of_any_group( user, - [GroupType.admin, GroupType.admin_cdr, db_membership.manager_group_id], + [GroupType.admin, db_membership.manager_group_id], ): raise HTTPException(status_code=403, detail="Unauthorized") @@ -321,7 +320,6 @@ async def create_user_membership( user, [ GroupType.admin, - GroupType.admin_cdr, db_association_membership.manager_group_id, ], ): @@ -394,7 +392,6 @@ async def add_batch_membership( user, [ GroupType.admin, - GroupType.admin_cdr, db_association_membership.manager_group_id, ], ): @@ -462,7 +459,7 @@ async def update_user_membership( if not is_user_member_of_any_group( user, - [GroupType.admin, GroupType.admin_cdr, db_membership.manager_group_id], + [GroupType.admin, db_membership.manager_group_id], ): raise HTTPException(status_code=403, detail="Unauthorized") @@ -515,7 +512,7 @@ async def delete_user_membership( if not is_user_member_of_any_group( user, - [GroupType.admin, GroupType.admin_cdr, db_membership.manager_group_id], + [GroupType.admin, db_membership.manager_group_id], ): raise HTTPException(status_code=403, detail="Unauthorized") diff --git a/app/core/memberships/factory_memberships.py b/app/core/memberships/factory_memberships.py index 7c1f6dc313..2cd60929a6 100644 --- a/app/core/memberships/factory_memberships.py +++ b/app/core/memberships/factory_memberships.py @@ -4,7 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession -from app.core.groups.groups_type import GroupType +from app.core.groups.factory_groups import CoreGroupsFactory from app.core.memberships import cruds_memberships from app.core.memberships.schemas_memberships import ( MembershipSimple, @@ -25,11 +25,11 @@ class CoreMembershipsFactory(Factory): "USEECL", ] memberships_manager_group_id = [ - GroupType.BDE.value, - GroupType.BDS.value, + CoreGroupsFactory.groups_ids[0], + CoreGroupsFactory.groups_ids[1], ] - depends_on = [CoreUsersFactory] + depends_on = [CoreUsersFactory, CoreGroupsFactory] @classmethod async def run(cls, db: AsyncSession, settings: Settings) -> None: diff --git a/app/core/permissions/__init__.py b/app/core/permissions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/core/permissions/cruds_permissions.py b/app/core/permissions/cruds_permissions.py new file mode 100644 index 0000000000..2d091c17bc --- /dev/null +++ b/app/core/permissions/cruds_permissions.py @@ -0,0 +1,235 @@ +"""File defining the functions called by the endpoints, making queries to the table using the models""" + +from sqlalchemy import delete, select +from sqlalchemy.exc import IntegrityError +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.groups.groups_type import AccountType +from app.core.permissions import models_permissions, schemas_permissions +from app.core.permissions.type_permissions import ModulePermissions + + +async def get_permissions( + db: AsyncSession, +) -> schemas_permissions.CorePermissions: + """Return all permissions from database""" + + result_group = ( + (await db.execute(select(models_permissions.CorePermissionGroup))) + .scalars() + .all() + ) + result_account_type = ( + (await db.execute(select(models_permissions.CorePermissionAccountType))) + .scalars() + .all() + ) + + return schemas_permissions.CorePermissions( + group_permissions=[ + schemas_permissions.CoreGroupPermission( + permission_name=permission.permission_name, + group_id=permission.group_id, + ) + for permission in result_group + ], + account_type_permissions=[ + schemas_permissions.CoreAccountTypePermission( + permission_name=permission.permission_name, + account_type=permission.account_type, + ) + for permission in result_account_type + ], + ) + + +async def get_permissions_by_permission_name( + db: AsyncSession, + permission_name: ModulePermissions, +) -> schemas_permissions.CorePermissions: + """Return permissions with name from database""" + result_group = ( + ( + await db.execute( + select(models_permissions.CorePermissionGroup).where( + models_permissions.CorePermissionGroup.permission_name + == permission_name, + ), + ) + ) + .scalars() + .all() + ) + result_account_type = ( + ( + await db.execute( + select(models_permissions.CorePermissionAccountType).where( + models_permissions.CorePermissionAccountType.permission_name + == permission_name, + ), + ) + ) + .scalars() + .all() + ) + return schemas_permissions.CorePermissions( + group_permissions=[ + schemas_permissions.CoreGroupPermission( + permission_name=permission.permission_name, + group_id=permission.group_id, + ) + for permission in result_group + ], + account_type_permissions=[ + schemas_permissions.CoreAccountTypePermission( + permission_name=permission.permission_name, + account_type=permission.account_type, + ) + for permission in result_account_type + ], + ) + + +async def get_group_permission_by_group_id_and_permission_name( + db: AsyncSession, + permission_name: ModulePermissions, + group_id: str, +) -> schemas_permissions.CoreGroupPermission | None: + """Return permission with name and group id from database""" + result = ( + ( + await db.execute( + select(models_permissions.CorePermissionGroup).where( + models_permissions.CorePermissionGroup.permission_name + == permission_name, + models_permissions.CorePermissionGroup.group_id == group_id, + ), + ) + ) + .scalars() + .first() + ) + return ( + schemas_permissions.CoreGroupPermission( + permission_name=result.permission_name, + group_id=result.group_id, + ) + if result + else None + ) + + +async def get_account_type_permission_by_account_type_and_permission_name( + db: AsyncSession, + permission_name: ModulePermissions, + account_type: AccountType, +) -> schemas_permissions.CoreAccountTypePermission | None: + """Return permission with name and account type from database""" + result = ( + ( + await db.execute( + select(models_permissions.CorePermissionAccountType).where( + models_permissions.CorePermissionAccountType.permission_name + == permission_name, + models_permissions.CorePermissionAccountType.account_type + == account_type, + ), + ) + ) + .scalars() + .first() + ) + return ( + schemas_permissions.CoreAccountTypePermission( + permission_name=result.permission_name, + account_type=result.account_type, + ) + if result + else None + ) + + +async def create_group_permission( + permission: schemas_permissions.CoreGroupPermission, + db: AsyncSession, +) -> None: + """Create a new permission in database""" + permission_db = models_permissions.CorePermissionGroup( + permission_name=permission.permission_name, + group_id=permission.group_id, + ) + + db.add(permission_db) + try: + await db.commit() + except IntegrityError: + await db.rollback() + raise + + +async def create_account_type_permission( + permission: schemas_permissions.CoreAccountTypePermission, + db: AsyncSession, +) -> None: + """Create a new permission in database""" + permission_db = models_permissions.CorePermissionAccountType( + permission_name=permission.permission_name, + account_type=permission.account_type, + ) + + db.add(permission_db) + try: + await db.commit() + except IntegrityError: + await db.rollback() + raise + + +async def delete_group_permission( + db: AsyncSession, + permission: schemas_permissions.CoreGroupPermission, +) -> None: + """Delete a permission""" + await db.execute( + delete(models_permissions.CorePermissionGroup).where( + models_permissions.CorePermissionGroup.permission_name + == permission.permission_name, + models_permissions.CorePermissionGroup.group_id == permission.group_id, + ), + ) + await db.commit() + + +async def delete_account_type_permission( + db: AsyncSession, + permission: schemas_permissions.CoreAccountTypePermission, +) -> None: + """Delete a permission""" + await db.execute( + delete(models_permissions.CorePermissionAccountType).where( + models_permissions.CorePermissionAccountType.permission_name + == permission.permission_name, + models_permissions.CorePermissionAccountType.account_type + == permission.account_type, + ), + ) + await db.commit() + + +async def delete_permissions_by_permission_name( + db: AsyncSession, + permission_name: ModulePermissions, +) -> None: + """Delete permissions from database by name""" + await db.execute( + delete(models_permissions.CorePermissionGroup).where( + models_permissions.CorePermissionGroup.permission_name == permission_name, + ), + ) + await db.execute( + delete(models_permissions.CorePermissionAccountType).where( + models_permissions.CorePermissionAccountType.permission_name + == permission_name, + ), + ) + await db.commit() diff --git a/app/core/permissions/endpoints_permissions.py b/app/core/permissions/endpoints_permissions.py new file mode 100644 index 0000000000..c3eff8fed5 --- /dev/null +++ b/app/core/permissions/endpoints_permissions.py @@ -0,0 +1,186 @@ +""" +File defining the API itself, using fastAPI and schemas, and calling the cruds functions + +Group management is part of the core of Hyperion. These endpoints allow managing membership between users and groups. +""" + +import logging +from typing import TYPE_CHECKING, cast + +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.groups.groups_type import GroupType +from app.core.permissions import cruds_permissions, schemas_permissions +from app.dependencies import ( + get_db, + is_user, + is_user_in, +) +from app.types.module import CoreModule +from app.utils.tools import is_group_id_valid + +if TYPE_CHECKING: + from app.core.permissions.type_permissions import ModulePermissions + +router = APIRouter(tags=["Permissions"]) + +core_module = CoreModule( + root="permissions", + tag="Permissions", + router=router, + factory=None, +) + +hyperion_security_logger = logging.getLogger("hyperion.security") + + +@router.get( + "/permissions/list", + response_model=list[str], + status_code=200, +) +async def read_permissions_list( + user=Depends(is_user()), +): + """ + Return all permissions from database + """ + from app.module import full_name_permissions_list + + return full_name_permissions_list + + +@router.get( + "/permissions/", + response_model=schemas_permissions.CorePermissions, + status_code=200, +) +async def read_permissions( + db: AsyncSession = Depends(get_db), + user=Depends(is_user()), +): + """ + Return all permissions from database + """ + + return await cruds_permissions.get_permissions(db) + + +@router.get( + "/permissions/{permission_name}", + response_model=schemas_permissions.CorePermissions, + status_code=200, +) +async def read_permission( + permission_name: str, + db: AsyncSession = Depends(get_db), + user=Depends(is_user_in(GroupType.admin)), +): + """ + Return permission with name from database + """ + from app.module import permissions_list + + if permission_name not in permissions_list: + raise HTTPException( + status_code=404, + detail="Permission not found", + ) + permission = cast( + "ModulePermissions", + permission_name, + ) + + return await cruds_permissions.get_permissions_by_permission_name( + db, + permission, + ) + + +@router.post( + "/permissions/", + status_code=201, +) +async def create_permission( + permission: schemas_permissions.CoreGroupPermission + | schemas_permissions.CoreAccountTypePermission, + db: AsyncSession = Depends(get_db), + user=Depends(is_user_in(GroupType.admin)), +): + """ + Create a new permission in database + """ + from app.module import permissions_list + + if permission.permission_name not in permissions_list: + raise HTTPException( + status_code=404, + detail="Permission not found", + ) + if isinstance(permission, schemas_permissions.CoreGroupPermission): + if not await is_group_id_valid(permission.group_id, db): + raise HTTPException( + status_code=404, + detail="Group not found", + ) + await cruds_permissions.create_group_permission(permission, db) + else: + await cruds_permissions.create_account_type_permission(permission, db) + return {"message": "Permission created successfully"} + + +@router.delete( + "/permissions/", + status_code=204, +) +async def delete_permission( + permission: schemas_permissions.CoreGroupPermission + | schemas_permissions.CoreAccountTypePermission, + db: AsyncSession = Depends(get_db), + user=Depends(is_user_in(GroupType.admin)), +): + """ + Delete a permission from database by name + """ + from app.module import permissions_list + + if permission.permission_name not in permissions_list: + raise HTTPException( + status_code=404, + detail="Permission not found", + ) + permission_name = cast( + "ModulePermissions", + permission.permission_name, + ) + if isinstance(permission, schemas_permissions.CoreGroupPermission): + if not await is_group_id_valid(permission.group_id, db): + raise HTTPException( + status_code=404, + detail="Group not found", + ) + group_permission_db = await cruds_permissions.get_group_permission_by_group_id_and_permission_name( + db, + permission_name, + permission.group_id, + ) + if not group_permission_db: + raise HTTPException( + status_code=404, + detail="Permission not found", + ) + await cruds_permissions.delete_group_permission(db, permission) + else: + account_type_permission_db = await cruds_permissions.get_account_type_permission_by_account_type_and_permission_name( + db, + permission_name, + permission.account_type, + ) + if not account_type_permission_db: + raise HTTPException( + status_code=404, + detail="Permission not found", + ) + await cruds_permissions.delete_account_type_permission(db, permission) + return {"message": "Permission deleted successfully"} diff --git a/app/core/permissions/models_permissions.py b/app/core/permissions/models_permissions.py new file mode 100644 index 0000000000..38299388ee --- /dev/null +++ b/app/core/permissions/models_permissions.py @@ -0,0 +1,26 @@ +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped, mapped_column + +from app.core.groups.groups_type import AccountType +from app.types.sqlalchemy import Base + + +class CorePermissionGroup(Base): + __tablename__ = "core_permission_group" + + permission_name: Mapped[str] = mapped_column(primary_key=True, index=True) + group_id: Mapped[str] = mapped_column( + ForeignKey("core_group.id"), + index=True, + primary_key=True, + ) + + +class CorePermissionAccountType(Base): + __tablename__ = "core_permission_account_type" + + permission_name: Mapped[str] = mapped_column(primary_key=True, index=True) + account_type: Mapped[AccountType] = mapped_column( + index=True, + primary_key=True, + ) diff --git a/app/core/permissions/schemas_permissions.py b/app/core/permissions/schemas_permissions.py new file mode 100644 index 0000000000..6a770a0ab7 --- /dev/null +++ b/app/core/permissions/schemas_permissions.py @@ -0,0 +1,18 @@ +from pydantic import BaseModel + +from app.core.groups.groups_type import AccountType + + +class CoreGroupPermission(BaseModel): + permission_name: str + group_id: str + + +class CoreAccountTypePermission(BaseModel): + permission_name: str + account_type: AccountType + + +class CorePermissions(BaseModel): + group_permissions: list[CoreGroupPermission] = [] + account_type_permissions: list[CoreAccountTypePermission] = [] diff --git a/app/core/permissions/type_permissions.py b/app/core/permissions/type_permissions.py new file mode 100644 index 0000000000..3dc5e83dd6 --- /dev/null +++ b/app/core/permissions/type_permissions.py @@ -0,0 +1,5 @@ +from enum import Enum + + +class ModulePermissions(str, Enum): + pass diff --git a/app/dependencies.py b/app/dependencies.py index eba12c3778..daf0d72b95 100644 --- a/app/dependencies.py +++ b/app/dependencies.py @@ -25,6 +25,8 @@ async def get_users(db: AsyncSession = Depends(get_db)): from app.core.groups.groups_type import AccountType, GroupType, get_ecl_account_types from app.core.payment.payment_tool import PaymentTool from app.core.payment.types_payment import HelloAssoConfigName +from app.core.permissions import cruds_permissions, schemas_permissions +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import cruds_users, models_users from app.core.utils import security from app.core.utils.config import Settings, construct_prod_settings @@ -375,8 +377,8 @@ async def get_user_from_user_id( def is_user( - excluded_groups: list[GroupType] | None = None, - included_groups: list[GroupType] | None = None, + excluded_groups: list[GroupType | str] | None = None, + included_groups: list[GroupType | str] | None = None, excluded_account_types: list[AccountType] | None = None, included_account_types: list[AccountType] | None = None, exclude_external: bool = False, @@ -497,3 +499,61 @@ async def is_user_in( return user return is_user_in + + +def is_user_allowed_to( + permissions_name: list[ModulePermissions], + db: AsyncSession = Depends(get_db), +) -> Callable[[models_users.CoreUser], Coroutine[Any, Any, models_users.CoreUser]]: + """ + Generate a dependency which will: + * check if the request header contains a valid API JWT token (a token that can be used to call endpoints from the API) + * make sure the user making the request exists + * make sure the user has the permission with the given name + * return the corresponding user `models_users.CoreUser` object + """ + + async def is_user_allowed_to( + user: models_users.CoreUser = Depends( + is_user(), + ), + db: AsyncSession = Depends(get_db), + ) -> models_users.CoreUser: + """ + A dependency that checks that user has the permission with the given name then returns the corresponding user. + """ + if GroupType.admin in [group.id for group in user.groups]: + return user + + group_permissions: list[schemas_permissions.CoreGroupPermission] = [] + account_type_permissions: list[ + schemas_permissions.CoreAccountTypePermission + ] = [] + for permission_name in permissions_name: + group_permissions += ( + await cruds_permissions.get_permissions_by_permission_name( + db, + permission_name, + ) + ).group_permissions + account_type_permissions += ( + await cruds_permissions.get_permissions_by_permission_name( + db, + permission_name, + ) + ).account_type_permissions + + if not any( + permission.group_id in [group.id for group in user.groups] + for permission in group_permissions + ) and user.account_type not in [ + permission.account_type for permission in account_type_permissions + ]: + raise HTTPException( + status_code=403, + detail="Unauthorized, user does not have the required permission", + ) + + return user + + return is_user_allowed_to diff --git a/app/module.py b/app/module.py index 8cce42d9e1..ed0f9d305f 100644 --- a/app/module.py +++ b/app/module.py @@ -3,6 +3,7 @@ from pathlib import Path from app.types.module import CoreModule, Module +from app.utils.auth.providers import AuthPermissions hyperion_error_logger = logging.getLogger("hyperion.error") @@ -36,3 +37,47 @@ hyperion_error_logger.error( f"Core module {endpoints_file} does not declare a core module. It won't be enabled.", ) + + +permissions_list: list[str] = [] +full_name_permissions_list: list[str] = [] + + +class DuplicatePermissionsError(Exception): + def __init__(self, permissions: list[list[str]]): + arranged_permissions = [ + f" - '{permission[0].split('.')[1]}': {', '.join(permission)}" + for permission in permissions + ] + super().__init__( + "Duplicate permissions name found in modules:\n" + + "\n".join(arranged_permissions), + ) + + +for each_module in all_modules: + if each_module.permissions: + permissions_list.extend( + each_module.permissions.__members__.keys(), + ) + full_name_permissions_list.extend( + [ + f"{each_module.root}.{name}" + for name in each_module.permissions.__members__ + ], + ) +permissions_list.extend(AuthPermissions.__members__.keys()) +full_name_permissions_list.extend( + [f"Auth.{name}" for name in AuthPermissions.__members__], +) +if len(set(permissions_list)) != len(permissions_list): + duplicates = list({x for x in permissions_list if permissions_list.count(x) > 1}) + full_name_duplicates = [ + [name for name in full_name_permissions_list if name.split(".")[1] == duplicate] + for duplicate in duplicates + ] + hyperion_error_logger.error( + "Duplicate permissions found in modules: %s", + full_name_duplicates, + ) + raise DuplicatePermissionsError(full_name_duplicates) diff --git a/app/modules/advert/endpoints_advert.py b/app/modules/advert/endpoints_advert.py index ba973aff1d..0eae5eacfc 100644 --- a/app/modules/advert/endpoints_advert.py +++ b/app/modules/advert/endpoints_advert.py @@ -9,13 +9,13 @@ from app.core.groups.groups_type import AccountType, GroupType from app.core.notification.schemas_notification import Message from app.core.notification.utils_notification import get_topic_by_root_and_identifier +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import models_users from app.dependencies import ( get_db, get_notification_manager, get_notification_tool, - is_user_an_ecl_member, - is_user_in, + is_user_allowed_to, ) from app.modules.advert import ( cruds_advert, @@ -29,17 +29,26 @@ from app.utils.communication.notifications import NotificationManager, NotificationTool from app.utils.tools import ( get_file_from_data, + has_user_permission, is_group_id_valid, is_user_member_of_any_group, save_file_as_data, ) root = "advert" + + +class AdvertPermissions(ModulePermissions): + access_adverts = "access_adverts" + manage_advertisers = "manage_advertisers" + + module = Module( root=root, tag="Advert", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=AdvertFactory(), + permissions=AdvertPermissions, ) hyperion_error_logger = logging.getLogger("hyperion.error") @@ -52,7 +61,9 @@ ) async def read_advertisers( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AdvertPermissions.access_adverts]), + ), ): """ Get existing advertisers. @@ -71,8 +82,10 @@ async def read_advertisers( async def create_advertiser( advertiser: schemas_advert.AdvertiserBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), notification_manager: NotificationManager = Depends(get_notification_manager), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AdvertPermissions.manage_advertisers]), + ), ): """ Create a new advertiser. @@ -117,7 +130,9 @@ async def create_advertiser( async def delete_advertiser( advertiser_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AdvertPermissions.manage_advertisers]), + ), ): """ Delete an advertiser. All adverts associated with the advertiser will also be deleted from the database. @@ -148,7 +163,9 @@ async def update_advertiser( advertiser_id: str, advertiser_update: schemas_advert.AdvertiserUpdate, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AdvertPermissions.manage_advertisers]), + ), ): """ Update an advertiser @@ -179,7 +196,9 @@ async def update_advertiser( ) async def get_current_user_advertisers( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AdvertPermissions.access_adverts]), + ), ): """ Return all advertisers the current user can manage. @@ -202,7 +221,9 @@ async def get_current_user_advertisers( async def read_adverts( advertisers: list[str] = Query(default=[]), db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AdvertPermissions.access_adverts]), + ), ): """ Get existing adverts. If advertisers optional parameter is used, search adverts by advertisers @@ -226,7 +247,9 @@ async def read_adverts( async def read_advert( advert_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AdvertPermissions.access_adverts]), + ), ): """ Get an advert @@ -245,7 +268,9 @@ async def read_advert( async def create_advert( advert: schemas_advert.AdvertBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AdvertPermissions.access_adverts]), + ), notification_tool: NotificationTool = Depends(get_notification_tool), ): """ @@ -308,7 +333,9 @@ async def update_advert( advert_id: str, advert_update: schemas_advert.AdvertUpdate, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AdvertPermissions.access_adverts]), + ), ): """ Edit an advert @@ -345,7 +372,9 @@ async def update_advert( async def delete_advert( advert_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AdvertPermissions.access_adverts]), + ), ): """ Delete an advert @@ -361,7 +390,11 @@ async def delete_advert( if not is_user_member_of_any_group( user, - [GroupType.admin, advert.advertiser.group_manager_id], + [advert.advertiser.group_manager_id], + ) and not await has_user_permission( + user, + AdvertPermissions.manage_advertisers, + db, ): raise HTTPException( status_code=403, @@ -379,7 +412,9 @@ async def delete_advert( async def read_advert_image( advert_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AdvertPermissions.access_adverts]), + ), ): """ Get the image of an advert @@ -408,7 +443,9 @@ async def read_advert_image( async def create_advert_image( advert_id: str, image: UploadFile = File(...), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AdvertPermissions.access_adverts]), + ), db: AsyncSession = Depends(get_db), ): """ diff --git a/app/modules/advert/factory_advert.py b/app/modules/advert/factory_advert.py index 521f508e8e..e3878f739f 100644 --- a/app/modules/advert/factory_advert.py +++ b/app/modules/advert/factory_advert.py @@ -3,14 +3,14 @@ from sqlalchemy.ext.asyncio import AsyncSession -from app.core.groups.groups_type import GroupType +from app.core.groups.factory_groups import CoreGroupsFactory from app.core.utils.config import Settings from app.modules.advert import cruds_advert, models_advert from app.types.factory import Factory class AdvertFactory(Factory): - depends_on = [] + depends_on = [CoreGroupsFactory] @classmethod async def run(cls, db: AsyncSession, settings: Settings) -> None: @@ -18,7 +18,7 @@ async def run(cls, db: AsyncSession, settings: Settings) -> None: id=str(uuid.uuid4()), name="Le BDE", adverts=[], - group_manager_id=str(GroupType.BDE.value), + group_manager_id=CoreGroupsFactory.groups_ids[0], ) await cruds_advert.create_advert( @@ -51,7 +51,7 @@ async def run(cls, db: AsyncSession, settings: Settings) -> None: id=str(uuid.uuid4()), name="Eclair ces bgs", adverts=[], - group_manager_id=str(GroupType.eclair.value), + group_manager_id=CoreGroupsFactory.groups_ids[1], ) await cruds_advert.create_advert( diff --git a/app/modules/amap/endpoints_amap.py b/app/modules/amap/endpoints_amap.py index 62542cdd67..957bae2141 100644 --- a/app/modules/amap/endpoints_amap.py +++ b/app/modules/amap/endpoints_amap.py @@ -6,8 +6,9 @@ from redis import Redis from sqlalchemy.ext.asyncio import AsyncSession -from app.core.groups.groups_type import AccountType, GroupType +from app.core.groups.groups_type import AccountType from app.core.notification.schemas_notification import Message, Topic +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import cruds_users, models_users, schemas_users from app.core.users.endpoints_users import read_user from app.dependencies import ( @@ -15,8 +16,7 @@ get_notification_tool, get_redis_client, get_request_id, - is_user_a_member, - is_user_in, + is_user_allowed_to, ) from app.modules.amap import cruds_amap, models_amap, schemas_amap from app.modules.amap.factory_amap import AmapFactory @@ -24,7 +24,13 @@ from app.types.module import Module from app.utils.communication.notifications import NotificationTool from app.utils.redis import locker_get, locker_set -from app.utils.tools import is_user_member_of_any_group +from app.utils.tools import has_user_permission + + +class AmapPermissions(ModulePermissions): + access_amap = "access_amap" + manage_amap = "manage_amap" + root = "amap" amap_topic = Topic( @@ -41,6 +47,7 @@ default_allowed_account_types=[AccountType.student, AccountType.staff], registred_topics=[amap_topic], factory=AmapFactory(), + permissions=AmapPermissions, ) hyperion_amap_logger = logging.getLogger("hyperion.amap") @@ -54,12 +61,14 @@ ) async def get_products( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): """ Return all products - **The user must be a member of the group AMAP to use this endpoint** + **The user must be a member of a group authorized to use manage AMAP to use this endpoint** """ return await cruds_amap.get_products(db) @@ -72,12 +81,14 @@ async def get_products( async def create_product( product: schemas_amap.ProductSimple, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): """ Create a new product - **The user must be a member of the group AMAP to use this endpoint** + **The user must be a member of a group authorized to use manage AMAP to use this endpoint** """ db_product = models_amap.Product(id=str(uuid.uuid4()), **product.__dict__) @@ -92,7 +103,9 @@ async def create_product( async def get_product_by_id( product_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.access_amap]), + ), ): """ Get a specific product @@ -113,12 +126,14 @@ async def edit_product( product_id: str, product_update: schemas_amap.ProductEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): """ Edit a product - **The user must be a member of the group AMAP to use this endpoint** + **The user must be a member of a group authorized to use manage AMAP to use this endpoint** """ product = await cruds_amap.get_product_by_id(db=db, product_id=product_id) @@ -139,12 +154,14 @@ async def edit_product( async def delete_product( product_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): """ Delete a product. A product can not be deleted if it is already used in a delivery. - **The user must be a member of the group AMAP to use this endpoint** + **The user must be a member of a group authorized to use manage AMAP to use this endpoint** """ product = await cruds_amap.get_product_by_id(db=db, product_id=product_id) @@ -167,7 +184,9 @@ async def delete_product( ) async def get_deliveries( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.access_amap]), + ), ): """ Get all deliveries. @@ -183,12 +202,14 @@ async def get_deliveries( async def create_delivery( delivery: schemas_amap.DeliveryBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): """ Create a new delivery. - **The user must be a member of the group AMAP to use this endpoint** + **The user must be a member of a group authorized to use manage AMAP to use this endpoint** """ db_delivery = schemas_amap.DeliveryComplete( @@ -215,12 +236,14 @@ async def create_delivery( async def delete_delivery( delivery_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): """ Delete a delivery. - **The user must be a member of the group AMAP to use this endpoint** + **The user must be a member of a group authorized to use manage AMAP to use this endpoint** """ delivery_db = await cruds_amap.get_delivery_by_id(db=db, delivery_id=delivery_id) @@ -244,12 +267,14 @@ async def edit_delivery( delivery_id: str, delivery: schemas_amap.DeliveryUpdate, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): """ Edit a delivery. - **The user must be a member of the group AMAP to use this endpoint** + **The user must be a member of a group authorized to use manage AMAP to use this endpoint** """ delivery_db = await cruds_amap.get_delivery_by_id(db=db, delivery_id=delivery_id) @@ -273,12 +298,14 @@ async def add_product_to_delivery( products_ids: schemas_amap.DeliveryProductsUpdate, delivery_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): """ Add `product_id` product to `delivery_id` delivery. This endpoint will only add a membership between the two objects. - **The user must be a member of the group AMAP to use this endpoint** + **The user must be a member of a group authorized to use manage AMAP to use this endpoint** """ delivery = await cruds_amap.get_delivery_by_id(db=db, delivery_id=delivery_id) if delivery is None: @@ -305,12 +332,14 @@ async def remove_product_from_delivery( delivery_id: str, products_ids: schemas_amap.DeliveryProductsUpdate, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): """ Remove a given product from a delivery. This won't delete the product nor the delivery. - **The user must be a member of the group AMAP to use this endpoint** + **The user must be a member of a group authorized to use manage AMAP to use this endpoint** """ delivery = await cruds_amap.get_delivery_by_id(db=db, delivery_id=delivery_id) if delivery is None: @@ -337,12 +366,14 @@ async def remove_product_from_delivery( async def get_orders_from_delivery( delivery_id: str, db: AsyncSession = Depends(get_db), - user_req: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user_req: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): """ Get orders from a delivery. - **The user must be a member of the group AMAP to use this endpoint** + **The user must be a member of a group authorized to use manage AMAP to use this endpoint** """ delivery = await cruds_amap.get_delivery_by_id(db=db, delivery_id=delivery_id) if delivery is None: @@ -371,12 +402,14 @@ async def get_orders_from_delivery( async def get_order_by_id( order_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): """ Get content of an order. - **The user must be a member of the group AMAP to use this endpoint** + **The user must be a member of a group authorized to use manage AMAP to use this endpoint** """ order = await cruds_amap.get_order_by_id(order_id=order_id, db=db) @@ -396,7 +429,9 @@ async def add_order_to_delievery( order: schemas_amap.OrderBase, db: AsyncSession = Depends(get_db), redis_client: Redis | None = Depends(get_redis_client), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.access_amap]), + ), request_id: str = Depends(get_request_id), ): """ @@ -419,7 +454,8 @@ async def add_order_to_delievery( raise HTTPException(status_code=400, detail="Invalid request") if not ( - user.id == order.user_id or is_user_member_of_any_group(user, [GroupType.amap]) + user.id == order.user_id + or await has_user_permission(user, AmapPermissions.manage_amap, db) ): raise HTTPException( status_code=403, @@ -509,7 +545,9 @@ async def edit_order_from_delivery( order: schemas_amap.OrderEdit, db: AsyncSession = Depends(get_db), redis_client: Redis | None = Depends(get_redis_client), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.access_amap]), + ), request_id: str = Depends(get_request_id), ): """ @@ -537,7 +575,7 @@ async def edit_order_from_delivery( if not ( user.id == previous_order.user_id - or is_user_member_of_any_group(user, [GroupType.amap]) + or await has_user_permission(user, AmapPermissions.manage_amap, db) ): raise HTTPException( status_code=403, @@ -623,7 +661,9 @@ async def remove_order( order_id: str, db: AsyncSession = Depends(get_db), redis_client: Redis | None = Depends(get_redis_client), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.access_amap]), + ), request_id: str = Depends(get_request_id), ): """ @@ -631,7 +671,7 @@ async def remove_order( **A member of the group AMAP can delete orders of other users** """ - is_user_admin = is_user_member_of_any_group(user, [GroupType.amap]) + is_user_admin = await has_user_permission(user, AmapPermissions.manage_amap, db) order = await cruds_amap.get_order_by_id(db=db, order_id=order_id) if not order: raise HTTPException(status_code=404, detail="No order found") @@ -694,7 +734,9 @@ async def remove_order( async def open_ordering_of_delivery( delivery_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), notification_tool: NotificationTool = Depends(get_notification_tool), ): delivery = await cruds_amap.get_delivery_by_id(db=db, delivery_id=delivery_id) @@ -727,7 +769,9 @@ async def open_ordering_of_delivery( async def lock_delivery( delivery_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): delivery = await cruds_amap.get_delivery_by_id(db=db, delivery_id=delivery_id) if delivery is None: @@ -748,7 +792,9 @@ async def lock_delivery( async def mark_delivery_as_delivered( delivery_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): delivery = await cruds_amap.get_delivery_by_id(db=db, delivery_id=delivery_id) if delivery is None: @@ -769,7 +815,9 @@ async def mark_delivery_as_delivered( async def archive_of_delivery( delivery_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): delivery = await cruds_amap.get_delivery_by_id(db=db, delivery_id=delivery_id) if delivery is None: @@ -791,12 +839,14 @@ async def archive_of_delivery( ) async def get_users_cash( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): """ Get cash from all users. - **The user must be a member of the group AMAP to use this endpoint** + **The user must be a member of a group authorized to use manage AMAP to use this endpoint** """ return await cruds_amap.get_users_cash(db) @@ -809,7 +859,9 @@ async def get_users_cash( async def get_cash_by_id( user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.access_amap]), + ), ): """ Get cash from a specific user. @@ -820,7 +872,10 @@ async def get_cash_by_id( if user_db is None: raise HTTPException(status_code=404, detail="User not found") - if not (user_id == user.id or is_user_member_of_any_group(user, [GroupType.amap])): + if not ( + user_id == user.id + or await has_user_permission(user, AmapPermissions.manage_amap, db) + ): raise HTTPException( status_code=403, detail="Users that are not member of the group AMAP can only access the endpoint for their own user_id.", @@ -849,14 +904,16 @@ async def create_cash_of_user( user_id: str, cash: schemas_amap.CashEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), request_id: str = Depends(get_request_id), notification_tool: NotificationTool = Depends(get_notification_tool), ): """ Create cash for an user. - **The user must be a member of the group AMAP to use this endpoint** + **The user must be a member of a group authorized to use manage AMAP to use this endpoint** """ user_db = await read_user(user_id=user_id, db=db) @@ -909,14 +966,16 @@ async def edit_cash_by_id( user_id: str, balance: schemas_amap.CashEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), request_id: str = Depends(get_request_id), ): """ Edit cash for an user. This will add the balance to the current balance. A negative value can be provided to remove money from the user. - **The user must be a member of the group AMAP to use this endpoint** + **The user must be a member of a group authorized to use manage AMAP to use this endpoint** """ user_db = await read_user(user_id=user_id, db=db) if not user_db: @@ -944,7 +1003,9 @@ async def edit_cash_by_id( async def get_orders_of_user( user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.access_amap]), + ), ): """ Get orders from an user. @@ -955,7 +1016,9 @@ async def get_orders_of_user( if not user_requested: raise HTTPException(status_code=404, detail="User not found") - if not (user_id == user.id or is_user_member_of_any_group(user, [GroupType.amap])): + if not ( + user_id == user.id or has_user_permission(user, AmapPermissions.manage_amap, db) + ): raise HTTPException( status_code=403, detail="Users that are not member of the group AMAP can only access the endpoint for their own user_id.", @@ -978,7 +1041,9 @@ async def get_orders_of_user( ) async def get_information( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.access_amap]), + ), ): """ Return all information @@ -1002,12 +1067,14 @@ async def get_information( async def edit_information( edit_information: schemas_amap.InformationEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.amap)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([AmapPermissions.manage_amap]), + ), ): """ Update information - **The user must be a member of the group AMAP to use this endpoint** + **The user must be a member of a group authorized to use manage AMAP to use this endpoint** """ # We need to check if informations are already in the database diff --git a/app/modules/booking/endpoints_booking.py b/app/modules/booking/endpoints_booking.py index 528243b31e..5db723bfba 100644 --- a/app/modules/booking/endpoints_booking.py +++ b/app/modules/booking/endpoints_booking.py @@ -7,14 +7,14 @@ from zoneinfo import ZoneInfo from app.core.groups import cruds_groups -from app.core.groups.groups_type import AccountType, GroupType +from app.core.groups.groups_type import AccountType from app.core.notification.schemas_notification import Message +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import models_users from app.dependencies import ( get_db, get_notification_tool, - is_user_an_ecl_member, - is_user_in, + is_user_allowed_to, ) from app.modules.booking import ( cruds_booking, @@ -27,11 +27,19 @@ from app.utils.communication.notifications import NotificationTool from app.utils.tools import is_group_id_valid, is_user_member_of_any_group + +class BookingPermissions(ModulePermissions): + access_booking = "access_booking" + manage_managers = "manage_managers" + manage_rooms = "manage_rooms" + + module = Module( root="booking", tag="Booking", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=BookingFactory(), + permissions=BookingPermissions, ) hyperion_error_logger = logging.getLogger("hyperion.error") @@ -44,7 +52,9 @@ ) async def get_managers( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.manage_managers]), + ), ): """ Get existing managers. @@ -63,7 +73,9 @@ async def get_managers( async def create_manager( manager: schemas_booking.ManagerBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.manage_managers]), + ), ): """ Create a manager. @@ -95,7 +107,9 @@ async def update_manager( manager_id: str, manager_update: schemas_booking.ManagerUpdate, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.manage_managers]), + ), ): """ Update a manager, the request should contain a JSON with the fields to change (not necessarily all fields) and their new value. @@ -127,7 +141,9 @@ async def update_manager( async def delete_manager( manager_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.manage_managers]), + ), ): """ Delete a manager only if the manager is not linked to any room @@ -151,7 +167,9 @@ async def delete_manager( ) async def get_current_user_managers( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.access_booking]), + ), ): """ Return all managers the current user is a member. @@ -169,7 +187,9 @@ async def get_current_user_managers( ) async def get_bookings_for_manager( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.access_booking]), + ), ): """ Return all bookings a user can manage. @@ -192,7 +212,9 @@ async def get_bookings_for_manager( ) async def get_confirmed_bookings_for_manager( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.access_booking]), + ), ): """ Return all confirmed bookings a user can manage. @@ -214,7 +236,9 @@ async def get_confirmed_bookings_for_manager( ) async def get_confirmed_bookings( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.access_booking]), + ), ): """ Return all confirmed bookings. @@ -232,7 +256,9 @@ async def get_confirmed_bookings( ) async def get_applicant_bookings( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.access_booking]), + ), ): """ Get the user bookings. @@ -250,7 +276,9 @@ async def get_applicant_bookings( async def create_booking( booking: schemas_booking.BookingBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.access_booking]), + ), notification_tool: NotificationTool = Depends(get_notification_tool), ): """ @@ -301,7 +329,9 @@ async def edit_booking( booking_id: str, booking_edit: schemas_booking.BookingEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.access_booking]), + ), ): """ Edit a booking. @@ -337,7 +367,9 @@ async def confirm_booking( booking_id: str, decision: Decision, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.access_booking]), + ), ): """ Give a decision to a booking. @@ -370,7 +402,9 @@ async def confirm_booking( async def delete_booking( booking_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.access_booking]), + ), ): """ Remove a booking. @@ -400,7 +434,9 @@ async def delete_booking( ) async def get_rooms( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.access_booking]), + ), ): """ Get all rooms. @@ -419,7 +455,9 @@ async def get_rooms( async def create_room( room: schemas_booking.RoomBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.manage_rooms]), + ), ): """ Create a new room in database. @@ -442,7 +480,9 @@ async def edit_room( room_id: str, room: schemas_booking.RoomBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.manage_rooms]), + ), ): """ Edit a room. @@ -460,7 +500,9 @@ async def edit_room( async def delete_room( room_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([BookingPermissions.manage_rooms]), + ), ): """ Delete a room only if there are not future or ongoing bookings of this room diff --git a/app/modules/booking/factory_booking.py b/app/modules/booking/factory_booking.py index d61d6e0b06..8319016abc 100644 --- a/app/modules/booking/factory_booking.py +++ b/app/modules/booking/factory_booking.py @@ -3,7 +3,7 @@ from sqlalchemy.ext.asyncio import AsyncSession -from app.core.groups.groups_type import GroupType +from app.core.groups.factory_groups import CoreGroupsFactory from app.core.users.factory_users import CoreUsersFactory from app.core.utils.config import Settings from app.modules.booking import ( @@ -16,7 +16,7 @@ class BookingFactory(Factory): - depends_on = [CoreUsersFactory] + depends_on = [CoreUsersFactory, CoreGroupsFactory] @classmethod async def run(cls, db: AsyncSession, settings: Settings) -> None: @@ -27,7 +27,7 @@ async def run(cls, db: AsyncSession, settings: Settings) -> None: manager=models_booking.Manager( id=booking_manager_id, name="BDE", - group_id=GroupType.BDE, + group_id=CoreGroupsFactory.groups_ids[0], rooms=[], ), ) diff --git a/app/modules/calendar/endpoints_calendar.py b/app/modules/calendar/endpoints_calendar.py index 3b69bbee15..e0dee79ec2 100644 --- a/app/modules/calendar/endpoints_calendar.py +++ b/app/modules/calendar/endpoints_calendar.py @@ -5,9 +5,13 @@ from fastapi.responses import FileResponse from sqlalchemy.ext.asyncio import AsyncSession -from app.core.groups.groups_type import AccountType, GroupType +from app.core.groups.groups_type import AccountType +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import models_users -from app.dependencies import get_db, is_user_an_ecl_member, is_user_in +from app.dependencies import ( + get_db, + is_user_allowed_to, +) from app.modules.calendar import ( cruds_calendar, models_calendar, @@ -16,13 +20,21 @@ from app.modules.calendar.factory_calendar import CalendarFactory from app.modules.calendar.types_calendar import Decision from app.types.module import Module -from app.utils.tools import is_user_member_of_any_group +from app.utils.tools import has_user_permission + + +class CalendarPermissions(ModulePermissions): + access_calendar = "access_calendar" + manage_events = "manage_events" + create_ical = "create_ical" + module = Module( root="event", tag="Calendar", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=CalendarFactory(), + permissions=CalendarPermissions, ) ical_file_path = "data/ics/ae_calendar.ics" @@ -35,7 +47,9 @@ ) async def get_events( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.BDE)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CalendarPermissions.manage_events]), + ), ): """Get all events from the database.""" return await cruds_calendar.get_all_events(db=db) @@ -48,7 +62,9 @@ async def get_events( ) async def get_confirmed_events( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CalendarPermissions.access_calendar]), + ), ): """ Get all confirmed events. @@ -66,16 +82,19 @@ async def get_confirmed_events( async def get_applicant_bookings( applicant_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CalendarPermissions.access_calendar]), + ), ): """ Get one user bookings. **Usable by the user or admins** """ - if user.id == applicant_id or is_user_member_of_any_group( + if user.id == applicant_id or await has_user_permission( user, - [GroupType.BDE], + CalendarPermissions.manage_events, + db, ): return await cruds_calendar.get_applicant_events( db=db, @@ -92,7 +111,9 @@ async def get_applicant_bookings( async def get_event_by_id( event_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CalendarPermissions.access_calendar]), + ), ): """Get an event's information by its id.""" @@ -110,7 +131,9 @@ async def get_event_by_id( async def get_event_applicant( event_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.BDE)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CalendarPermissions.manage_events]), + ), ): event = await cruds_calendar.get_event(db=db, event_id=event_id) if event is not None: @@ -126,7 +149,9 @@ async def get_event_applicant( async def add_event( event: schemas_calendar.EventBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CalendarPermissions.access_calendar]), + ), ): """Add an event to the calendar.""" @@ -150,7 +175,9 @@ async def edit_bookings_id( event_id: str, event_edit: schemas_calendar.EventEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CalendarPermissions.access_calendar]), + ), ): """ Edit an event. @@ -161,7 +188,11 @@ async def edit_bookings_id( if event is not None and not ( (user.id == event.applicant_id and event.decision == Decision.pending) - or is_user_member_of_any_group(user, [GroupType.BDE]) + or await has_user_permission( + user, + CalendarPermissions.manage_events, + db, + ) ): raise HTTPException( status_code=403, @@ -179,7 +210,9 @@ async def confirm_booking( event_id: str, decision: Decision, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.BDE)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CalendarPermissions.manage_events]), + ), ): """ Give a decision to an event. @@ -196,7 +229,9 @@ async def confirm_booking( async def delete_bookings_id( event_id, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CalendarPermissions.access_calendar]), + ), ): """ Remove an event. @@ -207,7 +242,11 @@ async def delete_bookings_id( if event is not None and ( (user.id == event.applicant_id and event.decision == Decision.pending) - or is_user_member_of_any_group(user, [GroupType.BDE]) + or await has_user_permission( + user, + CalendarPermissions.manage_events, + db, + ) ): await cruds_calendar.delete_event(event_id=event_id, db=db) @@ -224,7 +263,9 @@ async def delete_bookings_id( ) async def recreate_ical_file( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CalendarPermissions.create_ical]), + ), ): """ Create manually the icalendar file diff --git a/app/modules/campaign/cruds_campaign.py b/app/modules/campaign/cruds_campaign.py index 4a97aab0a9..48df1c7ba8 100644 --- a/app/modules/campaign/cruds_campaign.py +++ b/app/modules/campaign/cruds_campaign.py @@ -4,6 +4,7 @@ from fastapi import HTTPException from sqlalchemy import delete, select, update +from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload @@ -31,51 +32,17 @@ async def get_status( # Since this is the only place a row can be added to the status table, there should never be more than one row in the table status_model = models_campaign.Status(status=StatusType.waiting, id="id") db.add(status_model) - await db.flush() return StatusType.waiting # The status is contained in the only result returned by the database return status[0].status -async def get_voters(db: AsyncSession) -> Sequence[models_campaign.VoterGroups]: - result = await db.execute(select(models_campaign.VoterGroups)) - return result.scalars().all() - - -async def add_voter( - voter: models_campaign.VoterGroups, - db: AsyncSession, -) -> None: - db.add(voter) - await db.flush() - - -async def delete_voter_by_group_id( - group_id: str, - db: AsyncSession, -) -> None: - await db.execute( - delete(models_campaign.VoterGroups).where( - models_campaign.VoterGroups.group_id == group_id, - ), - ) - await db.flush() - - -async def delete_voters( - db: AsyncSession, -) -> None: - await db.execute(delete(models_campaign.VoterGroups)) - await db.flush() - - async def set_status( db: AsyncSession, new_status: StatusType, ): await db.execute(update(models_campaign.Status).values(status=new_status)) - await db.flush() async def get_vote_count(db: AsyncSession, section_id: str): @@ -103,7 +70,6 @@ async def add_blank_option(db: AsyncSession): members=[], ), ) - await db.flush() async def get_sections(db: AsyncSession) -> Sequence[models_campaign.Sections]: @@ -130,7 +96,6 @@ async def get_section_by_id(db: AsyncSession, section_id: str): async def add_section(db: AsyncSession, section: models_campaign.Sections) -> None: """Add a section of AEECL.""" db.add(section) - await db.flush() async def delete_section(db: AsyncSession, section_id: str) -> None: @@ -155,7 +120,6 @@ async def delete_section(db: AsyncSession, section_id: str) -> None: models_campaign.Sections.id == section_id, ), ) - await db.flush() else: raise HTTPException(status_code=400, detail="This section still has lists") else: @@ -168,7 +132,7 @@ async def delete_lists_from_section(db: AsyncSession, section_id: str) -> None: models_campaign.Lists.section_id == section_id, ), ) - await db.flush() + await db.commit() async def get_lists(db: AsyncSession) -> Sequence[models_campaign.Lists]: @@ -210,7 +174,6 @@ async def add_list( ) -> None: """Add a list to a section then add the members to the list.""" db.add(campaign_list) - await db.flush() async def remove_members_from_list( @@ -225,7 +188,6 @@ async def remove_members_from_list( models_campaign.ListMemberships.list_id == list_id, ), ) - await db.flush() async def delete_list(db: AsyncSession, list_id: str) -> None: @@ -234,7 +196,6 @@ async def delete_list(db: AsyncSession, list_id: str) -> None: await db.execute( delete(models_campaign.Lists).where(models_campaign.Lists.id == list_id), ) - await db.flush() async def delete_list_by_type(list_type: ListType, db: AsyncSession) -> None: @@ -248,7 +209,6 @@ async def delete_list_by_type(list_type: ListType, db: AsyncSession) -> None: await db.execute( delete(models_campaign.Lists).where(models_campaign.Lists.type == list_type), ) - await db.flush() async def update_list( @@ -285,13 +245,10 @@ async def update_list( ), ) - await db.flush() - async def add_vote(db: AsyncSession, vote: models_campaign.Votes) -> None: """Add a vote.""" db.add(vote) - await db.flush() async def has_user_voted_for_section( @@ -314,7 +271,6 @@ async def mark_has_voted(db: AsyncSession, user_id: str, section_id: str) -> Non """Mark user has having vote for the given section.""" has_voted = models_campaign.HasVoted(user_id=user_id, section_id=section_id) db.add(has_voted) - await db.flush() async def get_has_voted( @@ -339,7 +295,6 @@ async def delete_votes(db: AsyncSession) -> None: """Delete all votes in the db.""" await db.execute(delete(models_campaign.Votes)) await db.execute(delete(models_campaign.HasVoted)) - await db.flush() async def reset_campaign(db: AsyncSession) -> None: @@ -347,5 +302,4 @@ async def reset_campaign(db: AsyncSession) -> None: This will delete all the votes and blank list lists.""" await db.execute(delete(models_campaign.Votes)) await db.execute(delete(models_campaign.HasVoted)) - await db.flush() await delete_list_by_type(ListType.blank, db) diff --git a/app/modules/campaign/endpoints_campaign.py b/app/modules/campaign/endpoints_campaign.py index a638310927..b24e2ff481 100644 --- a/app/modules/campaign/endpoints_campaign.py +++ b/app/modules/campaign/endpoints_campaign.py @@ -6,16 +6,16 @@ import aiofiles from fastapi import Depends, File, HTTPException, UploadFile from fastapi.responses import FileResponse -from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession -from app.core.groups.groups_type import GroupType +from app.core.groups.groups_type import AccountType +from app.core.permissions import cruds_permissions, schemas_permissions +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import cruds_users, models_users from app.dependencies import ( get_db, get_request_id, - is_user_a_member, - is_user_in, + is_user_allowed_to, ) from app.modules.campaign import ( cruds_campaign, @@ -29,15 +29,23 @@ from app.types.module import Module from app.utils.tools import ( get_file_from_data, - is_user_member_of_any_group, + has_user_permission, save_file_as_data, ) + +class CampaignPermissions(ModulePermissions): + access_campaign = "access_campaign" + manage_campaign = "manage_campaign" + vote = "vote" + + module = Module( root="vote", tag="Campaign", - default_allowed_groups_ids=[GroupType.AE], + default_allowed_account_types=[AccountType.student], factory=CampaignFactory(), + permissions=CampaignPermissions, ) hyperion_error_logger = logging.getLogger("hyperion.error") @@ -50,22 +58,17 @@ ) async def get_sections( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to( + [CampaignPermissions.vote, CampaignPermissions.manage_campaign], + ), + ), ): """ Return sections in the database as a list of `schemas_campaign.SectionBase` - **The user must be a member of a group authorized to vote (voters) or a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to vote or to manage the campaign to use this endpoint** """ - voters = await cruds_campaign.get_voters(db) - voters_groups = [voter.group_id for voter in voters] - voters_groups.append(GroupType.CAA) - if not is_user_member_of_any_group(user, voters_groups): - raise HTTPException( - status_code=403, - detail="Access forbidden : you are not a poll member", - ) - return await cruds_campaign.get_sections(db) @@ -77,14 +80,16 @@ async def get_sections( async def add_section( section: schemas_campaign.SectionBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.manage_campaign]), + ), ): """ Add a section. This endpoint can only be used in 'waiting' status. - **The user must be a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to manage the campaign to use this endpoint** """ status = await cruds_campaign.get_status(db=db) if status != StatusType.waiting: @@ -110,14 +115,16 @@ async def add_section( async def delete_section( section_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.manage_campaign]), + ), ): """ Delete a section. This endpoint can only be used in 'waiting' status. - **The user must be a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to manage the campaign to use this endpoint** """ status = await cruds_campaign.get_status(db=db) @@ -137,21 +144,17 @@ async def delete_section( ) async def get_lists( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to( + [CampaignPermissions.vote, CampaignPermissions.manage_campaign], + ), + ), ): """ Return campaign lists registered for the vote. - **The user must be a member of a group authorized to vote (voters) or a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to vote or to manage the campaign to use this endpoint** """ - voters = await cruds_campaign.get_voters(db) - voters_groups = [voter.group_id for voter in voters] - voters_groups.append(GroupType.CAA) - if not is_user_member_of_any_group(user, voters_groups): - raise HTTPException( - status_code=403, - detail="Access forbidden : you are not a poll member", - ) return await cruds_campaign.get_lists(db=db) @@ -164,14 +167,16 @@ async def get_lists( async def add_list( campaign_list: schemas_campaign.ListBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.manage_campaign]), + ), ): """ Add a campaign list to a section. This endpoint can only be used in 'waiting' status. - **The user must be a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to manage the campaign to use this endpoint** """ status = await cruds_campaign.get_status(db=db) if status != StatusType.waiting: @@ -235,14 +240,16 @@ async def add_list( async def delete_list( list_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.manage_campaign]), + ), ): """ Delete the campaign list with the given id. This endpoint can only be used in 'waiting' status. - **The user must be a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to manage the campaign to use this endpoint** """ status = await cruds_campaign.get_status(db=db) @@ -262,14 +269,16 @@ async def delete_list( async def delete_lists_by_type( list_type: ListType | None = None, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.manage_campaign]), + ), ): """ Delete the all lists by type. This endpoint can only be used in 'waiting' status. - **The user must be a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to manage the campaign to use this endpoint** """ status = await cruds_campaign.get_status(db=db) @@ -294,14 +303,16 @@ async def update_list( list_id: str, campaign_list: schemas_campaign.ListEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.manage_campaign]), + ), ): """ Update the campaign list with the given id. This endpoint can only be used in 'waiting' status. - **The user must be a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to manage the campaign to use this endpoint** """ status = await cruds_campaign.get_status(db=db) if status != StatusType.waiting: @@ -332,94 +343,122 @@ async def update_list( @module.router.get( "/campaign/voters", - response_model=list[schemas_campaign.VoterGroup], + response_model=schemas_permissions.CorePermissions, status_code=200, ) async def get_voters( - user: models_users.CoreUser = Depends(is_user_a_member), db: AsyncSession = Depends(get_db), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.access_campaign]), + ), ): """ - Return the voters (groups allowed to vote) for the current campaign. + Return the list of voters. + + **The user must be a member of a group authorized to manage the campaign to use this endpoint** """ - return await cruds_campaign.get_voters(db=db) + return await cruds_permissions.get_permissions_by_permission_name( + db=db, + permission_name=CampaignPermissions.vote, + ) @module.router.post( - "/campaign/voters", - response_model=schemas_campaign.VoterGroup, - status_code=201, + "/campaign/voters/{group_id}", + status_code=204, ) async def add_voter( - voter: schemas_campaign.VoterGroup, - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + group_id: str, db: AsyncSession = Depends(get_db), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.manage_campaign]), + ), ): """ - Add voters (groups allowed to vote) for this campaign + Add a voter. - **The user must be a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to manage the campaign to use this endpoint** """ - status = await cruds_campaign.get_status(db=db) - if status != StatusType.waiting: + permission = ( + await cruds_permissions.get_group_permission_by_group_id_and_permission_name( + db=db, + permission_name=CampaignPermissions.vote, + group_id=group_id, + ) + ) + + if permission is not None: raise HTTPException( status_code=400, - detail=f"VoterGroups can only be edited in waiting mode. The current status is {status}", + detail="This group is already a voter.", ) - - db_voter = models_campaign.VoterGroups(**voter.model_dump(exclude_none=True)) - try: - await cruds_campaign.add_voter(voter=db_voter, db=db) - except IntegrityError as error: - raise HTTPException(status_code=400, detail=str(error)) - return db_voter + await cruds_permissions.create_group_permission( + db=db, + permission=schemas_permissions.CoreGroupPermission( + permission_name=CampaignPermissions.vote, + group_id=group_id, + ), + ) @module.router.delete( - "/campaign/voters/{group_id}", + "/campaign/voters", status_code=204, ) -async def delete_voter_by_group_id( - group_id: str, +async def delete_voters( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.manage_campaign]), + ), ): """ - Remove a voter by its group id + Delete all voters. - **The user must be a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to manage the campaign to use this endpoint** """ - status = await cruds_campaign.get_status(db=db) - if status != StatusType.waiting: - raise HTTPException( - status_code=400, - detail=f"VoterGroups can only be edited in waiting mode. The current status is {status}", - ) - - await cruds_campaign.delete_voter_by_group_id(group_id=group_id, db=db) + await cruds_permissions.delete_permissions_by_permission_name( + db=db, + permission_name=CampaignPermissions.vote, + ) @module.router.delete( - "/campaign/voters", + "/campaign/voters/{group_id}", status_code=204, ) -async def delete_voters( +async def delete_voter( + group_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.manage_campaign]), + ), ): """ - Remove voters (groups allowed to vote) + Delete a voter. - **The user must be a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to manage the campaign to use this endpoint** """ - status = await cruds_campaign.get_status(db=db) - if status != StatusType.waiting: + permission = ( + await cruds_permissions.get_group_permission_by_group_id_and_permission_name( + db=db, + permission_name=CampaignPermissions.vote, + group_id=group_id, + ) + ) + + if permission is None: raise HTTPException( - status_code=400, - detail=f"VoterGroups can only be edited in waiting mode. The current status is {status}", + status_code=404, + detail="This group is not a voter.", ) - await cruds_campaign.delete_voters(db=db) + await cruds_permissions.delete_group_permission( + db=db, + permission=schemas_permissions.CoreGroupPermission( + permission_name=CampaignPermissions.vote, + group_id=group_id, + ), + ) @module.router.post( @@ -428,7 +467,9 @@ async def delete_voters( ) async def open_vote( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.manage_campaign]), + ), ): """ If the status is 'waiting', change it to 'voting' and create the blank lists. @@ -436,7 +477,7 @@ async def open_vote( > WARNING: this operation can not be reversed. > When the status is 'open', all users can vote and sections and lists can no longer be edited. - **The user must be a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to manage the campaign to use this endpoint** """ status = await cruds_campaign.get_status(db=db) if status != StatusType.waiting: @@ -465,7 +506,9 @@ async def open_vote( ) async def close_vote( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.manage_campaign]), + ), ): """ If the status is 'open', change it to 'closed'. @@ -473,7 +516,7 @@ async def close_vote( > WARNING: this operation can not be reversed. > When the status is 'closed', users are no longer able to vote. - **The user must be a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to manage the campaign to use this endpoint** """ status = await cruds_campaign.get_status(db=db) if status != StatusType.open: @@ -491,7 +534,9 @@ async def close_vote( ) async def count_voting( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.manage_campaign]), + ), ): """ If the status is 'closed', change it to 'counting'. @@ -499,7 +544,7 @@ async def count_voting( > WARNING: this operation can not be reversed. > When the status is 'counting', administrators can see the results of the vote. - **The user must be a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to manage the campaign to use this endpoint** """ status = await cruds_campaign.get_status(db=db) if status != StatusType.closed: @@ -517,7 +562,9 @@ async def count_voting( ) async def publish_vote( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.manage_campaign]), + ), ): """ If the status is 'counting', change it to 'published'. @@ -525,7 +572,7 @@ async def publish_vote( > WARNING: this operation can not be reversed. > When the status is 'published', everyone can see the results of the vote. - **The user must be a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to manage the campaign to use this endpoint** """ status = await cruds_campaign.get_status(db=db) if status != StatusType.counting: @@ -543,14 +590,16 @@ async def publish_vote( ) async def reset_vote( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.manage_campaign]), + ), ): """ Reset the vote. Can only be used if the current status is counting ou published. > WARNING: This will delete all votes then put the module to Waiting status. This will also delete blank lists. - **The user must be a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to manage the campaign to use this endpoint** """ status = await cruds_campaign.get_status(db=db) if status not in [StatusType.published, StatusType.counting]: @@ -585,22 +634,17 @@ async def reset_vote( async def vote( vote: schemas_campaign.VoteBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.vote]), + ), ): """ Add a vote for a given campaign list. An user can only vote for one list per section. - **The user must be a member of a group authorized to vote (voters) to use this endpoint** + **The user must be a member of a group authorized to vote to use this endpoint** """ - voters = await cruds_campaign.get_voters(db) - voters_groups = [voter.group_id for voter in voters] - if not is_user_member_of_any_group(user, voters_groups): - raise HTTPException( - status_code=403, - detail="Access forbidden : you are not a poll member", - ) status = await cruds_campaign.get_status(db=db) if status != StatusType.open: @@ -652,20 +696,15 @@ async def vote( ) async def get_sections_already_voted( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.vote]), + ), ): """ Return the list of id of sections an user has already voted for. - **The user must be a member of a group authorized to vote (voters) to use this endpoint** + **The user must be a member of a group authorized to vote to use this endpoint** """ - voters = await cruds_campaign.get_voters(db) - voters_groups = [voter.group_id for voter in voters] - if not is_user_member_of_any_group(user, voters_groups): - raise HTTPException( - status_code=403, - detail="Access forbidden : you are not a poll member", - ) status = await cruds_campaign.get_status(db=db) if status != StatusType.open: @@ -685,27 +724,23 @@ async def get_sections_already_voted( ) async def get_results( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to( + [CampaignPermissions.vote, CampaignPermissions.manage_campaign], + ), + ), ): """ Return the results of the vote. - **The user must be a member of a group authorized to vote (voters) or a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to vote or to manage the campaign to use this endpoint** """ - voters = await cruds_campaign.get_voters(db) - voters_groups = [voter.group_id for voter in voters] - voters_groups.append(GroupType.CAA) - if not is_user_member_of_any_group(user, voters_groups): - raise HTTPException( - status_code=403, - detail="Access forbidden : you are not a poll member", - ) status = await cruds_campaign.get_status(db=db) if ( status == StatusType.counting - and is_user_member_of_any_group(user, [GroupType.CAA]) + and await has_user_permission(user, CampaignPermissions.manage_campaign, db) ) or status == StatusType.published: votes = await cruds_campaign.get_votes(db=db) @@ -739,21 +774,17 @@ async def get_results( ) async def get_status_vote( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to( + [CampaignPermissions.vote, CampaignPermissions.manage_campaign], + ), + ), ): """ Get the current status of the vote. - **The user must be a member of a group authorized to vote (voters) or a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to vote or to manage the campaign to use this endpoint** """ - voters = await cruds_campaign.get_voters(db) - voters_groups = [voter.group_id for voter in voters] - voters_groups.append(GroupType.CAA) - if not is_user_member_of_any_group(user, voters_groups): - raise HTTPException( - status_code=403, - detail="Access forbidden : you are not a poll member", - ) status = await cruds_campaign.get_status(db=db) return schemas_campaign.VoteStatus(status=status) @@ -767,12 +798,14 @@ async def get_status_vote( async def get_stats_for_section( section_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.vote]), + ), ): """ Get stats about a given section. - **The user must be a member of the group CAA to use this endpoint** + **The user must be authorized to vote to use this endpoint** """ status = await cruds_campaign.get_status(db=db) if status != StatusType.open: @@ -792,14 +825,16 @@ async def get_stats_for_section( async def create_campaigns_logo( list_id: str, image: UploadFile = File(...), - user: models_users.CoreUser = Depends(is_user_in(GroupType.CAA)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CampaignPermissions.manage_campaign]), + ), request_id: str = Depends(get_request_id), db: AsyncSession = Depends(get_db), ): """ Upload a logo for a campaign list. - **The user must be a member of the group CAA to use this endpoint** + **The user must be authorized to manage the campaign to use this endpoint** """ status = await cruds_campaign.get_status(db=db) @@ -838,21 +873,17 @@ async def create_campaigns_logo( ) async def read_campaigns_logo( list_id: str, - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to( + [CampaignPermissions.vote, CampaignPermissions.manage_campaign], + ), + ), db: AsyncSession = Depends(get_db), ): """ Get the logo of a campaign list. - **The user must be a member of a group authorized to vote (voters) or a member of the group CAA to use this endpoint** + **The user must be a member of a group authorized to vote or to manage the campaign to use this endpoint** """ - voters = await cruds_campaign.get_voters(db) - voters_groups = [voter.group_id for voter in voters] - voters_groups.append(GroupType.CAA) - if not is_user_member_of_any_group(user, voters_groups): - raise HTTPException( - status_code=403, - detail="Access forbidden : you are not a poll member", - ) campaign_list = await cruds_campaign.get_list_by_id(db=db, list_id=list_id) if campaign_list is None: diff --git a/app/modules/campaign/models_campaign.py b/app/modules/campaign/models_campaign.py index 851f748746..013bed7cf0 100644 --- a/app/modules/campaign/models_campaign.py +++ b/app/modules/campaign/models_campaign.py @@ -59,16 +59,6 @@ def as_dict(self) -> dict[str, Any]: return {c.name: getattr(self, c.name) for c in self.__table__.columns} -class VoterGroups(Base): - """ - VoterGroups are groups allowed to vote for a campaign - """ - - __tablename__ = "campaign_voter_groups" - - group_id: Mapped[str] = mapped_column(primary_key=True) - - class Votes(Base): __tablename__ = "campaign_votes" diff --git a/app/modules/cdr/endpoints_cdr.py b/app/modules/cdr/endpoints_cdr.py index 66cb82c025..d4af676047 100644 --- a/app/modules/cdr/endpoints_cdr.py +++ b/app/modules/cdr/endpoints_cdr.py @@ -16,10 +16,11 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.core.groups import cruds_groups, schemas_groups -from app.core.groups.groups_type import GroupType +from app.core.groups.groups_type import AccountType from app.core.memberships import cruds_memberships, schemas_memberships from app.core.payment.payment_tool import PaymentTool from app.core.payment.types_payment import HelloAssoConfigName +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import cruds_users, models_users, schemas_users from app.core.users.cruds_users import get_user_by_id, get_users from app.core.utils.config import Settings @@ -30,9 +31,7 @@ get_settings, get_unsafe_db, get_websocket_connection_manager, - is_user, - is_user_a_member, - is_user_in, + is_user_allowed_to, ) from app.modules.cdr import coredata_cdr, cruds_cdr, models_cdr, schemas_cdr from app.modules.cdr.dependencies_cdr import get_current_cdr_year @@ -58,16 +57,24 @@ from app.utils.tools import ( create_and_send_email_migration, get_core_data, + has_user_permission, is_user_member_of_any_group, set_core_data, ) + +class CdrPermissions(ModulePermissions): + access_cdr = "access_cdr" + manage_cdr = "manage_cdr" + + module = Module( root="cdr", tag="Cdr", payment_callback=validate_payment, - default_allowed_groups_ids=[GroupType.admin_cdr], + default_allowed_account_types=list(AccountType), factory=None, + permissions=CdrPermissions, ) hyperion_error_logger = logging.getLogger("hyperion.error") @@ -80,7 +87,9 @@ ) async def get_cdr_users( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Get all users. @@ -88,7 +97,7 @@ async def get_cdr_users( **User must be part of a seller group to use this endpoint** """ if not ( - is_user_member_of_any_group(user, [GroupType.admin_cdr]) + await has_user_permission(user, CdrPermissions.manage_cdr, db) or await cruds_cdr.get_sellers_by_group_ids( db=db, group_ids=[g.id for g in user.groups], @@ -114,7 +123,9 @@ async def get_cdr_users( ) async def get_cdr_users_pending_validation( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Get all users that have non-validated purchases. @@ -122,7 +133,7 @@ async def get_cdr_users_pending_validation( **User must be part of a seller group to use this endpoint** """ if not ( - is_user_member_of_any_group(user, [GroupType.admin_cdr]) + await has_user_permission(user, CdrPermissions.manage_cdr, db) or await cruds_cdr.get_sellers_by_group_ids( db=db, group_ids=[g.id for g in user.groups], @@ -172,7 +183,9 @@ async def get_cdr_users_pending_validation( async def get_cdr_user( user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Get a user. @@ -181,7 +194,7 @@ async def get_cdr_user( """ if user.id != user_id: if not ( - is_user_member_of_any_group(user, [GroupType.admin_cdr]) + await has_user_permission(user, CdrPermissions.manage_cdr, db) or await cruds_cdr.get_sellers_by_group_ids( db=db, group_ids=[g.id for g in user.groups], @@ -230,7 +243,7 @@ async def update_cdr_user( user_update: schemas_cdr.CdrUserUpdate, db: AsyncSession = Depends(get_db), seller_user: models_users.CoreUser = Depends( - is_user_in(GroupType.admin_cdr), + is_user_allowed_to([CdrPermissions.manage_cdr]), ), ws_manager: WebsocketConnectionManager = Depends(get_websocket_connection_manager), mail_templates: calypsso.MailTemplates = Depends(get_mail_templates), @@ -353,7 +366,9 @@ async def update_cdr_user( ) async def get_sellers( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin_cdr)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.manage_cdr]), + ), ): """ Get all sellers. @@ -370,7 +385,9 @@ async def get_sellers( ) async def get_sellers_by_user_id( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Get sellers user is part of the group. If user is adminCDR, returns all sellers. @@ -378,10 +395,7 @@ async def get_sellers_by_user_id( **User must be authenticated to use this endpoint** """ - if is_user_member_of_any_group( - user=user, - allowed_groups=[GroupType.admin_cdr], - ): + if await has_user_permission(user, CdrPermissions.manage_cdr, db): return await cruds_cdr.get_sellers(db) return await cruds_cdr.get_sellers_by_group_ids( db, @@ -396,7 +410,9 @@ async def get_sellers_by_user_id( ) async def get_online_sellers( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), ): """ @@ -527,7 +543,10 @@ async def generate_and_send_results( async def send_seller_results( seller_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), + # settings: Settings = Depends(get_settings), ): """ Get a seller's results. @@ -556,7 +575,9 @@ async def send_seller_results( ) async def get_all_available_online_products( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), ): """ @@ -577,7 +598,9 @@ async def get_all_available_online_products( ) async def get_all_products( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), ): """ @@ -589,7 +612,7 @@ async def get_all_products( db, [x.id for x in user.groups], ) - if not (sellers or is_user_member_of_any_group(user, [GroupType.admin_cdr])): + if not (sellers or await has_user_permission(user, CdrPermissions.manage_cdr, db)): raise HTTPException( status_code=403, detail="You must be a seller to get all documents.", @@ -608,7 +631,9 @@ async def get_all_products( async def create_seller( seller: schemas_cdr.SellerBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin_cdr)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.manage_cdr]), + ), ): """ Create a seller. @@ -643,7 +668,9 @@ async def update_seller( seller_id: UUID, seller: schemas_cdr.SellerEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin_cdr)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.manage_cdr]), + ), ): """ Update a seller. @@ -672,8 +699,10 @@ async def update_seller( async def delete_seller( seller_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin_cdr)), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.manage_cdr]), + ), ): """ Delete a seller. @@ -704,7 +733,9 @@ async def delete_seller( async def get_products_by_seller_id( seller_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), ): """ @@ -728,7 +759,9 @@ async def get_products_by_seller_id( async def get_available_online_products( seller_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), ): """ @@ -752,7 +785,9 @@ async def create_product( seller_id: UUID, product: schemas_cdr.ProductBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), ): """ @@ -830,7 +865,9 @@ async def update_product( product_id: UUID, product: schemas_cdr.ProductEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Edit a product. @@ -901,7 +938,9 @@ async def delete_product( seller_id: UUID, product_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Delete a product. @@ -946,7 +985,9 @@ async def create_product_variant( product_id: UUID, product_variant: schemas_cdr.ProductVariantBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), ): """ @@ -1034,7 +1075,9 @@ async def update_product_variant( variant_id: UUID, product_variant: schemas_cdr.ProductVariantEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Edit a product variant. @@ -1123,7 +1166,9 @@ async def delete_product_variant( product_id: UUID, variant_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Delete a product variant. @@ -1166,7 +1211,9 @@ async def delete_product_variant( async def get_seller_documents( seller_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Get a seller's documents. @@ -1188,7 +1235,9 @@ async def get_seller_documents( ) async def get_all_sellers_documents( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Get a seller's documents. @@ -1199,7 +1248,7 @@ async def get_all_sellers_documents( db, [x.id for x in user.groups], ) - if not (sellers or is_user_member_of_any_group(user, [GroupType.admin_cdr])): + if not (sellers or await has_user_permission(user, CdrPermissions.manage_cdr, db)): raise HTTPException( status_code=403, detail="You must be a seller to get all documents.", @@ -1217,7 +1266,9 @@ async def create_document( seller_id: UUID, document: schemas_cdr.DocumentBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Create a document. @@ -1251,7 +1302,9 @@ async def delete_document( seller_id: UUID, document_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Delete a document. @@ -1283,7 +1336,9 @@ async def delete_document( async def get_purchases_by_user_id( user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), ): """ @@ -1292,7 +1347,8 @@ async def get_purchases_by_user_id( **User must get his own purchases or be CDR Admin to use this endpoint** """ if not ( - user_id == user.id or is_user_member_of_any_group(user, [GroupType.admin_cdr]) + user_id == user.id + or await has_user_permission(user, CdrPermissions.manage_cdr, db) ): raise HTTPException( status_code=403, @@ -1364,7 +1420,9 @@ async def get_purchases_by_user_id( ) async def get_my_purchases( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), ): return await get_purchases_by_user_id(user.id, db, user, cdr_year) @@ -1377,7 +1435,9 @@ async def get_my_purchases( ) async def get_all_my_purchases( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): purchases = await cruds_cdr.get_all_purchases_by_user_id( db=db, @@ -1446,7 +1506,9 @@ async def get_purchases_by_user_id_by_seller_id( seller_id: UUID, user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), ): """ @@ -1527,7 +1589,9 @@ async def create_purchase( product_variant_id: UUID, purchase: schemas_cdr.PurchaseBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), ): """ @@ -1612,7 +1676,9 @@ async def create_purchase( async def create_purchase_batch( batch: schemas_cdr.BatchPurchase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), ): """ @@ -1768,7 +1834,9 @@ async def mark_purchase_as_validated( product_variant_id: UUID, validated: bool, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin_cdr)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.manage_cdr]), + ), ): """ Validate a purchase. @@ -1909,7 +1977,9 @@ async def mark_purchase_as_validated( async def validate_purchase_batch( batch: schemas_cdr.BatchValidation, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin_cdr)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.manage_cdr]), + ), ): for email in batch.user_emails: user_db = await cruds_users.get_user_by_email(db=db, email=email) @@ -1940,7 +2010,9 @@ async def delete_purchase( user_id: str, product_variant_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), ): """ @@ -2055,7 +2127,9 @@ async def delete_purchase( async def get_signatures_by_user_id( user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Get a user's signatures. @@ -2063,7 +2137,8 @@ async def get_signatures_by_user_id( **User must get his own signatures or be CDR Admin to use this endpoint** """ if not ( - user_id == user.id or is_user_member_of_any_group(user, [GroupType.admin_cdr]) + user_id == user.id + or await has_user_permission(user, CdrPermissions.manage_cdr, db) ): raise HTTPException( status_code=403, @@ -2081,7 +2156,9 @@ async def get_signatures_by_user_id_by_seller_id( seller_id: UUID, user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Get a user's signatures for a single seller. @@ -2108,7 +2185,9 @@ async def create_signature( document_id: UUID, signature: schemas_cdr.SignatureBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Create a signature. @@ -2133,13 +2212,13 @@ async def create_signature( sellers = await cruds_cdr.get_sellers(db) sellers_groups = [str(seller.group_id) for seller in sellers] - sellers_groups.append(GroupType.admin_cdr) if not ( ( user_id == user.id and signature.signature_type == DocumentSignatureType.numeric ) or is_user_member_of_any_group(user=user, allowed_groups=sellers_groups) + or await has_user_permission(user, CdrPermissions.manage_cdr, db) ): raise HTTPException( status_code=403, @@ -2173,7 +2252,9 @@ async def delete_signature( user_id: str, document_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin_cdr)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.manage_cdr]), + ), ): """ Delete a signature. @@ -2205,7 +2286,9 @@ async def delete_signature( ) async def get_curriculums( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): """ Get all curriculums. @@ -2223,7 +2306,9 @@ async def get_curriculums( async def create_curriculum( curriculum: schemas_cdr.CurriculumBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin_cdr)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.manage_cdr]), + ), ): """ Create a curriculum. @@ -2253,7 +2338,9 @@ async def create_curriculum( async def delete_curriculum( curriculum_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin_cdr)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.manage_cdr]), + ), ): """ Delete a curriculum. @@ -2284,7 +2371,9 @@ async def create_curriculum_membership( user_id: str, curriculum_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ws_manager: WebsocketConnectionManager = Depends(get_websocket_connection_manager), ): """ @@ -2293,7 +2382,8 @@ async def create_curriculum_membership( **User must add a curriculum to themself or be CDR Admin to use this endpoint** """ if not ( - user_id == user.id or is_user_member_of_any_group(user, [GroupType.admin_cdr]) + user_id == user.id + or await has_user_permission(user, CdrPermissions.manage_cdr, db) ): raise HTTPException( status_code=403, @@ -2384,7 +2474,9 @@ async def update_curriculum_membership( user_id: str, curriculum_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ws_manager: WebsocketConnectionManager = Depends(get_websocket_connection_manager), ): """ @@ -2393,7 +2485,8 @@ async def update_curriculum_membership( **User must add a curriculum to themself or be CDR Admin to use this endpoint** """ if not ( - user_id == user.id or is_user_member_of_any_group(user, [GroupType.admin_cdr]) + user_id == user.id + or await has_user_permission(user, CdrPermissions.manage_cdr, db) ): raise HTTPException( status_code=403, @@ -2461,7 +2554,9 @@ async def delete_curriculum_membership( user_id: str, curriculum_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ws_manager: WebsocketConnectionManager = Depends(get_websocket_connection_manager), ): """ @@ -2479,7 +2574,8 @@ async def delete_curriculum_membership( detail="Invalid curriculum_id", ) if not ( - user_id == user.id or is_user_member_of_any_group(user, [GroupType.admin_cdr]) + user_id == user.id + or await has_user_permission(user, CdrPermissions.manage_cdr, db) ): raise HTTPException( status_code=403, @@ -2535,7 +2631,9 @@ async def delete_curriculum_membership( async def get_payments_by_user_id( user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), ): """ @@ -2544,7 +2642,8 @@ async def get_payments_by_user_id( **User must get his own payments or be CDR Admin to use this endpoint** """ if not ( - user_id == user.id or is_user_member_of_any_group(user, [GroupType.admin_cdr]) + user_id == user.id + or await has_user_permission(user, CdrPermissions.manage_cdr, db) ): raise HTTPException( status_code=403, @@ -2566,8 +2665,10 @@ async def create_payment( user_id: str, payment: schemas_cdr.PaymentBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin_cdr)), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.manage_cdr]), + ), ): """ Create a payment. @@ -2610,7 +2711,9 @@ async def delete_payment( user_id: str, payment_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin_cdr)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.manage_cdr]), + ), ): """ Remove a payment. @@ -2654,7 +2757,9 @@ async def delete_payment( ) async def get_payment_url( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), settings: Settings = Depends(get_settings), payment_tool: PaymentTool = Depends(get_payment_tool(HelloAssoConfigName.CDR)), cdr_year: coredata_cdr.CdrYear = Depends(get_current_cdr_year), @@ -2748,7 +2853,9 @@ async def get_cdr_year( async def update_cdr_year( cdr_year: coredata_cdr.CdrYear, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin_cdr)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.manage_cdr]), + ), ): await set_core_data(cdr_year, db) @@ -2771,7 +2878,9 @@ async def get_status( async def update_status( status: coredata_cdr.Status, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin_cdr)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.manage_cdr]), + ), ): current_status = await get_core_data(coredata_cdr.Status, db) match status.status: @@ -2809,7 +2918,9 @@ async def update_status( ) async def get_my_tickets( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): return await cruds_cdr.get_tickets_of_user(db=db, user_id=user.id) @@ -2822,10 +2933,13 @@ async def get_my_tickets( async def get_tickets_of_user( user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): if not ( - is_user_member_of_any_group(user, [GroupType.admin_cdr]) or user_id == user.id + await has_user_permission(user, CdrPermissions.manage_cdr, db) + or user_id == user.id ): raise HTTPException( status_code=403, @@ -2842,7 +2956,9 @@ async def get_tickets_of_user( async def get_ticket_secret( ticket_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): ticket = await cruds_cdr.get_ticket(db=db, ticket_id=ticket_id) if not ticket: @@ -2869,7 +2985,9 @@ async def get_ticket_by_secret( generator_id: UUID, secret: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): product = await check_request_consistency( db=db, @@ -2921,7 +3039,9 @@ async def scan_ticket( secret: UUID, ticket_data: schemas_cdr.TicketScan, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): product = await check_request_consistency( db=db, @@ -2991,7 +3111,9 @@ async def get_users_by_tag( generator_id: UUID, tag: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): product = await check_request_consistency( db=db, @@ -3037,7 +3159,9 @@ async def get_tags_of_ticket( product_id: UUID, generator_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): product = await check_request_consistency( db=db, @@ -3083,7 +3207,9 @@ async def generate_ticket_for_product( product_id: UUID, ticket_data: schemas_cdr.GenerateTicketBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): await is_user_in_a_seller_group(seller_id=seller_id, user=user, db=db) product = await check_request_consistency( @@ -3138,7 +3264,9 @@ async def delete_ticket_generator_for_product( product_id: UUID, ticket_generator_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): await is_user_in_a_seller_group(seller_id=seller_id, user=user, db=db) product = await check_request_consistency( @@ -3179,7 +3307,9 @@ async def get_custom_data_fields( seller_id: UUID, product_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): await is_user_in_a_seller_group( seller_id, @@ -3200,7 +3330,9 @@ async def create_custom_data_field( product_id: UUID, custom_data_field: schemas_cdr.CustomDataFieldBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): await is_user_in_a_seller_group( seller_id, @@ -3230,7 +3362,9 @@ async def update_custom_data_field( field_id: UUID, custom_data_field: schemas_cdr.CustomDataFieldBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): await is_user_in_a_seller_group( seller_id, @@ -3271,7 +3405,9 @@ async def delete_customdata_field( product_id: UUID, field_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): await is_user_in_a_seller_group( seller_id, @@ -3308,7 +3444,9 @@ async def get_customdata( user_id: str, field_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): await is_user_in_a_seller_group( seller_id, @@ -3338,7 +3476,9 @@ async def create_custom_data( field_id: UUID, custom_data: schemas_cdr.CustomDataBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): await check_request_consistency(db=db, seller_id=seller_id, product_id=product_id) db_field = await cruds_cdr.get_customdata_field(db=db, field_id=field_id) @@ -3348,7 +3488,7 @@ async def create_custom_data( detail="Field not found.", ) if not ( - is_user_member_of_any_group(user, [GroupType.admin_cdr]) + await has_user_permission(user, CdrPermissions.manage_cdr, db) or seller_id in [ s.id @@ -3391,7 +3531,9 @@ async def update_custom_data( field_id: UUID, custom_data: schemas_cdr.CustomDataBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): await check_request_consistency(db=db, seller_id=seller_id, product_id=product_id) db_data = await cruds_cdr.get_customdata(db=db, field_id=field_id, user_id=user_id) @@ -3401,7 +3543,7 @@ async def update_custom_data( detail="Field Data not found.", ) if not ( - is_user_member_of_any_group(user, [GroupType.admin_cdr]) + await has_user_permission(user, CdrPermissions.manage_cdr, db) or seller_id in [ s.id @@ -3439,7 +3581,9 @@ async def delete_customdata( user_id: str, field_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CdrPermissions.access_cdr]), + ), ): await check_request_consistency(db=db, seller_id=seller_id, product_id=product_id) db_data = await cruds_cdr.get_customdata(db=db, field_id=field_id, user_id=user_id) @@ -3449,7 +3593,7 @@ async def delete_customdata( detail="Field Data not found.", ) if not ( - is_user_member_of_any_group(user, [GroupType.admin_cdr]) + await has_user_permission(user, CdrPermissions.manage_cdr, db) or seller_id in [ s.id diff --git a/app/modules/cdr/utils_cdr.py b/app/modules/cdr/utils_cdr.py index 5535d438eb..1fb940ba97 100644 --- a/app/modules/cdr/utils_cdr.py +++ b/app/modules/cdr/utils_cdr.py @@ -9,19 +9,30 @@ ) from sqlalchemy.ext.asyncio import AsyncSession -from app.core.groups.groups_type import GroupType from app.core.payment import schemas_payment +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import models_users from app.dependencies import ( hyperion_access_logger, ) from app.modules.cdr import coredata_cdr, cruds_cdr, models_cdr -from app.modules.cdr.types_cdr import CdrLogActionType, PaymentType -from app.utils.tools import get_core_data, is_user_member_of_any_group +from app.modules.cdr.types_cdr import ( + CdrLogActionType, + PaymentType, +) +from app.utils.tools import ( + get_core_data, + has_user_permission, + is_user_member_of_any_group, +) hyperion_error_logger = logging.getLogger("hyperion.error") +class CdrPermissions(ModulePermissions): + manage_cdr = "manage_cdr" + + async def validate_payment( checkout_payment: schemas_payment.CheckoutPayment, db: AsyncSession, @@ -80,7 +91,11 @@ async def is_user_in_a_seller_group( if is_user_member_of_any_group( user=user, - allowed_groups=[str(seller.group_id), GroupType.admin_cdr], + allowed_groups=[str(seller.group_id)], + ) or await has_user_permission( + user, + CdrPermissions.manage_cdr, + db, ): return user diff --git a/app/modules/centralassociation/endpoints_centralassociation.py b/app/modules/centralassociation/endpoints_centralassociation.py index c3471032ad..3cdd33e147 100644 --- a/app/modules/centralassociation/endpoints_centralassociation.py +++ b/app/modules/centralassociation/endpoints_centralassociation.py @@ -1,9 +1,16 @@ from app.core.groups.groups_type import AccountType +from app.core.permissions.type_permissions import ModulePermissions from app.types.module import Module + +class CentralassociationPermissions(ModulePermissions): + access_centralassociation = "access_centralassociation" + + module = Module( root="centralassociation", tag="Centralassociation", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=None, + permissions=CentralassociationPermissions, ) diff --git a/app/modules/centralisation/endpoints_centralisation.py b/app/modules/centralisation/endpoints_centralisation.py index 62338ca6c4..c9ad0e5e78 100644 --- a/app/modules/centralisation/endpoints_centralisation.py +++ b/app/modules/centralisation/endpoints_centralisation.py @@ -1,9 +1,16 @@ from app.core.groups.groups_type import AccountType +from app.core.permissions.type_permissions import ModulePermissions from app.types.module import Module + +class CentralisationPermissions(ModulePermissions): + access_centralisation = "access_centralisation" + + module = Module( root="centralisation", tag="Centralisation", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=None, + permissions=CentralisationPermissions, ) diff --git a/app/modules/cinema/endpoints_cinema.py b/app/modules/cinema/endpoints_cinema.py index 94dccf89c3..aabae310be 100644 --- a/app/modules/cinema/endpoints_cinema.py +++ b/app/modules/cinema/endpoints_cinema.py @@ -7,8 +7,9 @@ from fastapi.responses import FileResponse from sqlalchemy.ext.asyncio import AsyncSession -from app.core.groups.groups_type import AccountType, GroupType +from app.core.groups.groups_type import AccountType from app.core.notification.schemas_notification import Message, Topic +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import models_users from app.core.utils.config import Settings from app.dependencies import ( @@ -16,8 +17,7 @@ get_notification_tool, get_scheduler, get_settings, - is_user_a_member, - is_user_in, + is_user_allowed_to, ) from app.modules.cinema import cruds_cinema, schemas_cinema from app.modules.cinema.factory_cinema import CinemaFactory @@ -42,12 +42,20 @@ restrict_to_group_id=None, restrict_to_members=True, ) + + +class CinemaPermissions(ModulePermissions): + access_cinema = "access_cinema" + manage_sessions = "manage_sessions" + + module = Module( root=root, tag="Cinema", default_allowed_account_types=[AccountType.student, AccountType.staff], registred_topics=[cinema_topic], factory=CinemaFactory(), + permissions=CinemaPermissions, ) hyperion_error_logger = logging.getLogger("hyperion.error") @@ -60,7 +68,9 @@ async def get_movie( themoviedb_id: str, settings: Settings = Depends(get_settings), - user: models_users.CoreUser = Depends(is_user_in(GroupType.cinema)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CinemaPermissions.manage_sessions]), + ), ): """ Makes a HTTP request to The Movie Database (TMDB) @@ -117,7 +127,9 @@ async def get_movie( ) async def get_sessions( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CinemaPermissions.access_cinema]), + ), ): return await cruds_cinema.get_sessions(db=db) @@ -130,7 +142,9 @@ async def get_sessions( async def create_session( session: schemas_cinema.CineSessionBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.cinema)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CinemaPermissions.manage_sessions]), + ), notification_tool: NotificationTool = Depends(get_notification_tool), scheduler: Scheduler = Depends(get_scheduler), ): @@ -178,7 +192,9 @@ async def update_session( session_id: str, session_update: schemas_cinema.CineSessionUpdate, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.cinema)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CinemaPermissions.manage_sessions]), + ), ): await cruds_cinema.update_session( session_id=session_id, @@ -191,7 +207,9 @@ async def update_session( async def delete_session( session_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.cinema)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CinemaPermissions.manage_sessions]), + ), ): await cruds_cinema.delete_session(session_id=session_id, db=db) @@ -204,7 +222,9 @@ async def delete_session( async def create_campaigns_logo( session_id: str, image: UploadFile = File(...), - user: models_users.CoreUser = Depends(is_user_in(GroupType.cinema)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CinemaPermissions.manage_sessions]), + ), db: AsyncSession = Depends(get_db), ): session = await cruds_cinema.get_session_by_id(db=db, session_id=session_id) @@ -236,7 +256,9 @@ async def create_campaigns_logo( ) async def read_session_poster( session_id: str, - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([CinemaPermissions.access_cinema]), + ), db: AsyncSession = Depends(get_db), ): session = await cruds_cinema.get_session_by_id(db=db, session_id=session_id) diff --git a/app/modules/flappybird/endpoints_flappybird.py b/app/modules/flappybird/endpoints_flappybird.py index 6d7cede730..d1e68b2ade 100644 --- a/app/modules/flappybird/endpoints_flappybird.py +++ b/app/modules/flappybird/endpoints_flappybird.py @@ -4,9 +4,10 @@ from fastapi import Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession -from app.core.groups.groups_type import AccountType, GroupType +from app.core.groups.groups_type import AccountType +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import models_users -from app.dependencies import get_db, is_user_a_member, is_user_in +from app.dependencies import get_db, is_user_allowed_to from app.modules.flappybird import ( cruds_flappybird, models_flappybird, @@ -14,11 +15,18 @@ ) from app.types.module import Module + +class FlappyBirdPermissions(ModulePermissions): + access_flappybird = "access_flappybird" + manage_flappybird = "manage_flappybird" + + module = Module( root="flappybird", tag="Flappy Bird", default_allowed_account_types=[AccountType.student], factory=None, + permissions=FlappyBirdPermissions, ) @@ -29,7 +37,9 @@ ) async def get_flappybird_score( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([FlappyBirdPermissions.access_flappybird]), + ), ): """Return the leaderboard""" return await cruds_flappybird.get_flappybird_score_leaderboard(db=db) @@ -42,7 +52,9 @@ async def get_flappybird_score( ) async def get_current_user_flappybird_personal_best( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([FlappyBirdPermissions.access_flappybird]), + ), ): user_personal_best_table = ( await cruds_flappybird.get_flappybird_personal_best_by_user_id( @@ -81,7 +93,9 @@ async def get_current_user_flappybird_personal_best( ) async def create_flappybird_score( flappybird_score: schemas_flappybird.FlappyBirdScoreBase, - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([FlappyBirdPermissions.access_flappybird]), + ), db: AsyncSession = Depends(get_db), ): # Currently, flappybird_score is a schema instance @@ -124,6 +138,8 @@ async def create_flappybird_score( async def remove_flappybird_score( targeted_user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([FlappyBirdPermissions.manage_flappybird]), + ), ): await cruds_flappybird.delete_flappybird_best_score(db=db, user_id=targeted_user_id) diff --git a/app/modules/home/endpoints_home.py b/app/modules/home/endpoints_home.py index ed46d295cb..69ad2e6a63 100644 --- a/app/modules/home/endpoints_home.py +++ b/app/modules/home/endpoints_home.py @@ -1,9 +1,16 @@ from app.core.groups.groups_type import AccountType +from app.core.permissions.type_permissions import ModulePermissions from app.types.module import Module + +class HomePermissions(ModulePermissions): + access_home = "access_home" + + module = Module( root="home", tag="Home", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=None, + permissions=HomePermissions, ) diff --git a/app/modules/loan/endpoints_loan.py b/app/modules/loan/endpoints_loan.py index 9874bd4fb9..5fc8cf069b 100644 --- a/app/modules/loan/endpoints_loan.py +++ b/app/modules/loan/endpoints_loan.py @@ -6,15 +6,15 @@ from fastapi import Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession -from app.core.groups.groups_type import AccountType, GroupType +from app.core.groups.groups_type import AccountType from app.core.notification.schemas_notification import Message +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import models_users from app.dependencies import ( get_db, get_notification_tool, get_scheduler, - is_user_a_member, - is_user_in, + is_user_allowed_to, ) from app.modules.loan import cruds_loan, models_loan, schemas_loan from app.modules.loan.factory_loan import LoanFactory @@ -31,11 +31,17 @@ from collections.abc import Sequence +class LoanPermissions(ModulePermissions): + access_loan = "access_loan" + manage_loaners = "manage_loaners" + + module = Module( root="loan", tag="Loans", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=LoanFactory(), + permissions=LoanPermissions, ) @@ -49,7 +55,9 @@ ) async def read_loaners( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.manage_loaners]), + ), ): """ Get existing loaners. @@ -68,7 +76,9 @@ async def read_loaners( async def create_loaner( loaner: schemas_loan.LoanerBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.manage_loaners]), + ), ): """ Create a new loaner. @@ -104,7 +114,9 @@ async def create_loaner( async def delete_loaner( loaner_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.manage_loaners]), + ), ): """ Delete a loaner. All items and loans associated with the loaner will also be deleted from the database. @@ -140,7 +152,9 @@ async def update_loaner( loaner_id: str, loaner_update: schemas_loan.LoanerUpdate, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.manage_loaners]), + ), ): """ Update a loaner, the request should contain a JSON with the fields to change (not necessarily all fields) and their new value. @@ -164,7 +178,9 @@ async def get_loans_by_loaner( loaner_id: str, returned: bool | None = None, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.access_loan]), + ), ): """ Return all loans from a given group. @@ -227,7 +243,9 @@ async def get_loans_by_loaner( async def get_items_by_loaner( loaner_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.access_loan]), + ), ): """ Return all items of a loaner. @@ -271,7 +289,9 @@ async def create_items_for_loaner( loaner_id: str, item: schemas_loan.ItemBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.access_loan]), + ), ): """ Create a new item for a loaner. A given loaner can not have more than one item with the same `name`. @@ -335,7 +355,9 @@ async def update_items_for_loaner( item_id: str, item_update: schemas_loan.ItemUpdate, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.access_loan]), + ), ): """ Update a loaner's item. @@ -385,7 +407,9 @@ async def delete_loaner_item( loaner_id: str, item_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.access_loan]), + ), ): """ Delete a loaner's item. @@ -427,7 +451,9 @@ async def delete_loaner_item( async def get_current_user_loans( returned: bool | None = None, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.access_loan]), + ), ): """ Return all loans from the current user. @@ -471,7 +497,9 @@ async def get_current_user_loans( ) async def get_current_user_loaners( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.access_loan]), + ), ): """ Return all loaners the current user can manage. @@ -501,7 +529,9 @@ async def get_current_user_loaners( async def create_loan( loan_creation: schemas_loan.LoanCreation, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.access_loan]), + ), notification_tool: NotificationTool = Depends(get_notification_tool), scheduler: Scheduler = Depends(get_scheduler), ): @@ -655,7 +685,9 @@ async def update_loan( loan_id: str, loan_update: schemas_loan.LoanUpdate, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.access_loan]), + ), ): """ Update a loan and its items. @@ -778,7 +810,9 @@ async def update_loan( async def delete_loan( loan_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.access_loan]), + ), ): """ Delete a loan @@ -828,7 +862,9 @@ async def delete_loan( async def return_loan( loan_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.access_loan]), + ), scheduler: Scheduler = Depends(get_scheduler), notification_tool: NotificationTool = Depends(get_notification_tool), ): @@ -889,7 +925,9 @@ async def extend_loan( loan_id: str, loan_extend: schemas_loan.LoanExtend, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([LoanPermissions.access_loan]), + ), notification_tool: NotificationTool = Depends(get_notification_tool), scheduler: Scheduler = Depends(get_scheduler), ): diff --git a/app/modules/myeclpay/endpoints_myeclpay.py b/app/modules/myeclpay/endpoints_myeclpay.py index 0b031e976e..213cde1f2b 100644 --- a/app/modules/myeclpay/endpoints_myeclpay.py +++ b/app/modules/myeclpay/endpoints_myeclpay.py @@ -1,9 +1,16 @@ from app.core.groups.groups_type import AccountType +from app.core.permissions.type_permissions import ModulePermissions from app.types.module import Module + +class MyECLPayPermissions(ModulePermissions): + access_payment = "access_payment" + + module = Module( root="payment", tag="MyECLPay", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=None, + permissions=MyECLPayPermissions, ) diff --git a/app/modules/ph/endpoints_ph.py b/app/modules/ph/endpoints_ph.py index b23f87450f..a7590b8ebe 100644 --- a/app/modules/ph/endpoints_ph.py +++ b/app/modules/ph/endpoints_ph.py @@ -5,16 +5,16 @@ from fastapi.responses import FileResponse from sqlalchemy.ext.asyncio import AsyncSession -from app.core.groups.groups_type import AccountType, GroupType +from app.core.groups.groups_type import AccountType from app.core.notification.schemas_notification import Message, Topic +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import models_users from app.dependencies import ( get_db, get_notification_tool, get_request_id, get_scheduler, - is_user_a_member, - is_user_in, + is_user_allowed_to, ) from app.modules.ph import cruds_ph, models_ph, schemas_ph from app.types.content_type import ContentType @@ -37,12 +37,20 @@ restrict_to_group_id=None, restrict_to_members=True, ) + + +class PHPermissions(ModulePermissions): + access_ph = "access_ph" + manage_ph = "manage_ph" + + module = Module( root=root, tag="ph", default_allowed_account_types=[AccountType.student], registred_topics=[ph_topic], factory=None, + permissions=PHPermissions, ) @@ -54,7 +62,9 @@ async def get_paper_pdf( paper_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PHPermissions.access_ph]), + ), ): paper = await cruds_ph.get_paper_by_id(db=db, paper_id=paper_id) if paper is None: @@ -77,7 +87,9 @@ async def get_paper_pdf( ) async def get_papers( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PHPermissions.access_ph]), + ), ): """ Return all editions until now, sorted from the latest to the oldest @@ -95,7 +107,9 @@ async def get_papers( ) async def get_papers_admin( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.ph)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PHPermissions.manage_ph]), + ), ): """ Return all editions, sorted from the latest to the oldest @@ -113,7 +127,9 @@ async def get_papers_admin( async def create_paper( paper: schemas_ph.PaperBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.ph)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PHPermissions.manage_ph]), + ), notification_tool: NotificationTool = Depends(get_notification_tool), scheduler: Scheduler = Depends(get_scheduler), ): @@ -164,7 +180,9 @@ async def create_paper( async def create_paper_pdf_and_cover( paper_id: uuid.UUID, pdf: UploadFile = File(...), - user: models_users.CoreUser = Depends(is_user_in(GroupType.ph)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PHPermissions.manage_ph]), + ), request_id: str = Depends(get_request_id), db: AsyncSession = Depends(get_db), ): @@ -198,7 +216,9 @@ async def create_paper_pdf_and_cover( ) async def get_cover( paper_id: uuid.UUID, - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PHPermissions.access_ph]), + ), db: AsyncSession = Depends(get_db), ): paper = await cruds_ph.get_paper_by_id(db=db, paper_id=paper_id) @@ -222,7 +242,9 @@ async def get_cover( async def update_paper( paper_id: uuid.UUID, paper_update: schemas_ph.PaperUpdate, - user: models_users.CoreUser = Depends(is_user_in(GroupType.ph)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PHPermissions.manage_ph]), + ), db: AsyncSession = Depends(get_db), ): paper = await cruds_ph.get_paper_by_id(paper_id=paper_id, db=db) @@ -245,7 +267,9 @@ async def update_paper( ) async def delete_paper( paper_id: uuid.UUID, - user: models_users.CoreUser = Depends(is_user_in(GroupType.ph)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PHPermissions.manage_ph]), + ), db: AsyncSession = Depends(get_db), ): paper = await cruds_ph.get_paper_by_id(paper_id=paper_id, db=db) diff --git a/app/modules/phonebook/endpoints_phonebook.py b/app/modules/phonebook/endpoints_phonebook.py index f4406569ed..f13756831c 100644 --- a/app/modules/phonebook/endpoints_phonebook.py +++ b/app/modules/phonebook/endpoints_phonebook.py @@ -7,10 +7,11 @@ from app.core.groups import cruds_groups, models_groups from app.core.groups.groups_type import AccountType, GroupType +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import cruds_users, models_users from app.dependencies import ( get_db, - is_user_an_ecl_member, + is_user_allowed_to, is_user_in, ) from app.modules.phonebook import ( @@ -25,15 +26,22 @@ from app.types.module import Module from app.utils.tools import ( get_file_from_data, - is_user_member_of_any_group, + has_user_permission, save_file_as_data, ) + +class PhonebookPermissions(ModulePermissions): + access_phonebook = "access_phonebook" + manage_phonebook = "manage_phonebook" + + module = Module( root="phonebook", tag="Phonebook", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=PhonebookFactory(), + permissions=PhonebookPermissions, ) hyperion_error_logger = logging.getLogger("hyperion.error") @@ -46,7 +54,9 @@ ) async def get_all_associations( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PhonebookPermissions.access_phonebook]), + ), ): """ Return all associations from database as a list of AssociationComplete schemas @@ -73,7 +83,9 @@ async def get_all_associations( ) async def get_all_role_tags( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PhonebookPermissions.access_phonebook]), + ), ): """ Return all available role tags from RoleTags enum. @@ -88,7 +100,9 @@ async def get_all_role_tags( status_code=200, ) async def get_all_kinds( - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PhonebookPermissions.access_phonebook]), + ), ): """ Return all available kinds of from Kinds enum. @@ -105,23 +119,15 @@ async def get_all_kinds( async def create_association( association: schemas_phonebook.AssociationBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PhonebookPermissions.manage_phonebook]), + ), ): """ Create a new Association by giving an AssociationBase scheme **This endpoint is only usable by CAA, BDE** """ - - if not is_user_member_of_any_group( - user=user, - allowed_groups=[GroupType.CAA, GroupType.BDE], - ): - raise HTTPException( - status_code=403, - detail="You are not allowed to create association", - ) - association_id = str(uuid.uuid4()) association_model = models_phonebook.Association( id=association_id, @@ -153,7 +159,9 @@ async def create_association( async def update_association( association_id: str, association_edit: schemas_phonebook.AssociationEdit, - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PhonebookPermissions.access_phonebook]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -162,10 +170,7 @@ async def update_association( **This endpoint is only usable by CAA, BDE and association's president** """ if not ( - is_user_member_of_any_group( - user=user, - allowed_groups=[GroupType.CAA, GroupType.BDE], - ) + await has_user_permission(user, PhonebookPermissions.manage_phonebook, db) or await cruds_phonebook.is_user_president( association_id=association_id, user=user, @@ -215,7 +220,9 @@ async def update_association_groups( ) async def deactivate_association( association_id: str, - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PhonebookPermissions.manage_phonebook]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -223,14 +230,6 @@ async def deactivate_association( **This endpoint is only usable by CAA and BDE** """ - if not is_user_member_of_any_group( - user=user, - allowed_groups=[GroupType.CAA, GroupType.BDE], - ): - raise HTTPException( - status_code=403, - detail=f"You are not allowed to delete association {association_id}", - ) await cruds_phonebook.deactivate_association(association_id, db) @@ -241,7 +240,9 @@ async def deactivate_association( async def delete_association( association_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PhonebookPermissions.manage_phonebook]), + ), ): """ Delete an Association @@ -251,14 +252,6 @@ async def delete_association( **This endpoint is only usable by CAA and BDE** """ - if not is_user_member_of_any_group( - user=user, - allowed_groups=[GroupType.CAA, GroupType.BDE], - ): - raise HTTPException( - status_code=403, - detail=f"You are not allowed to delete association {association_id}", - ) association = await cruds_phonebook.get_association_by_id(association_id, db) if association is None: raise HTTPException(404, "Association does not exist.") @@ -280,7 +273,9 @@ async def delete_association( ) async def get_association_members( association_id: str, - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PhonebookPermissions.access_phonebook]), + ), db: AsyncSession = Depends(get_db), ): """Return the list of MemberComplete of an Association.""" @@ -316,7 +311,9 @@ async def get_association_members( async def get_association_members_by_mandate_year( association_id: str, mandate_year: int, - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PhonebookPermissions.access_phonebook]), + ), db: AsyncSession = Depends(get_db), ): """Return the list of MemberComplete of an Association with given mandate_year.""" @@ -355,7 +352,9 @@ async def get_association_members_by_mandate_year( async def get_member_details( user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PhonebookPermissions.access_phonebook]), + ), ): """Return MemberComplete for given user_id.""" @@ -379,7 +378,9 @@ async def get_member_details( ) async def create_membership( membership: schemas_phonebook.MembershipBase, - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PhonebookPermissions.access_phonebook]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -397,10 +398,7 @@ async def create_membership( ) if not ( - is_user_member_of_any_group( - user=user, - allowed_groups=[GroupType.CAA, GroupType.BDE], - ) + await has_user_permission(user, PhonebookPermissions.manage_phonebook, db) or await cruds_phonebook.is_user_president( association_id=membership.association_id, user=user, @@ -415,9 +413,10 @@ async def create_membership( if membership.role_tags is not None: if RoleTags.president.value in membership.role_tags.split( ";", - ) and not is_user_member_of_any_group( - user=user, - allowed_groups=[GroupType.CAA, GroupType.BDE], + ) and not await has_user_permission( + user, + PhonebookPermissions.manage_phonebook, + db, ): raise HTTPException( status_code=403, @@ -482,7 +481,9 @@ async def create_membership( async def update_membership( updated_membership: schemas_phonebook.MembershipEdit, membership_id: str, - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PhonebookPermissions.access_phonebook]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -502,10 +503,7 @@ async def update_membership( ) if not ( - is_user_member_of_any_group( - user=user, - allowed_groups=[GroupType.CAA, GroupType.BDE], - ) + await has_user_permission(user, PhonebookPermissions.manage_phonebook, db) or await cruds_phonebook.is_user_president( association_id=old_membership.association_id, user=user, @@ -520,9 +518,10 @@ async def update_membership( if updated_membership.role_tags is not None: if RoleTags.president.value in updated_membership.role_tags.split( ";", - ) and not is_user_member_of_any_group( - user=user, - allowed_groups=[GroupType.CAA, GroupType.BDE], + ) and not await has_user_permission( + user, + PhonebookPermissions.manage_phonebook, + db, ): raise HTTPException( status_code=403, @@ -548,7 +547,9 @@ async def update_membership( ) async def delete_membership( membership_id: str, - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PhonebookPermissions.access_phonebook]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -565,10 +566,7 @@ async def delete_membership( ) if not ( - is_user_member_of_any_group( - user=user, - allowed_groups=[GroupType.CAA, GroupType.BDE], - ) + await has_user_permission(user, PhonebookPermissions.manage_phonebook, db) or await cruds_phonebook.is_user_president( association_id=membership.association_id, user=user, @@ -598,7 +596,9 @@ async def delete_membership( async def create_association_logo( association_id: str, image: UploadFile = File(), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PhonebookPermissions.access_phonebook]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -606,9 +606,10 @@ async def create_association_logo( **The user must be a member of the group CAA or BDE to use this endpoint** """ - if not is_user_member_of_any_group( - user=user, - allowed_groups=[GroupType.CAA, GroupType.BDE], + if not await has_user_permission( + user, + PhonebookPermissions.manage_phonebook, + db, ) and not await cruds_phonebook.is_user_president( association_id=association_id, user=user, @@ -648,7 +649,9 @@ async def create_association_logo( async def read_association_logo( association_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_an_ecl_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([PhonebookPermissions.access_phonebook]), + ), ) -> FileResponse: """ Get the logo of an Association. diff --git a/app/modules/purchases/endpoints_purchases.py b/app/modules/purchases/endpoints_purchases.py index 2891eb86dd..fc38ea261b 100644 --- a/app/modules/purchases/endpoints_purchases.py +++ b/app/modules/purchases/endpoints_purchases.py @@ -1,9 +1,16 @@ from app.core.groups.groups_type import AccountType +from app.core.permissions.type_permissions import ModulePermissions from app.types.module import Module + +class PurchasesPermissions(ModulePermissions): + access_purchases = "access_purchases" + + module = Module( root="purchases", tag="Purchases", - default_allowed_account_types=[AccountType.student, AccountType.external], + default_allowed_account_types=list(AccountType), factory=None, + permissions=PurchasesPermissions, ) diff --git a/app/modules/raffle/endpoints_raffle.py b/app/modules/raffle/endpoints_raffle.py index 500f106ae1..abe5c287f2 100644 --- a/app/modules/raffle/endpoints_raffle.py +++ b/app/modules/raffle/endpoints_raffle.py @@ -7,15 +7,15 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.core.groups import cruds_groups -from app.core.groups.groups_type import AccountType, GroupType +from app.core.groups.groups_type import AccountType +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import cruds_users, models_users from app.core.users.endpoints_users import read_user from app.dependencies import ( get_db, get_redis_client, get_request_id, - is_user_a_member, - is_user_in, + is_user_allowed_to, ) from app.modules.raffle import cruds_raffle, models_raffle, schemas_raffle from app.modules.raffle.types_raffle import RaffleStatusType @@ -25,15 +25,24 @@ from app.utils.redis import locker_get, locker_set from app.utils.tools import ( get_file_from_data, + has_user_permission, is_user_member_of_any_group, save_file_as_data, ) + +class RafflePermissions(ModulePermissions): + access_raffle = "access_raffle" + manage_raffle = "manage_raffle" + manage_cash = "manage_cash" + + module = Module( root="tombola", tag="Raffle", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=None, + permissions=RafflePermissions, ) hyperion_raffle_logger = logging.getLogger("hyperion.raffle") @@ -47,7 +56,9 @@ ) async def get_raffle( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Return all raffles @@ -63,7 +74,9 @@ async def get_raffle( async def create_raffle( raffle: schemas_raffle.RaffleBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.manage_raffle]), + ), ): """ Create a new raffle @@ -87,7 +100,9 @@ async def edit_raffle( raffle_id: str, raffle_update: schemas_raffle.RaffleEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Edit a raffle @@ -125,7 +140,9 @@ async def edit_raffle( async def delete_raffle( raffle_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Delete a raffle. @@ -159,7 +176,9 @@ async def delete_raffle( async def get_raffles_by_group_id( group_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Return all raffles from a group @@ -175,7 +194,9 @@ async def get_raffles_by_group_id( async def get_raffle_stats( raffle_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """Return the number of ticket sold and the total amount recollected for a raffle""" raffle = await cruds_raffle.get_raffle_by_id(db=db, raffle_id=raffle_id) @@ -203,7 +224,9 @@ async def get_raffle_stats( async def create_current_raffle_logo( raffle_id: str, image: UploadFile = File(...), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), request_id: str = Depends(get_request_id), db: AsyncSession = Depends(get_db), ): @@ -250,7 +273,9 @@ async def create_current_raffle_logo( ) async def read_raffle_logo( raffle_id: str, - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -274,7 +299,9 @@ async def read_raffle_logo( ) async def get_pack_tickets( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Return all tickets @@ -290,7 +317,9 @@ async def get_pack_tickets( async def create_packticket( packticket: schemas_raffle.PackTicketBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Create a new packticket @@ -323,7 +352,9 @@ async def edit_packticket( packticket_id: str, packticket_update: schemas_raffle.PackTicketEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Edit a packticket @@ -369,7 +400,9 @@ async def edit_packticket( async def delete_packticket( packticket_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Delete a packticket. @@ -405,7 +438,9 @@ async def delete_packticket( async def get_pack_tickets_by_raffle_id( raffle_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Return all pack_tickets associated to a raffle @@ -420,7 +455,9 @@ async def get_pack_tickets_by_raffle_id( ) async def get_tickets( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.manage_raffle]), + ), ): """ Return all tickets @@ -439,7 +476,9 @@ async def buy_ticket( pack_id: str, db: AsyncSession = Depends(get_db), redis_client: Redis | None = Depends(get_redis_client), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), request_id: str = Depends(get_request_id), ): """ @@ -517,7 +556,9 @@ async def buy_ticket( async def get_tickets_by_userid( user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Get tickets of a specific user. @@ -528,7 +569,10 @@ async def get_tickets_by_userid( if user_db is None: raise HTTPException(status_code=404, detail="User not found") - if not (user_id == user.id or is_user_member_of_any_group(user, [GroupType.admin])): + if not ( + user_id == user.id + or await has_user_permission(user, RafflePermissions.manage_raffle, db) + ): raise HTTPException( status_code=403, detail="Users that are not member of the group admin can only access the endpoint for their own user_id.", @@ -545,7 +589,9 @@ async def get_tickets_by_userid( async def get_tickets_by_raffleid( raffle_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Get tickets from a specific raffle. @@ -579,7 +625,9 @@ async def get_tickets_by_raffleid( ) async def get_prizes( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Return all prizes @@ -595,7 +643,9 @@ async def get_prizes( async def create_prize( prize: schemas_raffle.PrizeBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Create a new prize @@ -631,7 +681,9 @@ async def edit_prize( prize_id: str, prize_update: schemas_raffle.PrizeEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Edit a prize @@ -670,7 +722,9 @@ async def edit_prize( async def delete_prize( prize_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Delete a prize. @@ -704,7 +758,9 @@ async def delete_prize( async def get_prizes_by_raffleid( raffle_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Get prizes from a specific raffle. @@ -721,7 +777,9 @@ async def get_prizes_by_raffleid( async def create_prize_picture( prize_id: str, image: UploadFile = File(...), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), request_id: str = Depends(get_request_id), db: AsyncSession = Depends(get_db), ): @@ -773,7 +831,9 @@ async def create_prize_picture( ) async def read_prize_logo( prize_id: str, - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -797,7 +857,9 @@ async def read_prize_logo( ) async def get_users_cash( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.manage_cash]), + ), ): """ Get cash from all users. @@ -815,7 +877,9 @@ async def get_users_cash( async def get_cash_by_id( user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Get cash from a specific user. @@ -826,9 +890,10 @@ async def get_cash_by_id( if user_db is None: raise HTTPException(status_code=404, detail="User not found") - if user_id == user.id or is_user_member_of_any_group( + if user_id == user.id or await has_user_permission( user, - [GroupType.admin], + RafflePermissions.manage_cash, + db, ): cash = await cruds_raffle.get_cash_by_id(user_id=user_id, db=db) if cash is not None: @@ -852,7 +917,9 @@ async def create_cash_of_user( user_id: str, cash: schemas_raffle.CashEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.manage_cash]), + ), ): """ Create cash for a user. @@ -894,7 +961,9 @@ async def edit_cash_by_id( user_id: str, balance: schemas_raffle.CashEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.manage_cash]), + ), redis_client: Redis = Depends(get_redis_client), ): """ @@ -941,7 +1010,9 @@ async def edit_cash_by_id( async def draw_winner( prize_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): prize = await cruds_raffle.get_prize_by_id(db=db, prize_id=prize_id) if prize is None: @@ -982,7 +1053,9 @@ async def draw_winner( async def open_raffle( raffle_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Open a raffle @@ -1020,7 +1093,9 @@ async def open_raffle( async def lock_raffle( raffle_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RafflePermissions.access_raffle]), + ), ): """ Lock a raffle diff --git a/app/modules/raid/endpoints_raid.py b/app/modules/raid/endpoints_raid.py index 9b866fd244..b23c60f345 100644 --- a/app/modules/raid/endpoints_raid.py +++ b/app/modules/raid/endpoints_raid.py @@ -7,15 +7,15 @@ from fastapi.responses import FileResponse from sqlalchemy.ext.asyncio import AsyncSession -from app.core.groups.groups_type import AccountType, GroupType +from app.core.groups.groups_type import AccountType from app.core.payment.payment_tool import PaymentTool from app.core.payment.types_payment import HelloAssoConfigName +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import models_users, schemas_users from app.dependencies import ( get_db, get_payment_tool, - is_user, - is_user_in, + is_user_allowed_to, ) from app.modules.raid import coredata_raid, cruds_raid, models_raid, schemas_raid from app.modules.raid.raid_type import DocumentType, DocumentValidation, Size @@ -34,7 +34,7 @@ get_core_data, get_file_from_data, get_random_string, - is_user_member_of_any_group, + has_user_permission, save_file_as_data, set_core_data, ) @@ -42,12 +42,18 @@ hyperion_error_logger = logging.getLogger("hyperion.error") +class RaidPermissions(ModulePermissions): + access_raid = "access_raid" + manage_raid = "manage_raid" + + module = Module( root="raid", tag="Raid", payment_callback=validate_payment, - default_allowed_account_types=[AccountType.student, AccountType.staff], + default_allowed_account_types=list(AccountType), factory=None, + permissions=RaidPermissions, ) @@ -59,14 +65,17 @@ async def get_participant_by_id( participant_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.access_raid]), + ), ): """ Get a participant by id """ - if participant_id != user.id and not is_user_member_of_any_group( + if participant_id != user.id and not await has_user_permission( user, - [GroupType.raid_admin], + RaidPermissions.manage_raid, + db, ): raise HTTPException( status_code=403, @@ -83,7 +92,9 @@ async def get_participant_by_id( ) async def create_participant( participant: schemas_raid.RaidParticipantBase, - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.access_raid]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -117,7 +128,9 @@ async def create_participant( async def update_participant( participant_id: str, participant: schemas_raid.RaidParticipantUpdate, - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.access_raid]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -219,7 +232,9 @@ async def update_participant( ) async def create_team( team: schemas_raid.RaidTeamBase, - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.access_raid]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -254,7 +269,9 @@ async def create_team( async def get_team_by_participant_id( participant_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.access_raid]), + ), ): """ Get a team by participant id @@ -280,7 +297,9 @@ async def get_team_by_participant_id( ) async def get_all_teams( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.raid_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.manage_raid]), + ), ): """ Get all teams @@ -296,7 +315,9 @@ async def get_all_teams( async def get_team_by_id( team_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.raid_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.manage_raid]), + ), ): """ Get a team by id @@ -312,7 +333,9 @@ async def update_team( team_id: str, team: schemas_raid.RaidTeamUpdate, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.access_raid]), + ), ): """ Update a team @@ -332,7 +355,9 @@ async def update_team( async def delete_team( team_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.raid_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.manage_raid]), + ), ): """ Delete a team @@ -350,7 +375,9 @@ async def delete_team( ) async def delete_all_teams( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.raid_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.manage_raid]), + ), ): """ Delete all teams @@ -375,7 +402,9 @@ async def delete_all_teams( async def upload_document( document_type: DocumentType, file: UploadFile = File(...), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.access_raid]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -430,7 +459,9 @@ async def upload_document( async def read_document( document_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.access_raid]), + ), ): """ Read a document @@ -460,7 +491,11 @@ async def read_document( user.id, participant.id, db, - ) and not is_user_member_of_any_group(user, [GroupType.raid_admin]): + ) and not await has_user_permission( + user, + RaidPermissions.manage_raid, + db, + ): raise HTTPException( status_code=403, detail="The owner of this document is not a member of your team.", @@ -481,7 +516,9 @@ async def validate_document( document_id: str, validation: DocumentValidation, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.raid_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.manage_raid]), + ), ): """ Validate a document @@ -498,7 +535,9 @@ async def set_security_file( security_file: schemas_raid.SecurityFileBase, participant_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.access_raid]), + ), ): """ Confirm security file @@ -554,7 +593,9 @@ async def set_security_file( async def confirm_payment( participant_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.raid_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.manage_raid]), + ), ): """ Confirm payment manually @@ -569,7 +610,9 @@ async def confirm_payment( async def confirm_t_shirt_payment( participant_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.raid_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.manage_raid]), + ), ): """ Confirm T shirt payment @@ -591,7 +634,9 @@ async def confirm_t_shirt_payment( async def validate_attestation_on_honour( participant_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.access_raid]), + ), ): """ Validate attestation on honour @@ -609,7 +654,9 @@ async def validate_attestation_on_honour( async def create_invite_token( team_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.access_raid]), + ), ): """ Create an invite token @@ -643,7 +690,9 @@ async def create_invite_token( async def join_team( token: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.access_raid]), + ), ): """ Join a team @@ -690,7 +739,9 @@ async def kick_team_member( team_id: str, participant_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.raid_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.manage_raid]), + ), ): """ Leave a team @@ -725,7 +776,9 @@ async def merge_teams( team1_id: str, team2_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.raid_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.manage_raid]), + ), ): """ Merge two teams @@ -772,7 +825,9 @@ async def merge_teams( ) async def get_raid_information( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.access_raid]), + ), ): """ Get raid information @@ -787,7 +842,9 @@ async def get_raid_information( async def update_raid_information( raid_information: coredata_raid.RaidInformation, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.raid_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.manage_raid]), + ), ): """ Update raid information @@ -815,7 +872,9 @@ async def update_raid_information( async def update_drive_folders( drive_folders: schemas_raid.RaidDriveFoldersCreation, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.raid_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.manage_raid]), + ), ): """ Update drive folders @@ -836,7 +895,9 @@ async def update_drive_folders( ) async def get_drive_folders( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.raid_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.manage_raid]), + ), ): """ Get drive folders @@ -851,7 +912,9 @@ async def get_drive_folders( ) async def get_raid_price( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.access_raid]), + ), ): """ Get raid price @@ -866,7 +929,9 @@ async def get_raid_price( async def update_raid_price( raid_price: coredata_raid.RaidPrice, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.raid_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.manage_raid]), + ), ): """ Update raid price @@ -881,7 +946,9 @@ async def update_raid_price( ) async def get_payment_url( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.access_raid]), + ), payment_tool: PaymentTool = Depends(get_payment_tool(HelloAssoConfigName.RAID)), ): """ @@ -931,7 +998,9 @@ async def get_payment_url( ) async def download_security_files_zip( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.raid_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.manage_raid]), + ), ): """ Generate and serve a ZIP file containing all security files. @@ -953,7 +1022,9 @@ async def download_security_files_zip( ) async def download_team_files_zip( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.raid_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RaidPermissions.manage_raid]), + ), ): """ Generate and serve a ZIP file containing all team files. diff --git a/app/modules/recommendation/endpoints_recommendation.py b/app/modules/recommendation/endpoints_recommendation.py index ff8b96c447..f0ad8492b7 100644 --- a/app/modules/recommendation/endpoints_recommendation.py +++ b/app/modules/recommendation/endpoints_recommendation.py @@ -5,12 +5,12 @@ from fastapi.responses import FileResponse from sqlalchemy.ext.asyncio import AsyncSession -from app.core.groups.groups_type import AccountType, GroupType +from app.core.groups.groups_type import AccountType +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import models_users from app.dependencies import ( get_db, - is_user_a_member, - is_user_in, + is_user_allowed_to, ) from app.modules.recommendation import ( cruds_recommendation, @@ -26,11 +26,17 @@ router = APIRouter() +class RecommendationPermissions(ModulePermissions): + access_recommendation = "access_recommendation" + manage_recommendation = "manage_recommendation" + + module = Module( root="recommendation", tag="Recommendation", default_allowed_account_types=[AccountType.student, AccountType.staff], factory=RecommendationFactory(), + permissions=RecommendationPermissions, ) @@ -41,7 +47,9 @@ ) async def get_recommendation( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RecommendationPermissions.access_recommendation]), + ), ): """ Get recommendations. @@ -60,7 +68,9 @@ async def get_recommendation( async def create_recommendation( recommendation: schemas_recommendation.RecommendationBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.BDE)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RecommendationPermissions.manage_recommendation]), + ), ): """ Create a recommendation. @@ -88,7 +98,9 @@ async def edit_recommendation( recommendation_id: uuid.UUID, recommendation: schemas_recommendation.RecommendationEdit, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.BDE)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RecommendationPermissions.manage_recommendation]), + ), ): """ Edit a recommendation. @@ -113,7 +125,9 @@ async def edit_recommendation( async def delete_recommendation( recommendation_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.BDE)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RecommendationPermissions.manage_recommendation]), + ), ): """ Delete a recommendation. @@ -138,7 +152,9 @@ async def delete_recommendation( async def read_recommendation_image( recommendation_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_a_member), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RecommendationPermissions.access_recommendation]), + ), ): """ Get the image of a recommendation. @@ -168,7 +184,9 @@ async def read_recommendation_image( async def create_recommendation_image( recommendation_id: uuid.UUID, image: UploadFile = File(), - user: models_users.CoreUser = Depends(is_user_in(GroupType.BDE)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([RecommendationPermissions.manage_recommendation]), + ), db: AsyncSession = Depends(get_db), ): """ diff --git a/app/modules/seed_library/endpoints_seed_library.py b/app/modules/seed_library/endpoints_seed_library.py index d979ecf88e..83bfb4a98c 100644 --- a/app/modules/seed_library/endpoints_seed_library.py +++ b/app/modules/seed_library/endpoints_seed_library.py @@ -5,12 +5,12 @@ from fastapi import Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession -from app.core.groups.groups_type import AccountType, GroupType +from app.core.groups.groups_type import AccountType +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import models_users from app.dependencies import ( get_db, - is_user, - is_user_in, + is_user_allowed_to, ) from app.modules.seed_library import ( coredata_seed_library, @@ -21,13 +21,20 @@ from app.modules.seed_library.types_seed_library import PlantState, SpeciesType from app.types.module import Module from app.utils import tools -from app.utils.tools import is_user_member_of_any_group +from app.utils.tools import has_user_permission + + +class SeedLibraryPermissions(ModulePermissions): + access_seed_library = "access_seed_library" + manage_seed_library = "manage_seed_library" + module = Module( root="seed_library", tag="seed_library", - default_allowed_account_types=[AccountType.student, AccountType.staff], + default_allowed_account_types=list(AccountType), factory=SeedLibraryFactory(), + permissions=SeedLibraryPermissions, ) @@ -41,7 +48,9 @@ ) async def get_all_species( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.access_seed_library]), + ), ): """ Return all species from database as a list of SpeciesComplete schemas @@ -55,7 +64,9 @@ async def get_all_species( status_code=200, ) async def get_all_species_types( - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.access_seed_library]), + ), ): """ Return all available types of species from SpeciesType enum. @@ -73,7 +84,9 @@ async def get_all_species_types( async def create_species( species_base: schemas_seed_library.SpeciesBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.seed_library)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.manage_seed_library]), + ), ): """ Create a new Species by giving an SpeciesBase scheme @@ -122,7 +135,9 @@ async def create_species( async def update_species( species_id: uuid.UUID, species_edit: schemas_seed_library.SpeciesEdit, - user: models_users.CoreUser = Depends(is_user_in(GroupType.seed_library)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.manage_seed_library]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -178,7 +193,9 @@ async def update_species( async def delete_species( species_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.seed_library)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.manage_seed_library]), + ), ): """ Delete a Species @@ -209,7 +226,9 @@ async def delete_species( ) async def get_waiting_plants( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.access_seed_library]), + ), ): """ Return all plants where state=waiting from database as a list of PlantsComplete schemas @@ -224,7 +243,9 @@ async def get_waiting_plants( ) async def get_my_plants( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.access_seed_library]), + ), ): """ Return all plants where user ={user_id} from database as a list of PlantsComplete schemas @@ -240,7 +261,9 @@ async def get_my_plants( async def get_plants_by_user_id( user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.seed_library)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.manage_seed_library]), + ), ): """ Return all plants where borrower_id = {user_id} from database as a list of PlantsComplete schemas @@ -256,7 +279,9 @@ async def get_plants_by_user_id( async def get_plant_by_id( plant_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.access_seed_library]), + ), ): """ Return the plants where plant ={plant_id} from database as a PlantsComplete schemas @@ -275,16 +300,21 @@ async def get_plant_by_id( async def create_plant( plant_base: schemas_seed_library.PlantCreation, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.access_seed_library]), + ), ): """ Create a new Plant by giving an PlantCreation scheme **This endpoint is only usable if the plant has an ancestor_id or by seed_library ** """ if plant_base.ancestor_id is None: - if not is_user_member_of_any_group( - user=user, - allowed_groups=[GroupType.seed_library], + if not ( + await has_user_permission( + user=user, + permission_name=SeedLibraryPermissions.manage_seed_library, + db=db, + ) ): raise HTTPException( status_code=403, @@ -337,7 +367,9 @@ async def create_plant( async def update_plant( plant_id: uuid.UUID, plant_edit: schemas_seed_library.PlantEdit, - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.access_seed_library]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -367,7 +399,9 @@ async def update_plant( async def update_plant_admin( plant_id: uuid.UUID, plant_edit: schemas_seed_library.PlantEdit, - user: models_users.CoreUser = Depends(is_user_in(GroupType.seed_library)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.manage_seed_library]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -391,7 +425,9 @@ async def update_plant_admin( ) async def borrow_plant( plant_id: uuid.UUID, - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.access_seed_library]), + ), db: AsyncSession = Depends(get_db), ): """ @@ -412,7 +448,9 @@ async def borrow_plant( async def delete_plant( plant_id: uuid.UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.seed_library)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.manage_seed_library]), + ), ): """ Delete a Plant @@ -442,7 +480,9 @@ async def delete_plant( ) async def get_seed_library_information( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.access_seed_library]), + ), ): return await tools.get_core_data( coredata_seed_library.SeedLibraryInformation, @@ -456,7 +496,9 @@ async def get_seed_library_information( ) async def update_seed_library_information( information: coredata_seed_library.SeedLibraryInformation, - user: models_users.CoreUser = Depends(is_user_in(GroupType.seed_library)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SeedLibraryPermissions.manage_seed_library]), + ), db: AsyncSession = Depends(get_db), ): await tools.set_core_data( diff --git a/app/modules/sport_competition/dependencies_sport_competition.py b/app/modules/sport_competition/dependencies_sport_competition.py index 5b3595cb87..b93e7b6adb 100644 --- a/app/modules/sport_competition/dependencies_sport_competition.py +++ b/app/modules/sport_competition/dependencies_sport_competition.py @@ -6,7 +6,6 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.core.groups import groups_type -from app.core.groups.groups_type import GroupType from app.core.users import models_users from app.dependencies import ( get_db, @@ -17,8 +16,12 @@ cruds_sport_competition, schemas_sport_competition, ) +from app.modules.sport_competition.permissions_sport_competition import ( + SportCompetitionPermissions, +) from app.modules.sport_competition.types_sport_competition import CompetitionGroupType from app.types.scopes_type import ScopeType +from app.utils.tools import has_user_permission hyperion_access_logger = logging.getLogger("hyperion.access") @@ -63,7 +66,7 @@ async def get_user_from_user_id( def is_competition_user( - group_id: GroupType | None = None, + group_id: str | None = None, competition_group: CompetitionGroupType | None = None, exclude_external: bool = False, ) -> Callable[ @@ -104,8 +107,12 @@ async def is_user_a_member_of( status_code=403, detail="User is not a member of the group", ) - if competition_group is not None and not any( - group.id == GroupType.competition_admin.value for group in user.user.groups + if competition_group is not None and not ( + await has_user_permission( + user.user, + SportCompetitionPermissions.manage_sport_competition, + db, + ) ): user_groups = ( await cruds_sport_competition.load_user_competition_groups_memberships( @@ -125,7 +132,7 @@ async def is_user_a_member_of( def has_user_competition_access( - group_id: GroupType | None = None, + group_id: str | None = None, competition_group: CompetitionGroupType | None = None, exclude_external: bool = False, ) -> Callable[ @@ -163,8 +170,12 @@ async def is_user_a_member_of( status_code=403, detail="User is not a member of the group", ) - if competition_group is not None and not any( - group.id == GroupType.competition_admin.value for group in user.groups + if competition_group is not None and not ( + await has_user_permission( + user, + SportCompetitionPermissions.manage_sport_competition, + db, + ) ): user_groups = ( await cruds_sport_competition.load_user_competition_groups_memberships( diff --git a/app/modules/sport_competition/endpoints_sport_competition.py b/app/modules/sport_competition/endpoints_sport_competition.py index 124f90e315..dd37b15b32 100644 --- a/app/modules/sport_competition/endpoints_sport_competition.py +++ b/app/modules/sport_competition/endpoints_sport_competition.py @@ -7,13 +7,17 @@ from fastapi.responses import FileResponse from sqlalchemy.ext.asyncio import AsyncSession -from app.core.groups.groups_type import GroupType, get_account_types_except_externals +from app.core.groups.groups_type import get_account_types_except_externals from app.core.payment.payment_tool import PaymentTool from app.core.payment.types_payment import HelloAssoConfigName from app.core.schools import cruds_schools from app.core.schools.schools_type import SchoolType from app.core.users import cruds_users, models_users, schemas_users -from app.dependencies import get_db, get_payment_tool, is_user, is_user_in +from app.dependencies import ( + get_db, + get_payment_tool, + is_user_allowed_to, +) from app.modules.sport_competition import ( cruds_sport_competition, schemas_sport_competition, @@ -23,6 +27,10 @@ has_user_competition_access, is_competition_user, ) +from app.modules.sport_competition.models_sport_competition import Sport +from app.modules.sport_competition.permissions_sport_competition import ( + SportCompetitionPermissions, +) from app.modules.sport_competition.types_sport_competition import ( CompetitionGroupType, ExcelExportParams, @@ -45,18 +53,20 @@ from app.utils.tools import ( delete_file_from_data, get_file_from_data, - is_user_member_of_any_group, + has_user_permission, save_file_as_data, ) hyperion_error_logger = logging.getLogger("hyperion.error") + module = Module( root="sport_competition", tag="Sport Competition", default_allowed_account_types=get_account_types_except_externals(), payment_callback=validate_payment, factory=None, + permissions=SportCompetitionPermissions, ) # region: Sport @@ -68,7 +78,9 @@ ) async def get_sports( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> list[schemas_sport_competition.Sport]: return await cruds_sport_competition.load_all_sports(db) @@ -81,8 +93,8 @@ async def get_sports( async def create_sport( sport: schemas_sport_competition.SportBase, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), ) -> schemas_sport_competition.Sport: stored = await cruds_sport_competition.load_sport_by_name(sport.name, db) @@ -104,8 +116,8 @@ async def edit_sport( sport_id: UUID, sport: schemas_sport_competition.SportEdit, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), ) -> None: stored = await cruds_sport_competition.load_sport_by_id(sport_id, db) @@ -135,8 +147,8 @@ async def edit_sport( async def delete_sport( sport_id: UUID, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), ) -> None: stored = await cruds_sport_competition.load_sport_by_id(sport_id, db) @@ -163,7 +175,9 @@ async def delete_sport( ) async def get_editions( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> list[schemas_sport_competition.CompetitionEdition]: return await cruds_sport_competition.load_all_editions(db) @@ -174,7 +188,9 @@ async def get_editions( ) async def get_active_edition( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> schemas_sport_competition.CompetitionEdition | None: """ Get the currently active competition edition. @@ -191,8 +207,8 @@ async def get_active_edition( async def create_edition( edition: schemas_sport_competition.CompetitionEditionBase, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), ) -> schemas_sport_competition.CompetitionEdition: stored = await cruds_sport_competition.load_edition_by_name(edition.name, db) @@ -216,8 +232,8 @@ async def create_edition( async def activate_edition( edition_id: UUID, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), ) -> None: """ @@ -240,8 +256,8 @@ async def activate_edition( async def enable_inscription( edition_id: UUID, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), enable: bool = Body(), ) -> None: @@ -271,8 +287,8 @@ async def edit_edition( edition_id: UUID, edition_edit: schemas_sport_competition.CompetitionEditionEdit, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), ) -> None: stored = await cruds_sport_competition.load_edition_by_id(edition_id, db) @@ -297,7 +313,9 @@ async def get_competition_users( edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> list[schemas_sport_competition.CompetitionUser]: """ Get all competition users for the current edition. @@ -315,7 +333,9 @@ async def get_competition_users_by_school( edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> list[schemas_sport_competition.CompetitionUser]: """ Get all competition users for the current edition by school. @@ -336,7 +356,9 @@ async def get_current_user_competition( edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> schemas_sport_competition.CompetitionUser: """ Get the competition user for the current edition. @@ -361,8 +383,8 @@ async def export_competition_users_data( included_fields: list[ExcelExportParams] = Query(default=[]), exclude_non_validated: bool = False, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -434,7 +456,7 @@ async def get_competition_user( get_current_edition, ), user: models_users.CoreUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), ) -> schemas_sport_competition.CompetitionUser: """ @@ -461,7 +483,9 @@ async def create_competition_user( edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> schemas_sport_competition.CompetitionUserSimple: """ Create a competition user for the current edition. @@ -523,7 +547,9 @@ async def edit_current_user_competition( edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), - user: schemas_users.CoreUser = Depends(is_user()), + user: schemas_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> None: """ Edit the current user's competition user for the current edition. @@ -600,8 +626,8 @@ async def edit_competition_user( edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), - current_user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + current_user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), ) -> None: """ @@ -692,7 +718,13 @@ async def validate_competition_user( ) if ( - GroupType.competition_admin.value not in [group.id for group in user.groups] + not ( + await has_user_permission( + user, + SportCompetitionPermissions.manage_sport_competition, + db, + ) + ) and user.school_id != user_to_validate.user.school_id ): raise HTTPException( @@ -758,7 +790,13 @@ async def invalidate_competition_user( detail="User is not validated", ) if ( - GroupType.competition_admin.value not in [group.id for group in user.groups] + not ( + await has_user_permission( + user, + SportCompetitionPermissions.manage_sport_competition, + db, + ) + ) and user.school_id != user_to_invalidate.user.school_id ): raise HTTPException( @@ -793,7 +831,7 @@ async def delete_competition_user( get_current_edition, ), user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), ) -> None: stored = await cruds_sport_competition.load_competition_user_by_id( @@ -857,8 +895,8 @@ async def delete_competition_user( async def get_group_members( group: CompetitionGroupType, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -881,7 +919,7 @@ async def get_group_members( async def get_current_user_groups( db: AsyncSession = Depends(get_db), user: schemas_users.CoreUser = Depends( - is_user(), + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -906,7 +944,7 @@ async def get_user_groups( user_id: str, db: AsyncSession = Depends(get_db), user: schemas_users.CoreUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -937,8 +975,8 @@ async def add_user_to_group( group: CompetitionGroupType, user_id: str, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -976,8 +1014,8 @@ async def remove_user_from_group( group: CompetitionGroupType, user_id: str, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -1014,7 +1052,9 @@ async def get_schools( edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> list[schemas_sport_competition.SchoolExtension]: return await cruds_sport_competition.load_all_schools(edition.id, db) @@ -1029,7 +1069,9 @@ async def get_school( edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> schemas_sport_competition.SchoolExtension: school = await cruds_sport_competition.load_school_by_id(school_id, db) if school is None: @@ -1048,8 +1090,8 @@ async def get_school( async def create_school_extension( school: schemas_sport_competition.SchoolExtensionBase, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), ) -> schemas_sport_competition.SchoolExtensionBase: core_school = await cruds_schools.get_school_by_id(db, school.school_id) @@ -1076,8 +1118,8 @@ async def edit_school_extension( school_id: UUID, school: schemas_sport_competition.SchoolExtensionEdit, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -1099,8 +1141,8 @@ async def edit_school_extension( async def delete_school_extension( school_id: UUID, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -1168,8 +1210,8 @@ async def create_school_general_quota( school_id: UUID, quota_info: schemas_sport_competition.SchoolGeneralQuotaBase, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -1217,8 +1259,8 @@ async def edit_school_general_quota( school_id: UUID, quota_info: schemas_sport_competition.SchoolGeneralQuotaBase, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -1259,8 +1301,8 @@ async def edit_school_general_quota( async def get_quotas_for_sport( sport_id: UUID, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -1289,7 +1331,9 @@ async def get_quotas_for_school( edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> list[schemas_sport_competition.SchoolSportQuota]: school = await cruds_sport_competition.load_school_by_id(school_id, db) if school is None: @@ -1313,8 +1357,8 @@ async def create_sport_quota( sport_id: UUID, quota_info: schemas_sport_competition.SportQuotaInfo, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -1359,8 +1403,8 @@ async def edit_sport_quota( sport_id: UUID, quota_info: schemas_sport_competition.SchoolSportQuotaEdit, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -1394,8 +1438,8 @@ async def delete_sport_quota( school_id: UUID, sport_id: UUID, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -1434,7 +1478,9 @@ async def get_product_quotas_for_school( edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> list[schemas_sport_competition.SchoolProductQuota]: school = await cruds_sport_competition.load_school_by_id(school_id, db) if school is None: @@ -1456,8 +1502,8 @@ async def get_product_quotas_for_school( async def get_product_quotas_for_product( product_id: UUID, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -1484,8 +1530,8 @@ async def create_product_quota( school_id: UUID, quota_info: schemas_sport_competition.SchoolProductQuotaBase, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -1532,8 +1578,8 @@ async def edit_product_quota( product_id: UUID, quota_info: schemas_sport_competition.SchoolProductQuotaEdit, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -1565,8 +1611,8 @@ async def delete_product_quota( school_id: UUID, product_id: UUID, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -1599,7 +1645,9 @@ async def delete_product_quota( ) async def get_teams( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -1613,7 +1661,9 @@ async def get_teams( ) async def get_current_user_team_as_captain( db: AsyncSession = Depends(get_db), - user: schemas_users.CoreUser = Depends(is_user()), + user: schemas_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -1639,7 +1689,7 @@ async def get_teams_for_sport( sport_id: UUID, db: AsyncSession = Depends(get_db), user: schemas_sport_competition.CompetitionUser = Depends( - is_user(), + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -1668,7 +1718,9 @@ async def get_teams_for_school( edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> list[schemas_sport_competition.TeamComplete]: school = await cruds_sport_competition.load_school_by_id(school_id, db) if school is None: @@ -1694,7 +1746,9 @@ async def get_sport_teams_for_school_and_sport( edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> list[schemas_sport_competition.TeamComplete]: school = await cruds_sport_competition.load_school_by_id(school_id, db) if school is None: @@ -1727,7 +1781,9 @@ async def create_team( edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), - user: schemas_users.CoreUser = Depends(is_user()), + user: schemas_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> schemas_sport_competition.Team: if not edition.inscription_enabled: raise HTTPException( @@ -1735,9 +1791,13 @@ async def create_team( detail="Inscriptions are not enabled for the current edition", ) - if GroupType.competition_admin.value not in [ - group.id for group in user.groups - ] and (user.id != team_info.captain_id or user.school_id != team_info.school_id): + if not ( + await has_user_permission( + user, + SportCompetitionPermissions.manage_sport_competition, + db, + ) + ) and (user.id != team_info.captain_id or user.school_id != team_info.school_id): raise HTTPException( status_code=403, detail="Unauthorized action", @@ -1813,7 +1873,9 @@ async def edit_team( team_id: UUID, team_info: schemas_sport_competition.TeamEdit, db: AsyncSession = Depends(get_db), - user: schemas_users.CoreUser = Depends(is_user()), + user: schemas_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> None: stored = await cruds_sport_competition.load_team_by_id(team_id, db) if stored is None: @@ -1830,7 +1892,13 @@ async def edit_team( ) if ( user.id != stored.captain_id - and GroupType.competition_admin.value not in [group.id for group in user.groups] + and not ( + await has_user_permission( + user, + SportCompetitionPermissions.manage_sport_competition, + db, + ) + ) and ( CompetitionGroupType.schools_bds not in [group.group for group in user_competition_groups] @@ -1886,7 +1954,9 @@ async def edit_team( async def delete_team( team_id: UUID, db: AsyncSession = Depends(get_db), - user: schemas_users.CoreUser = Depends(is_user()), + user: schemas_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -1906,7 +1976,13 @@ async def delete_team( ) if ( user.id != stored.captain_id - and GroupType.competition_admin.value not in [group.id for group in user.groups] + and not ( + await has_user_permission( + user, + SportCompetitionPermissions.manage_sport_competition, + db, + ) + ) and ( CompetitionGroupType.schools_bds not in [group.group for group in user_competition_groups] @@ -1927,7 +2003,9 @@ async def delete_team( ) async def get_current_user_participant( db: AsyncSession = Depends(get_db), - user: schemas_users.CoreUser = Depends(is_user()), + user: schemas_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -1952,7 +2030,9 @@ async def get_current_user_participant( async def get_participants_for_sport( sport_id: UUID, db: AsyncSession = Depends(get_db), - user: schemas_users.CoreUser = Depends(is_user()), + user: schemas_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -1977,13 +2057,21 @@ async def get_participants_for_sport( async def get_participants_for_school( school_id: UUID, db: AsyncSession = Depends(get_db), - user: schemas_users.CoreUser = Depends(is_user()), + user: schemas_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), ) -> list[schemas_sport_competition.ParticipantComplete]: if ( - GroupType.competition_admin.value not in [group.id for group in user.groups] + not ( + await has_user_permission( + user, + SportCompetitionPermissions.manage_sport_competition, + db, + ) + ) and user.school_id != school_id ): raise HTTPException( @@ -2004,13 +2092,21 @@ async def get_participants_for_school( async def download_participant_certificate( user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), ) -> FileResponse: if ( - GroupType.competition_admin.value not in [group.id for group in user.groups] + not ( + await has_user_permission( + user, + SportCompetitionPermissions.manage_sport_competition, + db, + ) + ) and user.id != user_id ): raise HTTPException( @@ -2196,7 +2292,9 @@ async def upload_participant_certificate( sport_id: UUID, certificate: UploadFile = File(...), db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -2244,8 +2342,8 @@ async def mark_participant_license_as_valid( user_id: str, is_license_valid: bool, db: AsyncSession = Depends(get_db), - user: schemas_sport_competition.CompetitionUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -2343,7 +2441,13 @@ async def delete_participant( ), ) -> None: if ( - GroupType.competition_admin.value not in [group.id for group in user.groups] + not ( + await has_user_permission( + user, + SportCompetitionPermissions.manage_sport_competition, + db, + ) + ) and not edition.inscription_enabled ): raise HTTPException( @@ -2367,7 +2471,13 @@ async def delete_participant( detail="Cannot delete a validated participant", ) if ( - GroupType.competition_admin.value not in [group.id for group in user.groups] + not ( + await has_user_permission( + user, + SportCompetitionPermissions.manage_sport_competition, + db, + ) + ) and user.school_id != participant.school_id ): raise HTTPException( @@ -2400,7 +2510,9 @@ async def delete_participant( async def delete_participant_certificate_file( sport_id: UUID, db: AsyncSession = Depends(get_db), - user: schemas_users.CoreUser = Depends(is_user()), + user: schemas_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -2440,7 +2552,9 @@ async def delete_participant_certificate_file( ) async def get_all_locations( db: AsyncSession = Depends(get_db), - user: schemas_users.CoreUser = Depends(is_user()), + user: schemas_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -2458,7 +2572,9 @@ async def get_all_locations( async def get_location_by_id( location_id: UUID, db: AsyncSession = Depends(get_db), - user: schemas_users.CoreUser = Depends(is_user()), + user: schemas_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -2498,7 +2614,7 @@ async def create_location( location_info: schemas_sport_competition.LocationBase, db: AsyncSession = Depends(get_db), user: schemas_users.CoreUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -2535,7 +2651,7 @@ async def edit_location( location_info: schemas_sport_competition.LocationEdit, db: AsyncSession = Depends(get_db), user: schemas_users.CoreUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -2574,7 +2690,7 @@ async def delete_location( location_id: UUID, db: AsyncSession = Depends(get_db), user: schemas_users.CoreUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -2615,7 +2731,9 @@ async def get_all_matches_for_edition( get_current_edition, ), db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> list[schemas_sport_competition.MatchComplete]: return await cruds_sport_competition.load_all_matches_by_edition_id( edition.id, @@ -2633,7 +2751,9 @@ async def get_matches_for_sport_and_edition( get_current_edition, ), db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> list[schemas_sport_competition.MatchComplete]: sport = await cruds_sport_competition.load_sport_by_id(sport_id, db) if sport is None: @@ -2658,7 +2778,9 @@ async def get_matches_for_school_sport_and_edition( get_current_edition, ), db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), ) -> list[schemas_sport_competition.MatchComplete]: school = await cruds_sport_competition.load_school_by_id(school_id, db) if school is None: @@ -2845,7 +2967,9 @@ async def delete_match( ) async def get_global_podiums( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -2864,7 +2988,9 @@ async def get_global_podiums( async def get_sport_podiums( sport_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -2888,7 +3014,9 @@ async def get_sport_podiums( ) async def get_pompom_podiums( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -2907,7 +3035,9 @@ async def get_pompom_podiums( async def get_school_podiums( school_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -3073,7 +3203,9 @@ async def delete_pompom_podium( ) async def get_all_products( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -3093,7 +3225,7 @@ async def create_product( product: schemas_sport_competition.ProductBase, db: AsyncSession = Depends(get_db), user: models_users.CoreUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -3125,7 +3257,7 @@ async def update_product( product: schemas_sport_competition.ProductEdit, db: AsyncSession = Depends(get_db), user: models_users.CoreUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -3161,7 +3293,7 @@ async def delete_product( product_id: UUID, db: AsyncSession = Depends(get_db), user: models_users.CoreUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -3251,7 +3383,7 @@ async def create_product_variant( product_variant: schemas_sport_competition.ProductVariantBase, db: AsyncSession = Depends(get_db), user: models_users.CoreUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -3301,7 +3433,7 @@ async def update_product_variant( product_variant: schemas_sport_competition.ProductVariantEdit, db: AsyncSession = Depends(get_db), user: models_users.CoreUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -3347,7 +3479,7 @@ async def delete_product_variant( variant_id: UUID, db: AsyncSession = Depends(get_db), user: models_users.CoreUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -3438,7 +3570,9 @@ async def get_purchases_by_school_id( async def get_purchases_by_user_id( user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.competition_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -3462,7 +3596,9 @@ async def get_purchases_by_user_id( ) async def get_my_purchases( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -3478,7 +3614,9 @@ async def get_my_purchases( async def create_purchase( purchase: schemas_sport_competition.PurchaseBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -3576,7 +3714,9 @@ async def create_purchase( async def delete_purchase( product_variant_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -3656,9 +3796,13 @@ async def get_users_payments_by_school_id( status_code=404, detail="The school does not exist.", ) - if user.school_id != school_id and GroupType.competition_admin.value not in [ - group.id for group in user.groups - ]: + if user.school_id != school_id and not ( + await has_user_permission( + user, + SportCompetitionPermissions.manage_sport_competition, + db, + ) + ): raise HTTPException( status_code=403, detail="You're not allowed to see other schools payments.", @@ -3688,7 +3832,9 @@ async def get_users_payments_by_school_id( async def get_payments_by_user_id( user_id: str, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -3714,7 +3860,13 @@ async def get_payments_by_user_id( if not ( user_id == user.id - or is_user_member_of_any_group(user, [GroupType.competition_admin]) + or not ( + await has_user_permission( + user, + SportCompetitionPermissions.manage_sport_competition, + db, + ) + ) or ( CompetitionGroupType.schools_bds in [competition_group.group for competition_group in competition_groups] @@ -3741,7 +3893,9 @@ async def create_payment( user_id: str, payment: schemas_sport_competition.PaymentBase, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.competition_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -3827,7 +3981,9 @@ async def delete_payment( user_id: str, payment_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user_in(GroupType.competition_admin)), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -3885,7 +4041,9 @@ async def delete_payment( ) async def get_payment_url( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to([SportCompetitionPermissions.access_sport_competition]), + ), payment_tool: PaymentTool = Depends( get_payment_tool(HelloAssoConfigName.CHALLENGER), ), @@ -3968,7 +4126,14 @@ async def get_payment_url( ) async def get_all_volunteer_shifts( db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to( + [ + SportCompetitionPermissions.volunteer_sport_competition, + SportCompetitionPermissions.manage_sport_competition, + ], + ), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), @@ -3991,7 +4156,7 @@ async def create_volunteer_shift( shift: schemas_sport_competition.VolunteerShiftBase, db: AsyncSession = Depends(get_db), user: models_users.CoreUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -4034,7 +4199,7 @@ async def update_volunteer_shift( shift_edit: schemas_sport_competition.VolunteerShiftEdit, db: AsyncSession = Depends(get_db), user: models_users.CoreUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -4070,7 +4235,7 @@ async def delete_volunteer_shift( shift_id: UUID, db: AsyncSession = Depends(get_db), user: models_users.CoreUser = Depends( - is_user_in(group_id=GroupType.competition_admin), + is_user_allowed_to([SportCompetitionPermissions.manage_sport_competition]), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -4109,8 +4274,13 @@ async def delete_volunteer_shift( ) async def get_my_volunteer_registrations( db: AsyncSession = Depends(get_db), - competition_user: schemas_sport_competition.CompetitionUser = Depends( - is_competition_user(), + user: models_users.CoreUser = Depends( + is_user_allowed_to( + [ + SportCompetitionPermissions.volunteer_sport_competition, + SportCompetitionPermissions.manage_sport_competition, + ], + ), ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, @@ -4120,7 +4290,7 @@ async def get_my_volunteer_registrations( Get my volunteer registrations. """ return await cruds_sport_competition.load_volunteer_registrations_by_user_id( - competition_user.user.id, + user.id, edition.id, db, ) @@ -4133,7 +4303,14 @@ async def get_my_volunteer_registrations( async def register_to_volunteer_shift( shift_id: UUID, db: AsyncSession = Depends(get_db), - user: models_users.CoreUser = Depends(is_user()), + user: models_users.CoreUser = Depends( + is_user_allowed_to( + [ + SportCompetitionPermissions.volunteer_sport_competition, + SportCompetitionPermissions.manage_sport_competition, + ], + ), + ), edition: schemas_sport_competition.CompetitionEdition = Depends( get_current_edition, ), diff --git a/app/modules/sport_competition/permissions_sport_competition.py b/app/modules/sport_competition/permissions_sport_competition.py new file mode 100644 index 0000000000..9194b7ae93 --- /dev/null +++ b/app/modules/sport_competition/permissions_sport_competition.py @@ -0,0 +1,7 @@ +from app.core.permissions.type_permissions import ModulePermissions + + +class SportCompetitionPermissions(ModulePermissions): + access_sport_competition = "access_sport_competition" + manage_sport_competition = "manage_sport_competition" + volunteer_sport_competition = "volunteer_sport_competition" diff --git a/app/types/module.py b/app/types/module.py index 185e3c60fa..39ebdb6e61 100644 --- a/app/types/module.py +++ b/app/types/module.py @@ -6,6 +6,7 @@ from app.core.groups.groups_type import AccountType, GroupType from app.core.notification.schemas_notification import Topic from app.core.payment import schemas_payment +from app.core.permissions.type_permissions import ModulePermissions from app.types.factory import Factory @@ -22,6 +23,7 @@ def __init__( ] | None = None, registred_topics: list[Topic] | None = None, + permissions: type[ModulePermissions] | None = None, ): """ Initialize a new Module object. @@ -32,6 +34,7 @@ def __init__( :param payment_callback: an optional method to call when a payment is notified by HelloAsso. A CheckoutPayment and the database will be provided during the call :param registred_topics: an optionnal list of Topics that should be registered by the module. Modules can also register topics dynamically. Once the Topic was registred, removing it from this list won't delete it + :param permissions: enum declaring permissions strings used by module """ self.root = root self.router = router or APIRouter(tags=[tag]) @@ -41,6 +44,7 @@ def __init__( ) = payment_callback self.registred_topics = registred_topics self.factory = factory + self.permissions = permissions class Module(CoreModule): @@ -58,6 +62,7 @@ def __init__( ] | None = None, registred_topics: list[Topic] | None = None, + permissions: type[ModulePermissions] | None = None, ): """ Initialize a new Module object. @@ -70,6 +75,7 @@ def __init__( :param payment_callback: an optional method to call when a payment is notified by HelloAsso. A CheckoutPayment and the database will be provided during the call :param registred_topics: an optionnal list of Topics that should be registered by the module. Modules can also register topics dynamically. Once the Topic was registred, removing it from this list won't delete it + :param permissions: enum declaring permissions strings used by module """ self.root = root self.default_allowed_groups_ids = default_allowed_groups_ids @@ -81,3 +87,4 @@ def __init__( ) = payment_callback self.registred_topics = registred_topics self.factory = factory + self.permissions = permissions diff --git a/app/utils/auth/providers.py b/app/utils/auth/providers.py index 12c9ceb2f2..8aaef0bd76 100644 --- a/app/utils/auth/providers.py +++ b/app/utils/auth/providers.py @@ -4,12 +4,9 @@ import unidecode from app.core.groups.groups_type import ( - AccountType, GroupType, - get_account_types_except_externals, - get_ecl_account_types, - get_schools_account_types, ) +from app.core.permissions.type_permissions import ModulePermissions from app.core.users import models_users from app.types.floors_type import FloorsType from app.types.scopes_type import ScopeType @@ -34,14 +31,9 @@ class BaseAuthClient: # These "string" scopes won't have any effect for Hyperion but won't raise a warning when asked by the client # WARNING: to be able to use openid connect, `ScopeType.openid` should always be allowed allowed_scopes: set[ScopeType | str] = {ScopeType.openid} - # Restrict the authentication to this client to specific Hyperion groups. + # Restrict the authentication to this client to specific Hyperion groups through permissions. # When set to `None`, users from any group can use the auth client - allowed_groups: list[GroupType] | None = None - # Restrict the authentication to this client to specific Hyperion account types. - # When set to `None`, users from any account type can use the auth client - allowed_account_types: list[AccountType] | None = ( - get_account_types_except_externals() - ) + permission: ModulePermissions | None = None # redirect_uri should alway match the one provided by the client redirect_uri: list[str] # Sometimes, when the client is wrongly configured, it may return an incorrect return_uri. This may also be useful for debugging clients. @@ -105,6 +97,26 @@ def filter_scopes(self, requested_scopes: set[str]) -> set[ScopeType | str]: return self.allowed_scopes.intersection(requested_scopes) +class AuthPermissions(ModulePermissions): + app = "app" + api = "api" + nextcloud = "nextcloud" + piwigo = "piwigo" + hedgedoc = "hedgedoc" + wikijs = "wikijs" + synapse = "synapse" + minecraft = "minecraft" + challenger = "challenger" + openproject = "openproject" + rallly = "rallly" + documenso = "documenso" + raid_registering = "raid_registering" + siarnaq = "siarnaq" + overleaf = "overleaf" + planka = "planka" + slash = "slash" + + class AppAuthClient(BaseAuthClient): """ An auth client for Hyperion mobile application @@ -115,9 +127,7 @@ class AppAuthClient(BaseAuthClient): # WARNING: to be able to use openid connect, `ScopeType.openid` should always be allowed allowed_scopes: set[ScopeType | str] = {ScopeType.API} - allowed_account_types: list[AccountType] | None = ( - None # No restriction on account types - ) + permission = AuthPermissions.app class APIToolAuthClient(BaseAuthClient): @@ -127,10 +137,11 @@ class APIToolAuthClient(BaseAuthClient): allowed_scopes: set[ScopeType | str] = {ScopeType.API} allow_pkce_with_client_secret: bool = True + permission = AuthPermissions.api class NextcloudAuthClient(BaseAuthClient): - allowed_account_types: list[AccountType] | None = get_ecl_account_types() + permission = AuthPermissions.nextcloud # For Nextcloud: # Required iss : the issuer value form .well-known (corresponding code : https://github.com/pulsejet/nextcloud-oidc-login/blob/0c072ecaa02579384bb5e10fbb9d219bbd96cfb8/3rdparty/jumbojett/openid-connect-php/src/OpenIDConnectClient.php#L1255) @@ -154,9 +165,7 @@ def get_userinfo(cls, user: models_users.CoreUser): class PiwigoAuthClient(BaseAuthClient): - # Restrict the authentication to this client to specific Hyperion groups. - # When set to `None`, users from any group can use the auth client - allowed_account_types: list[AccountType] | None = get_ecl_account_types() + permission = AuthPermissions.piwigo def get_userinfo(self, user: models_users.CoreUser) -> dict[str, Any]: """ @@ -189,7 +198,7 @@ class HedgeDocAuthClient(BaseAuthClient): # See app.types.scopes_type.ScopeType for possible values allowed_scopes: set[ScopeType | str] = {ScopeType.profile} - allowed_account_types: list[AccountType] | None = get_ecl_account_types() + permission = AuthPermissions.hedgedoc @classmethod def get_userinfo(cls, user: models_users.CoreUser): @@ -207,7 +216,7 @@ class WikijsAuthClient(BaseAuthClient): # See app.types.scopes_type.ScopeType for possible values allowed_scopes: set[ScopeType | str] = {ScopeType.openid, ScopeType.profile} - allowed_account_types: list[AccountType] | None = get_schools_account_types() + permission = AuthPermissions.wikijs @classmethod def get_userinfo(cls, user: models_users.CoreUser): @@ -224,13 +233,13 @@ class SynapseAuthClient(BaseAuthClient): # See app.types.scopes_type.ScopeType for possible values allowed_scopes: set[ScopeType | str] = {ScopeType.openid, ScopeType.profile} - allowed_account_types: list[AccountType] | None = get_ecl_account_types() - # https://github.com/matrix-org/matrix-authentication-service/issues/2088 return_userinfo_in_id_token: bool = True allow_token_introspection: bool = True + permission = AuthPermissions.synapse + @classmethod def get_userinfo(cls, user: models_users.CoreUser): # Accepted characters are [a-z] [0-9] `.` and `-`. Spaces are replaced by `-` and accents are removed. @@ -256,7 +265,7 @@ class MinecraftAuthClient(BaseAuthClient): # See app.types.scopes_type.ScopeType for possible values allowed_scopes: set[ScopeType | str] = {ScopeType.profile} - allowed_account_types: list[AccountType] | None = get_ecl_account_types() + permission = AuthPermissions.minecraft @classmethod def get_userinfo(cls, user: models_users.CoreUser): @@ -272,7 +281,8 @@ class ChallengerAuthClient(BaseAuthClient): # Set of scopes the auth client is authorized to grant when issuing an access token. # See app.types.scopes_type.ScopeType for possible values allowed_scopes: set[ScopeType | str] = {ScopeType.API} - allowed_account_types: list[AccountType] | None = None + + permission = AuthPermissions.challenger class OpenProjectAuthClient(BaseAuthClient): @@ -280,7 +290,7 @@ class OpenProjectAuthClient(BaseAuthClient): # See app.types.scopes_type.ScopeType for possible values allowed_scopes: set[ScopeType | str] = {ScopeType.openid, ScopeType.profile} - allowed_account_types: list[AccountType] | None = None + permission = AuthPermissions.openproject @classmethod def get_userinfo(cls, user: models_users.CoreUser): @@ -304,6 +314,8 @@ class RalllyAuthClient(BaseAuthClient): return_userinfo_in_id_token: bool = True + permission = AuthPermissions.rallly + @classmethod def get_userinfo(cls, user: models_users.CoreUser): return { @@ -318,14 +330,10 @@ class DocumensoAuthClient(BaseAuthClient): allow_pkce_with_client_secret: bool = True - allowed_groups: list[GroupType] | None = [ - GroupType.admin, - GroupType.BDE, - GroupType.eclair, - ] - return_userinfo_in_id_token: bool = True + permission = AuthPermissions.documenso + @classmethod def get_userinfo(cls, user: models_users.CoreUser): return { @@ -344,18 +352,16 @@ class RAIDRegisteringAuthClient(BaseAuthClient): # See app.utils.types.scopes_type.ScopeType for possible values # WARNING: to be able to use openid connect, `ScopeType.openid` should always be allowed allowed_scopes: set[ScopeType | str] = {ScopeType.API} - - allowed_account_types: list[AccountType] | None = None + permission = AuthPermissions.raid_registering class SiarnaqAuthClient(BaseAuthClient): allowed_scopes: set[ScopeType | str] = {ScopeType.API} - - allowed_account_types: list[AccountType] | None = None + permission = AuthPermissions.siarnaq class OverleafAuthClient(BaseAuthClient): - allowed_account_types: list[AccountType] | None = get_ecl_account_types() + permission = AuthPermissions.overleaf @classmethod def get_userinfo(cls, user: models_users.CoreUser): @@ -383,6 +389,8 @@ class PlankaAuthClient(BaseAuthClient): ScopeType.profile, } + permission = AuthPermissions.planka + @classmethod def get_userinfo(cls, user: models_users.CoreUser): return { @@ -394,8 +402,7 @@ def get_userinfo(cls, user: models_users.CoreUser): class SlashAuthClient(BaseAuthClient): - # When set to `None`, users from any group can use the auth client - allowed_account_types: list[AccountType] | None = get_ecl_account_types() + permission = AuthPermissions.slash def get_userinfo(self, user: models_users.CoreUser) -> dict[str, Any]: """ diff --git a/app/utils/initialization.py b/app/utils/initialization.py index e3c4e80d87..3f16a939fa 100644 --- a/app/utils/initialization.py +++ b/app/utils/initialization.py @@ -15,6 +15,8 @@ from app.core.core_endpoints import models_core from app.core.groups import models_groups +from app.core.groups.groups_type import AccountType +from app.core.permissions import models_permissions from app.core.schools import models_schools from app.core.utils.config import Settings from app.types import core_data @@ -46,58 +48,46 @@ def get_sync_db_engine(settings: Settings) -> Engine: ) -def get_all_module_group_visibility_membership_sync( +def create_group_permission_sync( + group_id: str, + permission_name: str, db: Session, -): - """ - Return the every module with their visibility - """ - result = db.execute(select(models_core.ModuleGroupVisibility)) - return result.unique().scalars().all() - - -def get_all_module_account_type_visibility_membership_sync( - db: Session, -): - """ - Return the every module with their visibility - """ - result = db.execute(select(models_core.ModuleAccountTypeVisibility)) - return result.unique().scalars().all() - - -def create_module_group_visibility_sync( - module_visibility: models_core.ModuleGroupVisibility, - db: Session, -) -> models_core.ModuleGroupVisibility: +) -> None: """ - Create a new module visibility in database and return it + Create a new group permission in database """ - db.add(module_visibility) + db.add( + models_permissions.CorePermissionGroup( + group_id=group_id, + permission_name=permission_name, + ), + ) try: db.commit() except IntegrityError: db.rollback() raise - else: - return module_visibility -def create_module_account_type_visibility_sync( - module_visibility: models_core.ModuleAccountTypeVisibility, +def create_account_type_permission_sync( + account_type: AccountType, + permission_name: str, db: Session, -) -> models_core.ModuleAccountTypeVisibility: +) -> None: """ - Create a new module visibility in database and return it + Create a new account type permission in database """ - db.add(module_visibility) + db.add( + models_permissions.CorePermissionAccountType( + account_type=account_type, + permission_name=permission_name, + ), + ) try: db.commit() except IntegrityError: db.rollback() raise - else: - return module_visibility def get_group_by_id_sync(group_id: str, db: Session) -> models_groups.CoreGroup | None: @@ -188,6 +178,20 @@ def create_school_sync( return school +def clean_permissions_sync(db: Session, permssion_list: list[str]) -> None: + """ + Delete all unused permissions in the database + """ + db.execute( + delete(models_permissions.CorePermissionGroup).where( + models_permissions.CorePermissionGroup.permission_name.notin_( + permssion_list, + ), + ), + ) + db.commit() + + def delete_core_data_crud_sync(schema: str, db: Session) -> None: """ Delete core data with schema from database diff --git a/app/utils/tools.py b/app/utils/tools.py index 97d155c07b..e31920104c 100644 --- a/app/utils/tools.py +++ b/app/utils/tools.py @@ -26,7 +26,9 @@ from app.core.core_endpoints import cruds_core, models_core from app.core.groups import cruds_groups from app.core.groups.groups_type import AccountType, GroupType -from app.core.users import cruds_users, models_users +from app.core.permissions import cruds_permissions +from app.core.permissions.type_permissions import ModulePermissions +from app.core.users import cruds_users, models_users, schemas_users from app.core.users.models_users import CoreUser from app.core.utils import security from app.types import core_data @@ -102,7 +104,7 @@ def unaccent(s: str) -> str: def is_user_member_of_any_group( - user: models_users.CoreUser, + user: models_users.CoreUser | schemas_users.CoreUser, allowed_groups: list[str] | list[GroupType], ) -> bool: """ @@ -130,6 +132,28 @@ async def is_user_id_valid(user_id: str, db: AsyncSession) -> bool: return await cruds_users.get_user_by_id(db=db, user_id=user_id) is not None +async def has_user_permission( + user: models_users.CoreUser | schemas_users.CoreUser, + permission_name: ModulePermissions, + db: AsyncSession, +) -> bool: + """ + Check if the user has the permission to perform the action. + """ + if GroupType.admin in [group.id for group in user.groups]: + return True + permissions = await cruds_permissions.get_permissions_by_permission_name( + permission_name=permission_name, + db=db, + ) + return is_user_member_of_any_group( + user, + [perm.group_id for perm in permissions.group_permissions], + ) or user.account_type in [ + perm.account_type for perm in permissions.account_type_permissions + ] + + async def save_file_as_data( upload_file: UploadFile, directory: str, diff --git a/migrations/versions/25-hardcode-eclair-group.py b/migrations/versions/25-hardcode-eclair-group.py index eff6a4d230..c1981ca441 100644 --- a/migrations/versions/25-hardcode-eclair-group.py +++ b/migrations/versions/25-hardcode-eclair-group.py @@ -6,8 +6,6 @@ from collections.abc import Sequence from typing import TYPE_CHECKING -from app.core.groups.groups_type import GroupType - if TYPE_CHECKING: from pytest_alembic import MigrationContext @@ -78,7 +76,7 @@ def upgrade() -> None: return old_eclair_id = res[0][0] - new_eclair_id = GroupType.eclair + new_eclair_id = "1f841bd9-00be-41a7-96e1-860a18a46105" # Hardcoded value deprecated with modular permissions # We don't need to do anything if the group id is already the correct one if old_eclair_id == new_eclair_id: @@ -234,7 +232,7 @@ def test_upgrade( alembic_runner: "MigrationContext", alembic_connection: sa.Connection, ) -> None: - new_eclair_id = GroupType.eclair + new_eclair_id = "1f841bd9-00be-41a7-96e1-860a18a46105" # Hardcoded value deprecated with modular permissions rows = alembic_connection.execute( sa.text("SELECT id from core_group WHERE name = 'eclair'"), diff --git a/migrations/versions/29-hardcode-BDS-group.py b/migrations/versions/29-hardcode-BDS-group.py index 02f7e1397e..f52f8fc0f3 100644 --- a/migrations/versions/29-hardcode-BDS-group.py +++ b/migrations/versions/29-hardcode-BDS-group.py @@ -4,10 +4,10 @@ """ from collections.abc import Sequence +from enum import Enum from typing import TYPE_CHECKING from uuid import uuid4 -from app.core.groups.groups_type import GroupType from app.core.schools.schools_type import SchoolType if TYPE_CHECKING: @@ -23,6 +23,24 @@ depends_on: str | Sequence[str] | None = None +class GroupType(str, Enum): + # Core groups + admin = "0a25cb76-4b63-4fd3-b939-da6d9feabf28" + AE = "45649735-866a-49df-b04b-a13c74fd5886" + + # Module related groups + amap = "70db65ee-d533-4f6b-9ffa-a4d70a17b7ef" + BDE = "53a669d6-84b1-4352-8d7c-421c1fbd9c6a" + CAA = "6c6d7e88-fdb8-4e42-b2b5-3d3cfd12e7d6" + cinema = "ce5f36e6-5377-489f-9696-de70e2477300" + raid_admin = "e9e6e3d3-9f5f-4e9b-8e5f-9f5f4e9b8e5f" + ph = "4ec5ae77-f955-4309-96a5-19cc3c8be71c" + admin_cdr = "c1275229-46b2-4e53-a7c4-305513bb1a2a" + eclair = "1f841bd9-00be-41a7-96e1-860a18a46105" + BDS = "61af3e52-7ef9-4608-823a-39d51e83d1db" + seed_library = "09153d2a-14f4-49a4-be57-5d0f265261b9" + + def upgrade() -> None: t_group = sa.Table( "core_group", diff --git a/migrations/versions/30-membership.py b/migrations/versions/30-membership.py index dc49330e42..ef31b2291e 100644 --- a/migrations/versions/30-membership.py +++ b/migrations/versions/30-membership.py @@ -9,7 +9,6 @@ from enum import Enum from typing import TYPE_CHECKING -from app.core.groups.groups_type import GroupType from app.core.schools.schools_type import SchoolType if TYPE_CHECKING: @@ -82,6 +81,24 @@ class AvailableAssociationMembership(str, Enum): USEECL_ID = uuid.uuid4() +class GroupType(str, Enum): + # Core groups + admin = "0a25cb76-4b63-4fd3-b939-da6d9feabf28" + AE = "45649735-866a-49df-b04b-a13c74fd5886" + + # Module related groups + amap = "70db65ee-d533-4f6b-9ffa-a4d70a17b7ef" + BDE = "53a669d6-84b1-4352-8d7c-421c1fbd9c6a" + CAA = "6c6d7e88-fdb8-4e42-b2b5-3d3cfd12e7d6" + cinema = "ce5f36e6-5377-489f-9696-de70e2477300" + raid_admin = "e9e6e3d3-9f5f-4e9b-8e5f-9f5f4e9b8e5f" + ph = "4ec5ae77-f955-4309-96a5-19cc3c8be71c" + admin_cdr = "c1275229-46b2-4e53-a7c4-305513bb1a2a" + eclair = "1f841bd9-00be-41a7-96e1-860a18a46105" + BDS = "61af3e52-7ef9-4608-823a-39d51e83d1db" + seed_library = "09153d2a-14f4-49a4-be57-5d0f265261b9" + + def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### conn = op.get_bind() diff --git a/migrations/versions/48-permissions.py b/migrations/versions/48-permissions.py new file mode 100644 index 0000000000..4fbf89ae12 --- /dev/null +++ b/migrations/versions/48-permissions.py @@ -0,0 +1,114 @@ +"""permissions + +Create Date: 2025-03-10 21:30:48.890171 +""" + +from collections.abc import Sequence +from enum import Enum +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pytest_alembic import MigrationContext + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = "1051d705419e" +down_revision: str | None = "12ceba87cf" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +class AccountType(str, Enum): + """ + Various account types that can be created in Hyperion. + Each account type is associated with a set of permissions. + """ + + student = "student" + former_student = "former_student" + staff = "staff" + association = "association" + external = "external" + other_school_student = "other_school_student" + demo = "demo" + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "core_permission_group", + sa.Column("permission_name", sa.String(), nullable=False, index=True), + sa.Column("group_id", sa.String(), nullable=False, index=True), + sa.ForeignKeyConstraint(["group_id"], ["core_group.id"]), + sa.PrimaryKeyConstraint("permission_name", "group_id"), + ) + op.create_table( + "core_permission_account_type", + sa.Column("permission_name", sa.String(), nullable=False, index=True), + sa.Column( + "account_type", + postgresql.ENUM( + AccountType, + name="accounttype", + create_type=False, + ), + nullable=False, + index=True, + ), + ) + op.drop_table("campaign_voter_groups") + op.drop_table("module_account_type_visibility") + op.drop_table("module_group_visibility") + op.execute("DELETE FROM core_data WHERE schema='ModuleVisibilityAwareness'") + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "campaign_voter_groups", + sa.Column("group_id", sa.VARCHAR(), autoincrement=False, nullable=False), + sa.PrimaryKeyConstraint("group_id", name="campaign_voter_groups_pkey"), + ) + op.create_table( + "module_account_type_visibility", + sa.Column("root", sa.String(), nullable=False, index=True), + sa.Column( + "allowed_account_type", + postgresql.ENUM( + AccountType, + name="accounttype", + create_type=False, + ), + nullable=False, + index=True, + ), + sa.PrimaryKeyConstraint("root", "allowed_account_type"), + ) + op.create_table( + "module_group_visibility", + sa.Column("root", sa.String(), nullable=False, index=True), + sa.Column("allowed_group_id", sa.String(), nullable=False, index=True), + sa.PrimaryKeyConstraint("root", "allowed_group_id"), + ) + op.drop_table("core_permission_group") + op.drop_table("core_permission_account_type") + op.execute("DELETE FROM core_data WHERE schema='ModuleVisibilityAwareness'") + # ### end Alembic commands ### + + +def pre_test_upgrade( + alembic_runner: "MigrationContext", + alembic_connection: sa.Connection, +) -> None: + pass + + +def test_upgrade( + alembic_runner: "MigrationContext", + alembic_connection: sa.Connection, +) -> None: + pass diff --git a/tests/commons.py b/tests/commons.py index 855d2429b4..93cc64896c 100644 --- a/tests/commons.py +++ b/tests/commons.py @@ -19,6 +19,7 @@ from app.core.payment import cruds_payment, models_payment, schemas_payment from app.core.payment.payment_tool import PaymentTool from app.core.payment.types_payment import HelloAssoConfig, HelloAssoConfigName +from app.core.permissions import cruds_permissions, schemas_permissions from app.core.schools.schools_type import SchoolType from app.core.users import cruds_users, models_users, schemas_users from app.core.utils import security @@ -164,8 +165,73 @@ def init_test_payment_tools() -> dict[HelloAssoConfigName, PaymentTool]: TEST_PASSWORD_HASH = security.get_password_hash(get_random_string()) +async def add_account_type_permission( + permission: str, + account_type: AccountType, +): + async with TestingSessionLocal() as db: + try: + await cruds_permissions.create_account_type_permission( + db=db, + permission=schemas_permissions.CoreAccountTypePermission( + permission_name=permission, + account_type=account_type, + ), + ) + await db.commit() + except Exception as error: + await db.rollback() + raise FailedToAddObjectToDB from error + finally: + await db.close() + + +async def create_groups_with_permissions( + permissions: list[str], + group_name: str, +) -> models_groups.CoreGroup: + """ + Add a dummy group to the database + Group property will be randomly generated if not provided + + The group will be added to provided `permissions` + """ + + group_id = str(uuid.uuid4()) + + group = models_groups.CoreGroup( + id=group_id, + name=group_name, + description=None, + ) + + async with TestingSessionLocal() as db: + try: + await cruds_groups.create_group(db=db, group=group) + + for permission in permissions: + await cruds_permissions.create_group_permission( + db=db, + permission=schemas_permissions.CoreGroupPermission( + permission_name=permission, + group_id=group_id, + ), + ) + await db.commit() + except Exception as error: + await db.rollback() + raise FailedToAddObjectToDB from error + finally: + await db.close() + async with TestingSessionLocal() as db: + group_db = await cruds_groups.get_group_by_id(db, group_id) + await db.close() + + return group_db # type: ignore # (group_db can't be None) # noqa: PGH003 + + async def create_user_with_groups( - groups: list[GroupType], + groups: list[GroupType | str], account_type: AccountType = AccountType.student, school_id: SchoolType | uuid.UUID = SchoolType.centrale_lyon, user_id: str | None = None, @@ -213,7 +279,7 @@ async def create_user_with_groups( await cruds_groups.create_membership( db=db, membership=models_groups.CoreMembership( - group_id=group.value, + group_id=group.value if isinstance(group, GroupType) else group, user_id=user_id, description=None, ), diff --git a/tests/config.test.yaml b/tests/config.test.yaml index fe325c6fca..8dd68b8298 100644 --- a/tests/config.test.yaml +++ b/tests/config.test.yaml @@ -91,31 +91,21 @@ AUTH_CLIENTS: redirect_uri: - http://127.0.0.1:8000/docs auth_client: "AppAuthClient" - BaseAuthClient: - secret: secret - redirect_uri: - - http://127.0.0.1:8000/docs - auth_client: BaseAuthClient - RalllyAuthClient: - secret: secret - redirect_uri: - - http://127.0.0.1:8000/docs - auth_client: RalllyAuthClient - SynapseAuthClient: - secret: secret + GroupPermissionAuthClient: + secret: "secret" redirect_uri: - http://127.0.0.1:8000/docs - auth_client: SynapseAuthClient - AcceptingOnlyECLUsersAuthClient: - secret: secret + auth_client: "AppAuthClient" + AccountTypePermissionAuthClient: + secret: "secret" redirect_uri: - - http://127.0.0.1:8000/docs - auth_client: NextcloudAuthClient - RestrictingUsersGroupsAuthClient: - secret: secret + - "http://127.0.0.1:8000/docs" + auth_client: "RalllyAuthClient" + TokenIntrospectionAuthClient: + secret: "secret" redirect_uri: - - http://127.0.0.1:8000/docs - auth_client: DocumensoAuthClient + - "http://127.0.0.1:8000/docs" + auth_client: "SynapseAuthClient" ##################### # Hyperion settings # diff --git a/tests/core/test_auth.py b/tests/core/test_auth.py index b2e0ab8f84..b09257850a 100644 --- a/tests/core/test_auth.py +++ b/tests/core/test_auth.py @@ -7,14 +7,20 @@ from fastapi.testclient import TestClient from app.core.auth import models_auth -from app.core.groups.groups_type import AccountType, GroupType +from app.core.groups import models_groups +from app.core.groups.groups_type import AccountType +from app.core.permissions import models_permissions from app.core.users import models_users +from app.utils.auth.providers import AuthPermissions from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) +group: models_groups.CoreGroup + user: models_users.CoreUser external_user: models_users.CoreUser ecl_user: models_users.CoreUser @@ -27,6 +33,22 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: + global group + group = await create_groups_with_permissions([AuthPermissions.app], "auth_group") + + await add_object_to_db( + models_permissions.CorePermissionAccountType( + account_type=AccountType.student, + permission_name=AuthPermissions.rallly.name, + ), + ) + await add_object_to_db( + models_permissions.CorePermissionAccountType( + account_type=AccountType.student, + permission_name=AuthPermissions.app.name, + ), + ) + global user user = await create_user_with_groups( groups=[], @@ -36,7 +58,7 @@ async def init_objects() -> None: global ecl_user ecl_user = await create_user_with_groups( - groups=[GroupType.eclair], + groups=[group.id], email="email@etu.ec-lyon.fr", password="azerty", ) @@ -274,7 +296,7 @@ def test_authorization_code_flow_secret(client: TestClient) -> None: def test_get_user_info(client: TestClient) -> None: # We first need an access token to query user info endpoints # data = { - "client_id": "BaseAuthClient", + "client_id": "AccountTypePermissionAuthClient", "client_secret": "secret", "redirect_uri": "http://127.0.0.1:8000/docs", "response_type": "code", @@ -298,7 +320,7 @@ def test_get_user_info(client: TestClient) -> None: "grant_type": "authorization_code", "code": code, "redirect_uri": "http://127.0.0.1:8000/docs", - "client_id": "BaseAuthClient", + "client_id": "AccountTypePermissionAuthClient", "client_secret": "secret", } @@ -314,16 +336,16 @@ def test_get_user_info(client: TestClient) -> None: headers={"Authorization": f"Bearer {access_token}"}, ) - assert response.status_code == 200 + assert response.status_code == 200, response.text json = response.json() - assert json["name"] == user.firstname + assert json["name"] == user.full_name def test_get_user_info_in_id_token(client: TestClient) -> None: # We first need an access token to query user info endpoints # data = { - "client_id": "RalllyAuthClient", + "client_id": "AccountTypePermissionAuthClient", "client_secret": "secret", "redirect_uri": "http://127.0.0.1:8000/docs", "response_type": "code", @@ -347,7 +369,7 @@ def test_get_user_info_in_id_token(client: TestClient) -> None: "grant_type": "authorization_code", "code": code, "redirect_uri": "http://127.0.0.1:8000/docs", - "client_id": "RalllyAuthClient", + "client_id": "AccountTypePermissionAuthClient", "client_secret": "secret", } @@ -469,12 +491,12 @@ def test_authorization_code_flow_with_invalid_user_credentials( # Valid user response -def test_authorization_code_flow_with_auth_client_restricting_allowed_groups_and_user_member_of_an_allowed_group( +def test_authorization_code_flow_with_group_permission_and_user_member_of_an_allowed_group( client: TestClient, ) -> None: # For an user that is a member of a required group # data_with_invalid_client_id = { - "client_id": "RestrictingUsersGroupsAuthClient", + "client_id": "GroupPermissionAuthClient", "client_secret": "secret", "redirect_uri": "http://127.0.0.1:8000/docs", "response_type": "code", @@ -496,12 +518,12 @@ def test_authorization_code_flow_with_auth_client_restricting_allowed_groups_and assert query["code"][0] != "" -def test_authorization_code_flow_with_auth_client_restricting_allowed_groups_and_user_not_member_of_an_allowed_group( +def test_authorization_code_flow_with_group_permission_and_user_not_member_of_an_allowed_group( client: TestClient, ) -> None: # For an user that is not a member of a required group # data_with_invalid_client_id = { - "client_id": "RestrictingUsersGroupsAuthClient", + "client_id": "GroupPermissionAuthClient", "client_secret": "secret", "redirect_uri": "http://127.0.0.1:8000/docs", "response_type": "code", @@ -523,12 +545,12 @@ def test_authorization_code_flow_with_auth_client_restricting_allowed_groups_and ) -def test_authorization_code_flow_with_auth_client_restricting_external_users_and_user_external( +def test_authorization_code_flow_with_account_type_permission_and_wrong_account_type( client: TestClient, ) -> None: # For an user that is not a member of a required group # data_with_invalid_client_id = { - "client_id": "RalllyAuthClient", + "client_id": "AccountTypePermissionAuthClient", "client_secret": "secret", "redirect_uri": "http://127.0.0.1:8000/docs", "response_type": "code", @@ -546,7 +568,7 @@ def test_authorization_code_flow_with_auth_client_restricting_external_users_and assert response.next_request is not None assert str(response.next_request.url).endswith( - "calypsso/message?type=user_account_type_not_allowed", + "calypsso/message?type=user_not_member_of_allowed_group", ) @@ -583,7 +605,7 @@ def test_token_introspection_with_invalid_client_secret( client: TestClient, ): data = { - "client_id": "BaseAuthClient", + "client_id": "TokenIntrospectionAuthClient", "client_secret": "invalid_secret", "token": access_token, } @@ -598,7 +620,7 @@ def test_token_introspection_with_access_token( client: TestClient, ): data = { - "client_id": "SynapseAuthClient", + "client_id": "TokenIntrospectionAuthClient", "client_secret": "secret", "token": access_token, } @@ -618,7 +640,7 @@ def test_token_introspection_with_access_token_and_auth_in_header( data = { "token": access_token, } - client_id = "SynapseAuthClient" + client_id = "TokenIntrospectionAuthClient" client_secret = "secret" basic_header = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode() response = client.post( @@ -640,7 +662,7 @@ def test_token_introspection_with_an_expired_access_token( expires_delta=timedelta(seconds=-1), ) data = { - "client_id": "SynapseAuthClient", + "client_id": "TokenIntrospectionAuthClient", "client_secret": "secret", "token": expired_access_token, } @@ -658,7 +680,7 @@ def test_token_introspection_with_invalid_refresh_token( client: TestClient, ): data = { - "client_id": "SynapseAuthClient", + "client_id": "TokenIntrospectionAuthClient", "client_secret": "secret", "token": "InvalidRefreshToken", } @@ -676,7 +698,7 @@ def test_token_introspection_with_valid_refresh_token( client: TestClient, ): data = { - "client_id": "SynapseAuthClient", + "client_id": "TokenIntrospectionAuthClient", "client_secret": "secret", "token": valid_refresh_token, } @@ -694,7 +716,7 @@ def test_token_introspection_with_expired_refresh_token( client: TestClient, ): data = { - "client_id": "SynapseAuthClient", + "client_id": "TokenIntrospectionAuthClient", "client_secret": "secret", "token": expired_refresh_token, } @@ -712,7 +734,7 @@ def test_token_introspection_with_revoked_refresh_token( client: TestClient, ): data = { - "client_id": "SynapseAuthClient", + "client_id": "TokenIntrospectionAuthClient", "client_secret": "secret", "token": revoked_refresh_token, } diff --git a/tests/core/test_core.py b/tests/core/test_core.py index 78b2253815..1f8bf28304 100644 --- a/tests/core/test_core.py +++ b/tests/core/test_core.py @@ -1,94 +1,5 @@ -import pytest_asyncio from fastapi.testclient import TestClient -from app.core.core_endpoints import models_core -from app.core.groups.groups_type import AccountType, GroupType -from app.core.users import models_users -from tests.commons import ( - add_object_to_db, - create_api_access_token, - create_user_with_groups, -) - -simple_user: models_users.CoreUser -admin_user: models_users.CoreUser -token_simple: str -token_admin: str -root = "root" -group_id = "random id" - - -@pytest_asyncio.fixture(scope="module", autouse=True) -async def init_objects() -> None: - global admin_user - admin_user = await create_user_with_groups([GroupType.admin]) - global token_admin - token_admin = create_api_access_token(admin_user) - user_simple = await create_user_with_groups([GroupType.AE]) - global token_simple - token_simple = create_api_access_token(user_simple) - module_visibility = models_core.ModuleGroupVisibility( - root=root, - allowed_group_id=group_id, - ) - await add_object_to_db(module_visibility) - - -def test_get_module_visibility(client: TestClient) -> None: - response = client.get( - "/module-visibility", - headers={"Authorization": f"Bearer {token_admin}"}, - ) - assert response.status_code == 200 - - -def test_get_my_module_visibility(client: TestClient) -> None: - response = client.get( - "/module-visibility/me", - headers={"Authorization": f"Bearer {token_simple}"}, - ) - assert response.status_code == 200 - - -def test_add_group_module_visibility(client: TestClient) -> None: - response = client.post( - "/module-visibility/", - json={ - "root": "root", - "allowed_group_id": GroupType.AE.value, - }, - headers={"Authorization": f"Bearer {token_admin}"}, - ) - assert response.status_code == 201 - - -def test_delete_group_visibility(client: TestClient) -> None: - response = client.delete( - f"/module-visibility/{root}/groups/{group_id}", - headers={"Authorization": f"Bearer {token_admin}"}, - ) - assert response.status_code == 204 - - -def test_add_account_type_module_visibility(client: TestClient) -> None: - response = client.post( - "/module-visibility/", - json={ - "root": "root", - "allowed_account_type": AccountType.demo.value, - }, - headers={"Authorization": f"Bearer {token_admin}"}, - ) - assert response.status_code == 201 - - -def test_delete_account_type_visibility(client: TestClient) -> None: - response = client.delete( - f"/module-visibility/{root}/account-types/{AccountType.demo.value}", - headers={"Authorization": f"Bearer {token_admin}"}, - ) - assert response.status_code == 204 - def test_get_information(client: TestClient) -> None: response = client.get( diff --git a/tests/core/test_dependencies.py b/tests/core/test_dependencies.py index 6b01d6fb1d..5dbf59b034 100644 --- a/tests/core/test_dependencies.py +++ b/tests/core/test_dependencies.py @@ -3,13 +3,18 @@ from fastapi import HTTPException from fastapi.testclient import TestClient +from app.core.groups import models_groups from app.core.groups.groups_type import AccountType, GroupType from app.core.users import models_users from app.dependencies import is_user from tests.commons import ( + create_groups_with_permissions, create_user_with_groups, ) +group1: models_groups.CoreGroup +group2: models_groups.CoreGroup + admin_user: models_users.CoreUser user_with_needed_account_type: models_users.CoreUser user_with_restricted_account_type: models_users.CoreUser @@ -21,6 +26,8 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: global \ + group1, \ + group2, \ admin_user, \ user_with_restricted_account_type, \ user_with_needed_account_type, \ @@ -28,6 +35,15 @@ async def init_objects() -> None: user_with_restricted_group, \ user_with_needed_group + group1 = await create_groups_with_permissions( + [], + "random group", + ) + group2 = await create_groups_with_permissions( + [], + "random group2", + ) + admin_user = await create_user_with_groups( [GroupType.admin], ) @@ -43,33 +59,33 @@ async def init_objects() -> None: ) user_with_restricted_group = await create_user_with_groups( - [GroupType.amap], + [group1.id], ) - user_with_needed_group = await create_user_with_groups([GroupType.eclair]) + user_with_needed_group = await create_user_with_groups([group2.id]) def test_exclude_access_on_group( client: TestClient, ) -> None: user = is_user( - excluded_groups=[GroupType.amap], + excluded_groups=[group1.id], )(admin_user) assert user == admin_user user = is_user( - excluded_groups=[GroupType.amap], + excluded_groups=[group1.id], )(user_external) assert user == user_external user = is_user( - excluded_groups=[GroupType.amap], + excluded_groups=[group1.id], )(user_with_restricted_account_type) assert user == user_with_restricted_account_type user = is_user( - excluded_groups=[GroupType.amap], + excluded_groups=[group1.id], )(user_with_needed_account_type) assert user == user_with_needed_account_type user = is_user( - excluded_groups=[GroupType.amap], + excluded_groups=[group1.id], )(user_with_needed_group) assert user == user_with_needed_group with pytest.raises( @@ -77,7 +93,7 @@ def test_exclude_access_on_group( match="Unauthorized, user is a member of any of the groups ", ): is_user( - excluded_groups=[GroupType.amap], + excluded_groups=[group1.id], )(user_with_restricted_group) @@ -85,11 +101,11 @@ def test_restrict_access_on_group( client: TestClient, ) -> None: user = is_user( - included_groups=[GroupType.eclair], + included_groups=[group2.id], )(admin_user) assert user == admin_user user = is_user( - included_groups=[GroupType.eclair], + included_groups=[group2.id], )(user_with_needed_group) assert user == user_with_needed_group with pytest.raises( @@ -97,28 +113,28 @@ def test_restrict_access_on_group( match="Unauthorized, user is not a member of an allowed group", ): is_user( - included_groups=[GroupType.eclair], + included_groups=[group2.id], )(user_with_restricted_group) with pytest.raises( HTTPException, match="Unauthorized, user is not a member of an allowed group", ): is_user( - included_groups=[GroupType.eclair], + included_groups=[group2.id], )(user_external) with pytest.raises( HTTPException, match="Unauthorized, user is not a member of an allowed group", ): is_user( - included_groups=[GroupType.eclair], + included_groups=[group2.id], )(user_with_needed_account_type) with pytest.raises( HTTPException, match="Unauthorized, user is not a member of an allowed group", ): is_user( - included_groups=[GroupType.eclair], + included_groups=[group2.id], )(user_with_restricted_account_type) diff --git a/tests/core/test_memberships.py b/tests/core/test_memberships.py index ff6c04ff3c..2b3459bf7c 100644 --- a/tests/core/test_memberships.py +++ b/tests/core/test_memberships.py @@ -4,15 +4,22 @@ import pytest_asyncio from fastapi.testclient import TestClient +from app.core.groups import models_groups from app.core.groups.groups_type import GroupType from app.core.memberships import models_memberships from app.core.users import models_users from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) +bde_group: models_groups.CoreGroup +bds_group: models_groups.CoreGroup +dummy_group_1: models_groups.CoreGroup +dummy_group_2: models_groups.CoreGroup + user: models_users.CoreUser admin_user: models_users.CoreUser bde_user: models_users.CoreUser @@ -28,13 +35,31 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects(): + global bde_group, bds_group, dummy_group_1, dummy_group_2 + bde_group = await create_groups_with_permissions( + [], + "BDE Group", + ) + bds_group = await create_groups_with_permissions( + [], + "BDS Group", + ) + dummy_group_1 = await create_groups_with_permissions( + [], + "Dummy Group 1", + ) + dummy_group_2 = await create_groups_with_permissions( + [], + "Dummy Group 2", + ) + global user, admin_user, bde_user user = await create_user_with_groups([]) admin_user = await create_user_with_groups( [GroupType.admin], ) bde_user = await create_user_with_groups( - [GroupType.BDE], + [bde_group.id], ) global token_user, token_admin, token_bde @@ -46,13 +71,13 @@ async def init_objects(): aeecl_association_membership = models_memberships.CoreAssociationMembership( id=uuid.uuid4(), name="AEECL", - manager_group_id=GroupType.BDE, + manager_group_id=bde_group.id, ) await add_object_to_db(aeecl_association_membership) useecl_association_membership = models_memberships.CoreAssociationMembership( id=uuid.uuid4(), name="USEECL", - manager_group_id=GroupType.BDS, + manager_group_id=bds_group.id, ) await add_object_to_db(useecl_association_membership) @@ -91,7 +116,7 @@ def test_create_association_membership_user(client: TestClient): "/memberships", json={ "name": "Random Association", - "manager_group_id": GroupType.AE, + "manager_group_id": dummy_group_1.id, }, headers={"Authorization": f"Bearer {token_user}"}, ) @@ -103,7 +128,7 @@ def test_create_association_membership_admin(client: TestClient): "/memberships", json={ "name": "Random Association", - "manager_group_id": GroupType.AE, + "manager_group_id": dummy_group_1.id, }, headers={"Authorization": f"Bearer {token_admin}"}, ) @@ -164,7 +189,7 @@ async def test_delete_association_membership_admin(client: TestClient): new_membership = models_memberships.CoreAssociationMembership( id=uuid.uuid4(), name="Random Association1", - manager_group_id=GroupType.AE, + manager_group_id=dummy_group_1.id, ) await add_object_to_db(new_membership) @@ -187,7 +212,7 @@ def test_patch_association_membership_user(client: TestClient): f"/memberships/{aeecl_association_membership.id}", json={ "name": "Random Association", - "manager_group_id": GroupType.eclair.value, + "manager_group_id": dummy_group_2.id, }, headers={"Authorization": f"Bearer {token_user}"}, ) @@ -208,12 +233,12 @@ async def test_patch_association_membership_admin(client: TestClient): new_membership = models_memberships.CoreAssociationMembership( id=uuid.uuid4(), name="Random Association2", - manager_group_id=GroupType.AE, + manager_group_id=dummy_group_1.id, ) await add_object_to_db(new_membership) new_name = "Random Association3" - new_group_id = GroupType.eclair.value + new_group_id = dummy_group_2.id response = client.patch( f"/memberships/{new_membership.id}", json={ diff --git a/tests/core/test_myeclpay.py b/tests/core/test_myeclpay.py index 9679fd47aa..c9205a05fa 100644 --- a/tests/core/test_myeclpay.py +++ b/tests/core/test_myeclpay.py @@ -11,6 +11,7 @@ from fastapi.testclient import TestClient from pytest_mock import MockerFixture +from app.core.groups import models_groups from app.core.groups.groups_type import GroupType from app.core.memberships import models_memberships from app.core.myeclpay import cruds_myeclpay, models_myeclpay @@ -31,10 +32,13 @@ add_coredata_to_db, add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, get_TestingSessionLocal, ) +bde_group: models_groups.CoreGroup + admin_user: models_users.CoreUser admin_user_token: str structure_manager_user: models_users.CoreUser @@ -99,6 +103,12 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: + global bde_group + bde_group = await create_groups_with_permissions( + [], + "BDE Group", + ) + global admin_user, admin_user_token admin_user = await create_user_with_groups(groups=[GroupType.admin]) admin_user_token = create_api_access_token(admin_user) @@ -107,7 +117,7 @@ async def init_objects() -> None: association_membership = models_memberships.CoreAssociationMembership( id=uuid4(), name="Test Association Membership", - manager_group_id=GroupType.BDE, + manager_group_id=bde_group.id, ) await add_object_to_db(association_membership) @@ -235,7 +245,7 @@ async def init_objects() -> None: firstname="firstname", name="ECL User 2", nickname="nickname", - groups=[GroupType.BDE], + groups=[bde_group.id], ) ecl_user2_access_token = create_api_access_token(ecl_user2) diff --git a/tests/core/test_payment.py b/tests/core/test_payment.py index 8cd1ee1483..9f3756b3a6 100644 --- a/tests/core/test_payment.py +++ b/tests/core/test_payment.py @@ -306,6 +306,7 @@ async def test_webhook_payment_callback( default_allowed_groups_ids=[], payment_callback=callback, factory=None, + permissions=None, ) mocker.patch( "app.core.payment.endpoints_payment.all_modules", @@ -348,6 +349,7 @@ async def test_webhook_payment_callback_fail( default_allowed_groups_ids=[], payment_callback=callback, factory=None, + permissions=None, ) mocker.patch( "app.core.payment.endpoints_payment.all_modules", diff --git a/tests/core/test_user_fusion.py b/tests/core/test_user_fusion.py index 6d71e38b48..c7ff9af558 100644 --- a/tests/core/test_user_fusion.py +++ b/tests/core/test_user_fusion.py @@ -4,6 +4,7 @@ import pytest_asyncio from fastapi.testclient import TestClient +from app.core.groups import models_groups from app.core.groups.groups_type import GroupType from app.core.memberships import models_memberships from app.core.users import models_users @@ -13,6 +14,9 @@ create_user_with_groups, ) +group1: models_groups.CoreGroup +group2: models_groups.CoreGroup + admin_user: models_users.CoreUser student_user_to_delete: models_users.CoreUser student_user_to_keep: models_users.CoreUser @@ -31,17 +35,31 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: + global group1, group2 + group1 = models_groups.CoreGroup( + id=str(uuid4()), + name="BDE", + description=None, + ) + await add_object_to_db(group1) + group2 = models_groups.CoreGroup( + id=str(uuid4()), + name="CAA", + description=None, + ) + await add_object_to_db(group2) + global admin_user, student_user_to_delete, student_user_to_keep admin_user = await create_user_with_groups( - [GroupType.admin, GroupType.admin_cdr], + [GroupType.admin], ) student_user_to_keep = await create_user_with_groups( - [GroupType.BDE], + [group1.id], email=FABRISTPP_EMAIL_1, ) student_user_to_delete = await create_user_with_groups( - [GroupType.BDE, GroupType.CAA], + [group1.id, group2.id], email=FABRISTPP_EMAIL_2, ) @@ -49,7 +67,7 @@ async def init_objects() -> None: core_association_membership = models_memberships.CoreAssociationMembership( id=uuid4(), name="AEECL", - manager_group_id=GroupType.BDE, + manager_group_id=group1.id, ) await add_object_to_db(core_association_membership) @@ -114,7 +132,7 @@ def test_fusion_users(client: TestClient) -> None: assert response.status_code == 200 response = client.get( - f"/groups/{GroupType.CAA.value}", + f"/groups/{group1.id}", headers={"Authorization": f"Bearer {token_admin_user}"}, ) assert response.status_code == 200 diff --git a/tests/core/test_users.py b/tests/core/test_users.py index bdc2fd68ce..0a711a95c8 100644 --- a/tests/core/test_users.py +++ b/tests/core/test_users.py @@ -6,15 +6,19 @@ from fastapi.testclient import TestClient from pytest_mock import MockerFixture +from app.core.groups import models_groups from app.core.groups.groups_type import AccountType, GroupType from app.core.schools.schools_type import SchoolType from app.core.users import models_users from app.dependencies import is_user from tests.commons import ( create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) +group_amap: models_groups.CoreGroup + admin_user: models_users.CoreUser student_user: models_users.CoreUser external_user: models_users.CoreUser @@ -35,6 +39,12 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: + global group_amap + group_amap = await create_groups_with_permissions( + permissions=["manage_amap"], + group_name="AMAP", + ) + global admin_user, student_user, student_user_with_old_email, external_user admin_user = await create_user_with_groups( @@ -54,7 +64,7 @@ async def init_objects() -> None: ) global user_with_group - user_with_group = await create_user_with_groups([GroupType.amap]) + user_with_group = await create_user_with_groups([group_amap.id]) global token_admin_user token_admin_user = create_api_access_token(admin_user) @@ -116,7 +126,7 @@ def test_restrict_access_on_group(client: TestClient) -> None: match="Unauthorized, user is a member of any of the groups ", ): is_user( - excluded_groups=[GroupType.amap], + excluded_groups=[group_amap.id], )(user_with_group) diff --git a/tests/modules/cdr/test_cdr.py b/tests/modules/cdr/test_cdr.py index f31bd7d77a..a0b04b4a2f 100644 --- a/tests/modules/cdr/test_cdr.py +++ b/tests/modules/cdr/test_cdr.py @@ -5,11 +5,13 @@ from fastapi.testclient import TestClient from pytest_mock import MockerFixture +from app.core.groups import models_groups from app.core.groups.groups_type import GroupType from app.core.memberships import models_memberships from app.core.users import models_users from app.modules.cdr import models_cdr from app.modules.cdr.coredata_cdr import CdrYear +from app.modules.cdr.endpoints_cdr import CdrPermissions from app.modules.cdr.types_cdr import ( CdrStatus, DocumentSignatureType, @@ -19,15 +21,21 @@ add_coredata_to_db, add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, mocked_checkout_id, ) year = datetime.now(UTC).year -cdr_admin: models_users.CoreUser -cdr_bde: models_users.CoreUser -cdr_user: models_users.CoreUser +admin_group: models_groups.CoreGroup +seller_group: models_groups.CoreGroup +online_seller_group: models_groups.CoreGroup +empty_seller_group: models_groups.CoreGroup + +user_admin: models_users.CoreUser +user_seller: models_users.CoreUser +user: models_users.CoreUser token_admin: str token_bde: str @@ -78,36 +86,54 @@ async def init_objects(): await add_coredata_to_db(CdrYear(year=year)) - global cdr_admin - cdr_admin = await create_user_with_groups( - [GroupType.admin_cdr], - email="cdr_admin@etu.ec-lyon.fr", + global admin_group, seller_group, online_seller_group, empty_seller_group + admin_group = await create_groups_with_permissions( + [CdrPermissions.manage_cdr], + "cdr_admin", + ) + seller_group = await create_groups_with_permissions( + [], + "BDE", + ) + online_seller_group = await create_groups_with_permissions( + [], + "CAA", + ) + empty_seller_group = await create_groups_with_permissions( + [], + "cinema", + ) + + global user_admin + user_admin = await create_user_with_groups( + [admin_group.id], + email="user_admin@etu.ec-lyon.fr", ) global token_admin - token_admin = create_api_access_token(cdr_admin) + token_admin = create_api_access_token(user_admin) - global cdr_bde - cdr_bde = await create_user_with_groups( - [GroupType.BDE], + global user_seller + user_seller = await create_user_with_groups( + [seller_group.id], ) global token_bde - token_bde = create_api_access_token(cdr_bde) + token_bde = create_api_access_token(user_seller) - global cdr_user - cdr_user = await create_user_with_groups( + global user + user = await create_user_with_groups( [], ) global token_user - token_user = create_api_access_token(cdr_user) + token_user = create_api_access_token(user) global seller seller = models_cdr.Seller( id=uuid.uuid4(), name="BDE", - group_id=str(GroupType.BDE.value), + group_id=str(seller_group.id), order=5, ) await add_object_to_db(seller) @@ -116,7 +142,7 @@ async def init_objects(): online_seller = models_cdr.Seller( id=uuid.uuid4(), name="CAA", - group_id=str(GroupType.CAA.value), + group_id=str(online_seller_group.id), order=12, ) await add_object_to_db(online_seller) @@ -125,7 +151,7 @@ async def init_objects(): empty_seller = models_cdr.Seller( id=uuid.uuid4(), name="Seller Vide", - group_id=str(GroupType.cinema.value), + group_id=str(empty_seller_group.id), order=99, ) await add_object_to_db(empty_seller) @@ -297,7 +323,7 @@ async def init_objects(): global purchase purchase = models_cdr.Purchase( - user_id=cdr_user.id, + user_id=user.id, product_variant_id=variant.id, quantity=1, validated=False, @@ -307,7 +333,7 @@ async def init_objects(): global signature signature = models_cdr.Signature( - user_id=cdr_user.id, + user_id=user.id, document_id=document.id, signature_type=DocumentSignatureType.numeric, numeric_signature_id="somedocumensoid", @@ -317,7 +343,7 @@ async def init_objects(): global payment payment = models_cdr.Payment( id=uuid.uuid4(), - user_id=cdr_user.id, + user_id=user.id, total=5000, payment_type=PaymentType.cash, year=year, @@ -328,14 +354,14 @@ async def init_objects(): association_membership = models_memberships.CoreAssociationMembership( id=uuid.uuid4(), name="AEECL", - manager_group_id=GroupType.BDE, + manager_group_id=admin_group.id, ) await add_object_to_db(association_membership) global user_membership user_membership = models_memberships.CoreAssociationUserMembership( id=uuid.uuid4(), - user_id=cdr_user.id, + user_id=user.id, association_membership_id=association_membership.id, start_date=date(2022, 9, 1), end_date=date(2026, 9, 1), @@ -384,7 +410,7 @@ async def init_objects(): product_variant_id=ticket_variant.id, generator_id=ticket_generator.id, name="Ticket", - user_id=cdr_user.id, + user_id=user.id, scan_left=1, tags="", expiration=datetime.now(UTC) + timedelta(days=1), @@ -398,7 +424,7 @@ def test_get_all_cdr_users_seller(client: TestClient): headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 200 - assert str(cdr_user.id) in [x["id"] for x in response.json()] + assert str(user.id) in [x["id"] for x in response.json()] def test_get_all_cdr_users_user(client: TestClient): @@ -431,7 +457,7 @@ def test_get_all_cdr_pending_users_user(client: TestClient): def test_update_cdr_user_seller(client: TestClient): response = client.patch( - f"/cdr/users/{cdr_user.id}", + f"/cdr/users/{user.id}", json={ "nickname": "surnom", "promo": 2023, @@ -446,7 +472,7 @@ def test_update_cdr_user_seller(client: TestClient): def test_update_cdr_user_seller_with_non_ecl_email(client: TestClient): response = client.patch( - f"/cdr/users/{cdr_user.id}", + f"/cdr/users/{user.id}", json={"email": "some.email@test.fr"}, headers={"Authorization": f"Bearer {token_admin}"}, ) @@ -456,8 +482,8 @@ def test_update_cdr_user_seller_with_non_ecl_email(client: TestClient): def test_update_cdr_user_seller_with_already_existing_email(client: TestClient): response = client.patch( - f"/cdr/users/{cdr_user.id}", - json={"email": "cdr_admin@etu.ec-lyon.fr"}, + f"/cdr/users/{user.id}", + json={"email": "user_admin@etu.ec-lyon.fr"}, headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 400 @@ -466,7 +492,7 @@ def test_update_cdr_user_seller_with_already_existing_email(client: TestClient): def test_update_cdr_user_seller_email(client: TestClient): response = client.patch( - f"/cdr/users/{cdr_user.id}", + f"/cdr/users/{user.id}", json={"email": "some.email@etu.ec-lyon.fr"}, headers={"Authorization": f"Bearer {token_admin}"}, ) @@ -484,7 +510,7 @@ def test_update_cdr_user_seller_wrong_user(client: TestClient): def test_update_cdr_user_user(client: TestClient): response = client.patch( - f"/cdr/users/{cdr_user.id}", + f"/cdr/users/{user.id}", json={"nickname": "surnom"}, headers={"Authorization": f"Bearer {token_user}"}, ) @@ -493,11 +519,11 @@ def test_update_cdr_user_user(client: TestClient): def test_get_cdr_user(client: TestClient): response = client.get( - f"/cdr/users/{cdr_user.id}", + f"/cdr/users/{user.id}", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 200 - assert str(cdr_user.id) == response.json()["id"] + assert str(user.id) == response.json()["id"] def test_get_all_sellers_admin(client: TestClient): @@ -555,12 +581,16 @@ def test_get_online_sellers(client: TestClient): assert str(seller.id) not in [x["id"] for x in response.json()] -def test_create_seller_admin(client: TestClient): +async def test_create_seller_admin(client: TestClient): + group = await create_groups_with_permissions( + [], + "ph", + ) response = client.post( "/cdr/sellers/", json={ "name": "Seller créé", - "group_id": str(GroupType.ph.value), + "group_id": group.id, "order": 1, }, headers={"Authorization": f"Bearer {token_admin}"}, @@ -582,7 +612,7 @@ def test_create_seller_not_admin(client: TestClient): "/cdr/sellers/", json={ "name": "Seller créé", - "group_id": str(GroupType.cinema.value), + "group_id": GroupType.admin.value, "order": 1, }, headers={"Authorization": f"Bearer {token_bde}"}, @@ -1232,7 +1262,7 @@ def test_delete_document_seller(client: TestClient): def test_get_purchases_by_user_id_user(client: TestClient): response = client.get( - f"/cdr/users/{cdr_user.id}/purchases/", + f"/cdr/users/{user.id}/purchases/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 200 @@ -1243,7 +1273,7 @@ def test_get_purchases_by_user_id_user(client: TestClient): def test_get_purchases_by_user_id_wrong_user(client: TestClient): response = client.get( - f"/cdr/users/{cdr_bde.id}/purchases/", + f"/cdr/users/{user_seller.id}/purchases/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 403 @@ -1251,7 +1281,7 @@ def test_get_purchases_by_user_id_wrong_user(client: TestClient): def test_get_purchases_by_user_id_other_user_purchase(client: TestClient): response = client.get( - f"/cdr/users/{cdr_bde.id}/purchases/", + f"/cdr/users/{user_seller.id}/purchases/", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 200 @@ -1284,7 +1314,7 @@ def test_get_all_my_purchases(client: TestClient): def test_get_purchases_by_user_id_by_seller_id_user(client: TestClient): response = client.get( - f"/cdr/sellers/{seller.id}/users/{cdr_user.id}/purchases/", + f"/cdr/sellers/{seller.id}/users/{user.id}/purchases/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 200 @@ -1295,7 +1325,7 @@ def test_get_purchases_by_user_id_by_seller_id_user(client: TestClient): def test_get_purchases_by_user_id_by_seller_id_other_user(client: TestClient): response = client.get( - f"/cdr/sellers/{seller.id}/users/{cdr_bde.id}/purchases/", + f"/cdr/sellers/{seller.id}/users/{user_seller.id}/purchases/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 403 @@ -1303,7 +1333,7 @@ def test_get_purchases_by_user_id_by_seller_id_other_user(client: TestClient): def test_get_purchases_by_user_id_by_seller_id_seller(client: TestClient): response = client.get( - f"/cdr/sellers/{seller.id}/users/{cdr_user.id}/purchases/", + f"/cdr/sellers/{seller.id}/users/{user.id}/purchases/", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 200 @@ -1316,7 +1346,7 @@ def test_get_purchases_by_user_id_by_seller_id_other_seller_purchase( client: TestClient, ): response = client.get( - f"/cdr/sellers/{online_seller.id}/users/{cdr_user.id}/purchases/", + f"/cdr/sellers/{online_seller.id}/users/{user.id}/purchases/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 200 @@ -1327,7 +1357,7 @@ def test_get_purchases_by_user_id_by_seller_id_other_seller_purchase( def test_get_purchases_by_user_id_by_seller_id_other_user_purchase(client: TestClient): response = client.get( - f"/cdr/sellers/{seller.id}/users/{cdr_bde.id}/purchases/", + f"/cdr/sellers/{seller.id}/users/{user_seller.id}/purchases/", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 200 @@ -1338,7 +1368,7 @@ def test_get_purchases_by_user_id_by_seller_id_other_user_purchase(client: TestC def test_create_purchase_cdr_not_started(client: TestClient): response = client.post( - f"/cdr/users/{cdr_admin.id}/purchases/{variant.id}/", + f"/cdr/users/{user_admin.id}/purchases/{variant.id}/", json={ "quantity": 1, }, @@ -1349,7 +1379,7 @@ def test_create_purchase_cdr_not_started(client: TestClient): def test_patch_purchase_cdr_not_started(client: TestClient): response = client.post( - f"/cdr/users/{cdr_admin.id}/purchases/{variant.id}/", + f"/cdr/users/{user_admin.id}/purchases/{variant.id}/", json={ "quantity": 2, }, @@ -1360,7 +1390,7 @@ def test_patch_purchase_cdr_not_started(client: TestClient): def test_create_signature_cdr_not_started(client: TestClient): response = client.post( - f"/cdr/users/{cdr_admin.id}/signatures/{document.id}/", + f"/cdr/users/{user_admin.id}/signatures/{document.id}/", json={ "signature_type": DocumentSignatureType.material, }, @@ -1371,7 +1401,7 @@ def test_create_signature_cdr_not_started(client: TestClient): def test_create_payment_not_started(client: TestClient): response = client.post( - f"/cdr/users/{cdr_user.id}/payments/", + f"/cdr/users/{user.id}/payments/", json={ "total": 12345, "payment_type": PaymentType.card, @@ -1433,7 +1463,7 @@ def test_change_status_admin_onsite(client: TestClient): def test_update_cdr_user_seller_onsite(client: TestClient): response = client.patch( - f"/cdr/users/{cdr_user.id}", + f"/cdr/users/{user.id}", json={"nickname": "surnom_onsite"}, headers={"Authorization": f"Bearer {token_admin}"}, ) @@ -1485,7 +1515,7 @@ def test_delete_product_variant_cdr_started(client: TestClient): def test_create_purchase_user(client: TestClient): response = client.post( - f"/cdr/users/{cdr_admin.id}/purchases/{variant.id}/", + f"/cdr/users/{user_admin.id}/purchases/{variant.id}/", json={ "quantity": 1, }, @@ -1494,7 +1524,7 @@ def test_create_purchase_user(client: TestClient): assert response.status_code == 403 response = client.get( - f"/cdr/users/{cdr_admin.id}/purchases/", + f"/cdr/users/{user_admin.id}/purchases/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -1503,7 +1533,7 @@ def test_create_purchase_user(client: TestClient): def test_create_purchase_seller(client: TestClient): response = client.post( - f"/cdr/users/{cdr_admin.id}/purchases/{variant.id}/", + f"/cdr/users/{user_admin.id}/purchases/{variant.id}/", json={ "quantity": 1, }, @@ -1512,7 +1542,7 @@ def test_create_purchase_seller(client: TestClient): assert response.status_code == 201 response = client.get( - f"/cdr/users/{cdr_admin.id}/purchases/", + f"/cdr/users/{user_admin.id}/purchases/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -1521,7 +1551,7 @@ def test_create_purchase_seller(client: TestClient): def test_create_purchase_wrong_id(client: TestClient): response = client.post( - f"/cdr/users/{cdr_admin.id}/purchases/{uuid.uuid4()}/", + f"/cdr/users/{user_admin.id}/purchases/{uuid.uuid4()}/", json={ "quantity": 1, }, @@ -1532,7 +1562,7 @@ def test_create_purchase_wrong_id(client: TestClient): def test_create_purchase_other_user(client: TestClient): response = client.post( - f"/cdr/users/{cdr_admin.id}/purchases/{uuid.uuid4()}/", + f"/cdr/users/{user_admin.id}/purchases/{uuid.uuid4()}/", json={ "quantity": 1, }, @@ -1543,7 +1573,7 @@ def test_create_purchase_other_user(client: TestClient): def test_patch_purchase_seller(client: TestClient): response = client.post( - f"/cdr/users/{cdr_admin.id}/purchases/{variant.id}/", + f"/cdr/users/{user_admin.id}/purchases/{variant.id}/", json={ "quantity": 2, }, @@ -1552,7 +1582,7 @@ def test_patch_purchase_seller(client: TestClient): assert response.status_code == 201 response = client.get( - f"/cdr/users/{cdr_admin.id}/purchases/", + f"/cdr/users/{user_admin.id}/purchases/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -1564,7 +1594,7 @@ def test_patch_purchase_seller(client: TestClient): def test_patch_purchase_wrong_purchase(client: TestClient): response = client.post( - f"/cdr/users/{cdr_admin.id}/purchases/{variant.id}/", + f"/cdr/users/{user_admin.id}/purchases/{variant.id}/", json={ "erstrdyfgu": 2, }, @@ -1575,7 +1605,7 @@ def test_patch_purchase_wrong_purchase(client: TestClient): def test_patch_purchase_wrong_purchase_id(client: TestClient): response = client.post( - f"/cdr/users/{cdr_admin.id}/purchases/{empty_variant.id}/", + f"/cdr/users/{user_admin.id}/purchases/{empty_variant.id}/", json={ "quantity": 2, }, @@ -1586,7 +1616,7 @@ def test_patch_purchase_wrong_purchase_id(client: TestClient): def test_patch_purchase_user(client: TestClient): response = client.post( - f"/cdr/users/{cdr_admin.id}/purchases/{variant.id}/", + f"/cdr/users/{user_admin.id}/purchases/{variant.id}/", json={ "quantity": 2, }, @@ -1597,7 +1627,7 @@ def test_patch_purchase_user(client: TestClient): def test_create_signature_user(client: TestClient): response = client.post( - f"/cdr/users/{cdr_user.id}/signatures/{document.id}/", + f"/cdr/users/{user.id}/signatures/{document.id}/", json={ "signature_type": DocumentSignatureType.material, }, @@ -1606,7 +1636,7 @@ def test_create_signature_user(client: TestClient): assert response.status_code == 403 response = client.get( - f"/cdr/users/{cdr_admin.id}/signatures/", + f"/cdr/users/{user_admin.id}/signatures/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -1615,7 +1645,7 @@ def test_create_signature_user(client: TestClient): def test_create_signature_seller(client: TestClient): response = client.post( - f"/cdr/users/{cdr_admin.id}/signatures/{document.id}/", + f"/cdr/users/{user_admin.id}/signatures/{document.id}/", json={ "signature_type": DocumentSignatureType.material, }, @@ -1624,7 +1654,7 @@ def test_create_signature_seller(client: TestClient): assert response.status_code == 201 response = client.get( - f"/cdr/users/{cdr_admin.id}/signatures/", + f"/cdr/users/{user_admin.id}/signatures/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -1633,13 +1663,13 @@ def test_create_signature_seller(client: TestClient): def test_validate_purchase_seller(client: TestClient): response = client.patch( - f"/cdr/users/{cdr_admin.id}/purchases/{variant.id}/validated/?validated=True", + f"/cdr/users/{user_admin.id}/purchases/{variant.id}/validated/?validated=True", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 403 response = client.get( - f"/cdr/users/{cdr_admin.id}/purchases/", + f"/cdr/users/{user_admin.id}/purchases/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -1651,13 +1681,13 @@ def test_validate_purchase_seller(client: TestClient): def test_validate_purchase_admin(client: TestClient): response = client.patch( - f"/cdr/users/{cdr_admin.id}/purchases/{variant.id}/validated/?validated=True", + f"/cdr/users/{user_admin.id}/purchases/{variant.id}/validated/?validated=True", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 204 response = client.get( - f"/cdr/users/{cdr_admin.id}/purchases/", + f"/cdr/users/{user_admin.id}/purchases/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -1669,13 +1699,13 @@ def test_validate_purchase_admin(client: TestClient): def test_delete_purchase_validates(client: TestClient): response = client.delete( - f"/cdr/users/{cdr_admin.id}/purchases/{variant.id}/", + f"/cdr/users/{user_admin.id}/purchases/{variant.id}/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 403 response = client.get( - f"/cdr/users/{cdr_admin.id}/purchases/", + f"/cdr/users/{user_admin.id}/purchases/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -1684,13 +1714,13 @@ def test_delete_purchase_validates(client: TestClient): def test_unvalidate_purchase(client: TestClient): response = client.patch( - f"/cdr/users/{cdr_admin.id}/purchases/{variant.id}/validated/?validated=False", + f"/cdr/users/{user_admin.id}/purchases/{variant.id}/validated/?validated=False", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 204 response = client.get( - f"/cdr/users/{cdr_admin.id}/purchases/", + f"/cdr/users/{user_admin.id}/purchases/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -1702,13 +1732,13 @@ def test_unvalidate_purchase(client: TestClient): def test_delete_purchase_user(client: TestClient): response = client.delete( - f"/cdr/users/{cdr_admin.id}/purchases/{variant.id}/", + f"/cdr/users/{user_admin.id}/purchases/{variant.id}/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 403 response = client.get( - f"/cdr/users/{cdr_admin.id}/purchases/", + f"/cdr/users/{user_admin.id}/purchases/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -1717,13 +1747,13 @@ def test_delete_purchase_user(client: TestClient): def test_delete_purchase_admin(client: TestClient): response = client.delete( - f"/cdr/users/{cdr_admin.id}/purchases/{variant.id}/", + f"/cdr/users/{user_admin.id}/purchases/{variant.id}/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 204 response = client.get( - f"/cdr/users/{cdr_admin.id}/purchases/", + f"/cdr/users/{user_admin.id}/purchases/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -1732,7 +1762,7 @@ def test_delete_purchase_admin(client: TestClient): def test_delete_purchase_wrong_variant(client: TestClient): response = client.delete( - f"/cdr/users/{cdr_admin.id}/purchases/{uuid.uuid4()}/", + f"/cdr/users/{user_admin.id}/purchases/{uuid.uuid4()}/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 404 @@ -1740,7 +1770,7 @@ def test_delete_purchase_wrong_variant(client: TestClient): def test_get_signatures_by_user_id_user(client: TestClient): response = client.get( - f"/cdr/users/{cdr_user.id}/signatures/", + f"/cdr/users/{user.id}/signatures/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 200 @@ -1749,7 +1779,7 @@ def test_get_signatures_by_user_id_user(client: TestClient): def test_get_signatures_by_user_id_other_user(client: TestClient): response = client.get( - f"/cdr/users/{cdr_user.id}/signatures/", + f"/cdr/users/{user.id}/signatures/", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 403 @@ -1757,7 +1787,7 @@ def test_get_signatures_by_user_id_other_user(client: TestClient): def test_get_signatures_by_user_id_admin(client: TestClient): response = client.get( - f"/cdr/users/{cdr_user.id}/signatures/", + f"/cdr/users/{user.id}/signatures/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -1766,7 +1796,7 @@ def test_get_signatures_by_user_id_admin(client: TestClient): def test_get_signatures_by_user_id_other_signature(client: TestClient): response = client.get( - f"/cdr/users/{cdr_bde.id}/signatures/", + f"/cdr/users/{user_seller.id}/signatures/", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 200 @@ -1775,7 +1805,7 @@ def test_get_signatures_by_user_id_other_signature(client: TestClient): def test_get_signatures_by_user_id_by_seller_id_user(client: TestClient): response = client.get( - f"/cdr/sellers/{seller.id}/users/{cdr_user.id}/signatures/", + f"/cdr/sellers/{seller.id}/users/{user.id}/signatures/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 200 @@ -1784,7 +1814,7 @@ def test_get_signatures_by_user_id_by_seller_id_user(client: TestClient): def test_get_signatures_by_user_id_by_seller_id_seller(client: TestClient): response = client.get( - f"/cdr/sellers/{seller.id}/users/{cdr_user.id}/signatures/", + f"/cdr/sellers/{seller.id}/users/{user.id}/signatures/", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 200 @@ -1793,7 +1823,7 @@ def test_get_signatures_by_user_id_by_seller_id_seller(client: TestClient): def test_get_signatures_by_user_id_by_seller_id_other_seller(client: TestClient): response = client.get( - f"/cdr/sellers/{online_seller.id}/users/{cdr_user.id}/signatures/", + f"/cdr/sellers/{online_seller.id}/users/{user.id}/signatures/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 200 @@ -1802,7 +1832,7 @@ def test_get_signatures_by_user_id_by_seller_id_other_seller(client: TestClient) def test_get_signatures_by_user_id_by_seller_id_other_user(client: TestClient): response = client.get( - f"/cdr/sellers/{seller.id}/users/{cdr_bde.id}/signatures/", + f"/cdr/sellers/{seller.id}/users/{user_seller.id}/signatures/", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 200 @@ -1811,7 +1841,7 @@ def test_get_signatures_by_user_id_by_seller_id_other_user(client: TestClient): def test_create_signature_wrong_document(client: TestClient): response = client.post( - f"/cdr/users/{cdr_admin.id}/signatures/{uuid.uuid4()}/", + f"/cdr/users/{user_admin.id}/signatures/{uuid.uuid4()}/", json={ "signature_type": DocumentSignatureType.material, }, @@ -1822,7 +1852,7 @@ def test_create_signature_wrong_document(client: TestClient): def test_create_signature_numeric_no_id(client: TestClient): response = client.post( - f"/cdr/users/{cdr_admin.id}/signatures/{document.id}/", + f"/cdr/users/{user_admin.id}/signatures/{document.id}/", json={ "signature_type": DocumentSignatureType.numeric, }, @@ -1833,13 +1863,13 @@ def test_create_signature_numeric_no_id(client: TestClient): def test_delete_signature_not_admin(client: TestClient): response = client.delete( - f"/cdr/users/{cdr_admin.id}/signatures/{document.id}/", + f"/cdr/users/{user_admin.id}/signatures/{document.id}/", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 403 response = client.get( - f"/cdr/users/{cdr_admin.id}/signatures/", + f"/cdr/users/{user_admin.id}/signatures/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -1848,13 +1878,13 @@ def test_delete_signature_not_admin(client: TestClient): def test_delete_signature_admin(client: TestClient): response = client.delete( - f"/cdr/users/{cdr_admin.id}/signatures/{document.id}/", + f"/cdr/users/{user_admin.id}/signatures/{document.id}/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 204 response = client.get( - f"/cdr/users/{cdr_admin.id}/signatures/", + f"/cdr/users/{user_admin.id}/signatures/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -1863,7 +1893,7 @@ def test_delete_signature_admin(client: TestClient): def test_delete_signature_wrong_signature(client: TestClient): response = client.delete( - f"/cdr/users/{cdr_admin.id}/signatures/{uuid.uuid4()}/", + f"/cdr/users/{user_admin.id}/signatures/{uuid.uuid4()}/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 404 @@ -1956,7 +1986,7 @@ def test_delete_curriculum_wrong_id(client: TestClient): def test_create_curriculum_membership_wrong_user(client: TestClient): response = client.post( - f"/cdr/users/{cdr_user.id!s}/curriculums/{curriculum.id!s}/", + f"/cdr/users/{user.id!s}/curriculums/{curriculum.id!s}/", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 403 @@ -1964,7 +1994,7 @@ def test_create_curriculum_membership_wrong_user(client: TestClient): def test_create_curriculum_membership_user(client: TestClient): response = client.post( - f"/cdr/users/{cdr_user.id!s}/curriculums/{curriculum.id!s}/", + f"/cdr/users/{user.id!s}/curriculums/{curriculum.id!s}/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 201 @@ -1972,7 +2002,7 @@ def test_create_curriculum_membership_user(client: TestClient): def test_delete_curriculum_membership_wrong_user(client: TestClient): response = client.delete( - f"/cdr/users/{cdr_user.id!s}/curriculums/{curriculum.id!s}/", + f"/cdr/users/{user.id!s}/curriculums/{curriculum.id!s}/", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 403 @@ -1980,7 +2010,7 @@ def test_delete_curriculum_membership_wrong_user(client: TestClient): def test_delete_curriculum_membership_user(client: TestClient): response = client.delete( - f"/cdr/users/{cdr_user.id!s}/curriculums/{curriculum.id!s}/", + f"/cdr/users/{user.id!s}/curriculums/{curriculum.id!s}/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 204 @@ -1988,7 +2018,7 @@ def test_delete_curriculum_membership_user(client: TestClient): def test_delete_curriculum_membership_wrong_curriculum(client: TestClient): response = client.delete( - f"/cdr/users/{cdr_user.id!s}/curriculums/{uuid.uuid4()}/", + f"/cdr/users/{user.id!s}/curriculums/{uuid.uuid4()}/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 404 @@ -1996,7 +2026,7 @@ def test_delete_curriculum_membership_wrong_curriculum(client: TestClient): def test_get_payments_by_user_id_user(client: TestClient): response = client.get( - f"/cdr/users/{cdr_user.id}/payments/", + f"/cdr/users/{user.id}/payments/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 200 @@ -2005,7 +2035,7 @@ def test_get_payments_by_user_id_user(client: TestClient): def test_get_payments_by_user_id_wrong_user(client: TestClient): response = client.get( - f"/cdr/users/{cdr_bde.id}/payments/", + f"/cdr/users/{user_seller.id}/payments/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 403 @@ -2013,7 +2043,7 @@ def test_get_payments_by_user_id_wrong_user(client: TestClient): def test_get_payments_by_user_id_other_user_payment(client: TestClient): response = client.get( - f"/cdr/users/{cdr_bde.id}/payments/", + f"/cdr/users/{user_seller.id}/payments/", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 200 @@ -2022,7 +2052,7 @@ def test_get_payments_by_user_id_other_user_payment(client: TestClient): def test_create_payment_not_admin(client: TestClient): response = client.post( - f"/cdr/users/{cdr_admin.id}/payments/", + f"/cdr/users/{user_admin.id}/payments/", json={ "total": 12345, "payment_type": PaymentType.card, @@ -2032,7 +2062,7 @@ def test_create_payment_not_admin(client: TestClient): assert response.status_code == 403 response = client.get( - f"/cdr/users/{cdr_user.id}/payments/", + f"/cdr/users/{user.id}/payments/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -2041,7 +2071,7 @@ def test_create_payment_not_admin(client: TestClient): def test_create_payment_admin(client: TestClient): response = client.post( - f"/cdr/users/{cdr_user.id}/payments/", + f"/cdr/users/{user.id}/payments/", json={ "total": 12345, "payment_type": PaymentType.card, @@ -2052,7 +2082,7 @@ def test_create_payment_admin(client: TestClient): payment_id = uuid.UUID(response.json()["id"]) response = client.get( - f"/cdr/users/{cdr_user.id}/payments/", + f"/cdr/users/{user.id}/payments/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -2061,13 +2091,13 @@ def test_create_payment_admin(client: TestClient): def test_delete_payment_not_admin(client: TestClient): response = client.delete( - f"/cdr/users/{cdr_user.id}/payments/{payment.id}/", + f"/cdr/users/{user.id}/payments/{payment.id}/", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 403 response = client.get( - f"/cdr/users/{cdr_user.id}/payments/", + f"/cdr/users/{user.id}/payments/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -2076,7 +2106,7 @@ def test_delete_payment_not_admin(client: TestClient): def test_delete_payment_wrong_id(client: TestClient): response = client.delete( - f"/cdr/users/{cdr_user.id}/payments/{uuid.uuid4()}/", + f"/cdr/users/{user.id}/payments/{uuid.uuid4()}/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 404 @@ -2084,7 +2114,7 @@ def test_delete_payment_wrong_id(client: TestClient): def test_delete_payment_wrong_user(client: TestClient): response = client.delete( - f"/cdr/users/{cdr_bde.id}/payments/{payment.id}/", + f"/cdr/users/{user_seller.id}/payments/{payment.id}/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 403 @@ -2092,13 +2122,13 @@ def test_delete_payment_wrong_user(client: TestClient): def test_delete_payment_admin(client: TestClient): response = client.delete( - f"/cdr/users/{cdr_user.id}/payments/{payment.id}/", + f"/cdr/users/{user.id}/payments/{payment.id}/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 204 response = client.get( - f"/cdr/users/{cdr_user.id}/payments/", + f"/cdr/users/{user.id}/payments/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -2252,7 +2282,7 @@ async def test_pay(mocker: MockerFixture, client: TestClient): ) await add_object_to_db(variant_new) purchase_bde = models_cdr.Purchase( - user_id=cdr_bde.id, + user_id=user_seller.id, product_variant_id=variant_new.id, quantity=5, validated=False, @@ -2283,7 +2313,7 @@ async def test_pay(mocker: MockerFixture, client: TestClient): def test_get_user_tickets(client: TestClient): response = client.get( - f"/cdr/users/{cdr_user.id}/tickets/", + f"/cdr/users/{user.id}/tickets/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 200 @@ -2293,7 +2323,7 @@ def test_get_user_tickets(client: TestClient): def test_get_user_tickets_other_user(client: TestClient): response = client.get( - f"/cdr/users/{cdr_user.id}/tickets/", + f"/cdr/users/{user.id}/tickets/", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 403 @@ -2408,7 +2438,7 @@ async def test_scan_ticket_no_scan_left(client: TestClient): generator_id=ticket_generator.id, name="Ticket", product_variant_id=ticket_variant.id, - user_id=cdr_user.id, + user_id=user.id, scan_left=0, tags="", expiration=datetime.now(UTC) + timedelta(days=1), @@ -2430,7 +2460,7 @@ async def test_scan_ticket_expired(client: TestClient): name="Ticket", generator_id=ticket_generator.id, product_variant_id=ticket_variant.id, - user_id=cdr_user.id, + user_id=user.id, scan_left=1, tags="", expiration=datetime.now(UTC) - timedelta(days=1), @@ -2458,7 +2488,7 @@ def test_get_ticket_list(client: TestClient): f"/cdr/sellers/{seller.id}/products/{ticket_product.id}/tickets/{ticket_generator.id}/lists/Bus 2/", headers={"Authorization": f"Bearer {token_bde}"}, ) - assert str(cdr_user.id) in [user["id"] for user in response.json()] + assert str(user.id) in [user["id"] for user in response.json()] async def test_validate_purchase(client: TestClient): @@ -2522,7 +2552,7 @@ async def test_validate_purchase(client: TestClient): ) await add_object_to_db(purchase_product_constraint) purchase = models_cdr.Purchase( - user_id=cdr_user.id, + user_id=user.id, product_variant_id=variant_purchased.id, quantity=2, validated=False, @@ -2530,7 +2560,7 @@ async def test_validate_purchase(client: TestClient): ) await add_object_to_db(purchase) purchase_to_validate = models_cdr.Purchase( - user_id=cdr_user.id, + user_id=user.id, product_variant_id=variant_to_validate.id, quantity=2, validated=False, @@ -2539,7 +2569,7 @@ async def test_validate_purchase(client: TestClient): await add_object_to_db(purchase_to_validate) membership = models_memberships.CoreAssociationUserMembership( id=uuid.uuid4(), - user_id=cdr_user.id, + user_id=user.id, association_membership_id=association_membership.id, start_date=date(2022, 9, 1), end_date=datetime.now(UTC).date() + timedelta(days=100), @@ -2547,25 +2577,25 @@ async def test_validate_purchase(client: TestClient): await add_object_to_db(membership) response = client.patch( - f"/cdr/users/{cdr_user.id}/purchases/{variant_to_validate.id}/validated/?validated=True", + f"/cdr/users/{user.id}/purchases/{variant_to_validate.id}/validated/?validated=True", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 204 response = client.delete( - f"/cdr/users/{cdr_user.id}/purchases/{variant_purchased.id}/", + f"/cdr/users/{user.id}/purchases/{variant_purchased.id}/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 403 response = client.patch( - f"/cdr/users/{cdr_user.id}/purchases/{variant_to_validate.id}/validated/?validated=False", + f"/cdr/users/{user.id}/purchases/{variant_to_validate.id}/validated/?validated=False", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 204 response = client.delete( - f"/cdr/users/{cdr_user.id}/purchases/{variant_purchased.id}/", + f"/cdr/users/{user.id}/purchases/{variant_purchased.id}/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 204 @@ -2620,7 +2650,7 @@ async def test_create_customdata(client: TestClient): ) await add_object_to_db(customdata_field) response = client.post( - f"/cdr/sellers/{seller.id}/products/{product.id}/users/{cdr_user.id}/data/{customdata_field.id}/", + f"/cdr/sellers/{seller.id}/products/{product.id}/users/{user.id}/data/{customdata_field.id}/", json={"value": "ABCD"}, headers={"Authorization": f"Bearer {token_bde}"}, ) @@ -2636,7 +2666,7 @@ async def test_create_customdata_user(client: TestClient): ) await add_object_to_db(customdata_field) response = client.post( - f"/cdr/sellers/{seller.id}/products/{product.id}/users/{cdr_user.id}/data/{customdata_field.id}/", + f"/cdr/sellers/{seller.id}/products/{product.id}/users/{user.id}/data/{customdata_field.id}/", json={"value": "ABCD"}, headers={"Authorization": f"Bearer {token_user}"}, ) @@ -2653,20 +2683,20 @@ async def test_update_customdata(client: TestClient): await add_object_to_db(field) customdata = models_cdr.CustomData( field_id=field.id, - user_id=cdr_user.id, + user_id=user.id, value="Edit", ) await add_object_to_db(customdata) response = client.patch( - f"/cdr/sellers/{seller.id}/products/{product.id}/users/{cdr_user.id}/data/{field.id}/", + f"/cdr/sellers/{seller.id}/products/{product.id}/users/{user.id}/data/{field.id}/", json={"value": "ABCD"}, headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 204 response = client.get( - f"/cdr/sellers/{seller.id}/products/{product.id}/users/{cdr_user.id}/data/{field.id}/", + f"/cdr/sellers/{seller.id}/products/{product.id}/users/{user.id}/data/{field.id}/", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 200 @@ -2683,13 +2713,13 @@ async def test_update_customdata_user(client: TestClient): await add_object_to_db(field) customdata = models_cdr.CustomData( field_id=field.id, - user_id=cdr_user.id, + user_id=user.id, value="Edit", ) await add_object_to_db(customdata) response = client.patch( - f"/cdr/sellers/{seller.id}/products/{product.id}/users/{cdr_user.id}/data/{field.id}/", + f"/cdr/sellers/{seller.id}/products/{product.id}/users/{user.id}/data/{field.id}/", json={"value": "ABCD"}, headers={"Authorization": f"Bearer {token_user}"}, ) @@ -2706,19 +2736,19 @@ async def test_delete_customdata(client: TestClient): await add_object_to_db(field) customdata = models_cdr.CustomData( field_id=field.id, - user_id=cdr_user.id, + user_id=user.id, value="Edit", ) await add_object_to_db(customdata) response = client.delete( - f"/cdr/sellers/{seller.id}/products/{product.id}/users/{cdr_user.id}/data/{field.id}/", + f"/cdr/sellers/{seller.id}/products/{product.id}/users/{user.id}/data/{field.id}/", headers={"Authorization": f"Bearer {token_user}"}, ) assert response.status_code == 403 response = client.delete( - f"/cdr/sellers/{seller.id}/products/{product.id}/users/{cdr_user.id}/data/{field.id}/", + f"/cdr/sellers/{seller.id}/products/{product.id}/users/{user.id}/data/{field.id}/", headers={"Authorization": f"Bearer {token_bde}"}, ) assert response.status_code == 204 @@ -2734,19 +2764,19 @@ async def test_customdata_deletion_on_purchase_deletion(client: TestClient): await add_object_to_db(field) customdata = models_cdr.CustomData( field_id=field.id, - user_id=cdr_user.id, + user_id=user.id, value="Edit", ) await add_object_to_db(customdata) response = client.delete( - f"/cdr/users/{cdr_user.id}/purchases/{variant.id}/", + f"/cdr/users/{user.id}/purchases/{variant.id}/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 204 response = client.get( - f"/cdr/sellers/{seller.id}/products/{product.id}/users/{cdr_user.id}/data/{field.id}/", + f"/cdr/sellers/{seller.id}/products/{product.id}/users/{user.id}/data/{field.id}/", headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 404 diff --git a/tests/modules/cdr/test_cdr_result.py b/tests/modules/cdr/test_cdr_result.py index bb7fdd39d6..48c0ff7596 100644 --- a/tests/modules/cdr/test_cdr_result.py +++ b/tests/modules/cdr/test_cdr_result.py @@ -4,15 +4,21 @@ import pytest_asyncio -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups from app.core.users import models_users from app.modules.cdr import models_cdr +from app.modules.cdr.endpoints_cdr import CdrPermissions from app.modules.cdr.utils_cdr import construct_dataframe_from_users_purchases from tests.commons import ( create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) +admin_group: models_groups.CoreGroup +seller1_group: models_groups.CoreGroup +seller2_group: models_groups.CoreGroup + cdr_admin: models_users.CoreUser cdr_user1: models_users.CoreUser cdr_user2: models_users.CoreUser @@ -52,9 +58,23 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects(): + global admin_group, seller1_group, seller2_group + admin_group = await create_groups_with_permissions( + [CdrPermissions.manage_cdr], + "cdr_admin", + ) + seller1_group = await create_groups_with_permissions( + [], + "BDE", + ) + seller2_group = await create_groups_with_permissions( + [], + "CAA", + ) + global cdr_admin cdr_admin = await create_user_with_groups( - [GroupType.admin_cdr], + [admin_group.id], email="cdr_admin@etu.ec-lyon.fr", ) @@ -86,7 +106,7 @@ async def init_objects(): seller1 = models_cdr.Seller( id=uuid.uuid4(), name="BDE", - group_id=str(GroupType.BDE.value), + group_id=str(seller1_group.id), order=1, ) @@ -94,7 +114,7 @@ async def init_objects(): seller2 = models_cdr.Seller( id=uuid.uuid4(), name="CAA", - group_id=str(GroupType.CAA.value), + group_id=str(seller2_group.id), order=2, ) diff --git a/tests/modules/sport_competition/test_purchases.py b/tests/modules/sport_competition/test_purchases.py index 453e948598..7353997091 100644 --- a/tests/modules/sport_competition/test_purchases.py +++ b/tests/modules/sport_competition/test_purchases.py @@ -15,6 +15,9 @@ models_sport_competition, schemas_sport_competition, ) +from app.modules.sport_competition.permissions_sport_competition import ( + SportCompetitionPermissions, +) from app.modules.sport_competition.types_sport_competition import ( ProductPublicType, ProductSchoolType, @@ -23,6 +26,7 @@ from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, get_TestingSessionLocal, mocked_checkout_id, @@ -129,6 +133,12 @@ def variants(): @pytest_asyncio.fixture(scope="module", autouse=True) async def setup(): + global admin_group + admin_group = await create_groups_with_permissions( + [SportCompetitionPermissions.manage_sport_competition], + "competition_admin_group", + ) + global school_from_lyon, school_others school_from_lyon = models_schools.CoreSchool( @@ -176,7 +186,7 @@ async def setup(): user_fanfare, \ user_multiple admin_user = await create_user_with_groups( - [GroupType.competition_admin], + [admin_group.id], email="Admin User", ) user_from_lyon = await create_user_with_groups( diff --git a/tests/modules/sport_competition/test_sport_inscription.py b/tests/modules/sport_competition/test_sport_inscription.py index a9994c8584..478cf2e6a0 100644 --- a/tests/modules/sport_competition/test_sport_inscription.py +++ b/tests/modules/sport_competition/test_sport_inscription.py @@ -5,11 +5,15 @@ from fastapi.testclient import TestClient from sqlalchemy import delete, update -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups +from app.core.groups.groups_type import AccountType, GroupType from app.core.schools import models_schools from app.core.schools.schools_type import SchoolType from app.core.users import models_users from app.modules.sport_competition import models_sport_competition +from app.modules.sport_competition.permissions_sport_competition import ( + SportCompetitionPermissions, +) from app.modules.sport_competition.schemas_sport_competition import ( LocationBase, MatchBase, @@ -25,12 +29,16 @@ SportCategory, ) from tests.commons import ( + add_account_type_permission, add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, get_TestingSessionLocal, ) +admin_group: models_groups.CoreGroup + school1: models_schools.CoreSchool school2: models_schools.CoreSchool @@ -108,6 +116,17 @@ async def create_competition_user( @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: + await add_account_type_permission( + SportCompetitionPermissions.volunteer_sport_competition, + AccountType.student, + ) + + global admin_group + admin_group = await create_groups_with_permissions( + [SportCompetitionPermissions.manage_sport_competition], + "competition_admin_group", + ) + global school1, school2, active_edition, old_edition school1 = models_schools.CoreSchool( id=uuid4(), @@ -144,7 +163,7 @@ async def init_objects() -> None: global admin_user, school_bds_user, sport_manager_user, user3, user4 admin_user = await create_user_with_groups( - [GroupType.competition_admin], + [admin_group.id], email="Admin User", ) school_bds_user = await create_user_with_groups( diff --git a/tests/modules/sport_competition/test_validation.py b/tests/modules/sport_competition/test_validation.py index 2cb84981fa..e91fe421b0 100644 --- a/tests/modules/sport_competition/test_validation.py +++ b/tests/modules/sport_competition/test_validation.py @@ -6,13 +6,16 @@ from fastapi.testclient import TestClient from sqlalchemy import update -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups from app.core.schools import models_schools from app.core.users import cruds_users, models_users, schemas_users from app.modules.sport_competition import ( cruds_sport_competition, models_sport_competition, ) +from app.modules.sport_competition.permissions_sport_competition import ( + SportCompetitionPermissions, +) from app.modules.sport_competition.types_sport_competition import ( ProductPublicType, ProductSchoolType, @@ -21,10 +24,13 @@ from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, get_TestingSessionLocal, ) +admin_group: models_groups.CoreGroup + school_sport_quota: models_schools.CoreSchool school_simple_general_quota: models_schools.CoreSchool school_athlete_general_quota: models_schools.CoreSchool @@ -142,6 +148,12 @@ async def create_competition_user( @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: + global admin_group + admin_group = await create_groups_with_permissions( + [SportCompetitionPermissions.manage_sport_competition], + "competition_admin_group", + ) + global \ school_sport_quota, \ school_simple_general_quota, \ @@ -272,7 +284,7 @@ async def init_objects() -> None: competition_user_athlete_fanfare admin_user = await create_user_with_groups( - [GroupType.admin, GroupType.competition_admin], + [admin_group.id], ) admin_token = create_api_access_token(admin_user) competition_user_admin = models_sport_competition.CompetitionUser( diff --git a/tests/modules/test_advert.py b/tests/modules/test_advert.py index b2f3ae35ce..b69d6e6087 100644 --- a/tests/modules/test_advert.py +++ b/tests/modules/test_advert.py @@ -5,20 +5,27 @@ import pytest_asyncio from fastapi.testclient import TestClient -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups from app.core.users import models_users from app.modules.advert import models_advert +from app.modules.advert.endpoints_advert import AdvertPermissions from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) +admin_group: models_groups.CoreGroup +advertiser_group: models_groups.CoreGroup + advert: models_advert.Advert advertiser: models_advert.Advertiser + user_admin: models_users.CoreUser user_advertiser: models_users.CoreUser user_simple: models_users.CoreUser + token_admin: str = "" token_advertiser: str = "" token_simple: str = "" @@ -26,8 +33,18 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: + global admin_group, advertiser_group + admin_group = await create_groups_with_permissions( + [AdvertPermissions.manage_advertisers], + "advert_admin", + ) + advertiser_group = await create_groups_with_permissions( + [], + "CAA advertiser", + ) + global user_admin - user_admin = await create_user_with_groups([GroupType.admin]) + user_admin = await create_user_with_groups([admin_group.id]) global token_admin token_admin = create_api_access_token(user_admin) @@ -37,12 +54,12 @@ async def init_objects() -> None: advertiser = models_advert.Advertiser( id=str(uuid.uuid4()), name="CAA", - group_manager_id=GroupType.CAA.value, + group_manager_id=advertiser_group.id, ) await add_object_to_db(advertiser) user_advertiser = await create_user_with_groups( - [GroupType.CAA], + [advertiser_group.id], ) global token_advertiser diff --git a/tests/modules/test_amap.py b/tests/modules/test_amap.py index 4619e94b19..4109e8257d 100644 --- a/tests/modules/test_amap.py +++ b/tests/modules/test_amap.py @@ -4,17 +4,20 @@ import pytest_asyncio from fastapi.testclient import TestClient -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups from app.core.users import models_users from app.modules.amap import models_amap +from app.modules.amap.endpoints_amap import AmapPermissions from app.modules.amap.types_amap import AmapSlotType, DeliveryStatusType from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) -amap_user: models_users.CoreUser +admin_group: models_groups.CoreGroup +admin_user: models_users.CoreUser student_user: models_users.CoreUser product: models_amap.Product deletable_product: models_amap.Product @@ -28,7 +31,8 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: global \ - amap_user, \ + admin_group, \ + admin_user, \ student_user, \ product, \ deletable_product, \ @@ -38,7 +42,12 @@ async def init_objects() -> None: order, \ deletable_order_by_admin - amap_user = await create_user_with_groups([GroupType.amap]) + admin_group = await create_groups_with_permissions( + [AmapPermissions.manage_amap], + "AMAP", + ) + + admin_user = await create_user_with_groups([admin_group.id]) student_user = await create_user_with_groups( [], ) @@ -106,7 +115,7 @@ async def init_objects() -> None: def test_get_products(client: TestClient) -> None: - token = create_api_access_token(amap_user) + token = create_api_access_token(admin_user) response = client.get( "/amap/products", @@ -116,7 +125,7 @@ def test_get_products(client: TestClient) -> None: def test_create_product(client: TestClient) -> None: - token = create_api_access_token(amap_user) + token = create_api_access_token(admin_user) response = client.post( "/amap/products", @@ -138,7 +147,7 @@ def test_get_product_by_id(client: TestClient) -> None: def test_edit_product(client: TestClient) -> None: - token = create_api_access_token(amap_user) + token = create_api_access_token(admin_user) response = client.patch( f"/amap/products/{product.id}", @@ -149,7 +158,7 @@ def test_edit_product(client: TestClient) -> None: def test_delete_product(client: TestClient) -> None: - token = create_api_access_token(amap_user) + token = create_api_access_token(admin_user) response = client.delete( f"/amap/products/{deletable_product.id}", @@ -170,7 +179,7 @@ def test_get_deliveries(client: TestClient) -> None: def test_create_delivery(client: TestClient) -> None: - token = create_api_access_token(amap_user) + token = create_api_access_token(admin_user) response = client.post( "/amap/deliveries", @@ -185,7 +194,7 @@ def test_create_delivery(client: TestClient) -> None: def test_delete_delivery(client: TestClient) -> None: - token = create_api_access_token(amap_user) + token = create_api_access_token(admin_user) response = client.delete( f"/amap/deliveries/{deletable_delivery.id}", @@ -195,7 +204,7 @@ def test_delete_delivery(client: TestClient) -> None: def test_edit_delivery(client: TestClient) -> None: - token = create_api_access_token(amap_user) + token = create_api_access_token(admin_user) response = client.patch( f"/amap/deliveries/{delivery.id}", @@ -206,7 +215,7 @@ def test_edit_delivery(client: TestClient) -> None: def test_add_product_to_delivery(client: TestClient) -> None: - token = create_api_access_token(amap_user) + token = create_api_access_token(admin_user) response = client.post( f"/amap/deliveries/{delivery.id}/products", @@ -217,7 +226,7 @@ def test_add_product_to_delivery(client: TestClient) -> None: def test_remove_product_from_delivery(client: TestClient) -> None: - token = create_api_access_token(amap_user) + token = create_api_access_token(admin_user) response = client.request( method="DELETE", url=f"/amap/deliveries/{delivery.id}/products", @@ -236,7 +245,7 @@ def test_remove_product_from_delivery(client: TestClient) -> None: def test_get_orders_from_delivery(client: TestClient) -> None: - token = create_api_access_token(amap_user) + token = create_api_access_token(admin_user) response = client.get( f"/amap/deliveries/{delivery.id}/orders", @@ -246,7 +255,7 @@ def test_get_orders_from_delivery(client: TestClient) -> None: def test_get_order_by_id(client: TestClient) -> None: - token = create_api_access_token(amap_user) + token = create_api_access_token(admin_user) response = client.get( f"/amap/orders/{order.order_id}", headers={"Authorization": f"Bearer {token}"}, @@ -255,7 +264,7 @@ def test_get_order_by_id(client: TestClient) -> None: def test_make_delivery_orderable(client: TestClient) -> None: - token = create_api_access_token(amap_user) + token = create_api_access_token(admin_user) response = client.post( f"/amap/deliveries/{delivery.id}/openordering", headers={"Authorization": f"Bearer {token}"}, @@ -315,7 +324,7 @@ def test_remove_order(client: TestClient) -> None: def test_remove_order_by_admin(client: TestClient) -> None: token = create_api_access_token(student_user) - token_amap = create_api_access_token(amap_user) + token_amap = create_api_access_token(admin_user) response = client.delete( f"/amap/orders/{deletable_order_by_admin.order_id}", @@ -331,7 +340,7 @@ def test_remove_order_by_admin(client: TestClient) -> None: def test_get_users_cash(client: TestClient) -> None: - token = create_api_access_token(amap_user) + token = create_api_access_token(admin_user) response = client.get( "/amap/users/cash", @@ -341,7 +350,7 @@ def test_get_users_cash(client: TestClient) -> None: def test_get_cash_by_id(client: TestClient) -> None: - amap_token = create_api_access_token(amap_user) + amap_token = create_api_access_token(admin_user) student_token = create_api_access_token(student_user) # The student user who is not part of AMAP group @@ -355,7 +364,7 @@ def test_get_cash_by_id(client: TestClient) -> None: # The student user who is not part of AMAP group # should not be able to access an other user cash response = client.get( - f"/amap/users/{amap_user.id}/cash", + f"/amap/users/{admin_user.id}/cash", headers={"Authorization": f"Bearer {student_token}"}, ) assert response.status_code == 403 @@ -369,27 +378,27 @@ def test_get_cash_by_id(client: TestClient) -> None: def test_create_cash_of_user(client: TestClient) -> None: - token = create_api_access_token(amap_user) + token = create_api_access_token(admin_user) response = client.post( - f"/amap/users/{amap_user.id}/cash", - json={"balance": 50, "user_id": amap_user.id}, + f"/amap/users/{admin_user.id}/cash", + json={"balance": 50, "user_id": admin_user.id}, headers={"Authorization": f"Bearer {token}"}, ) assert response.status_code == 201 def test_edit_cash_by_id(client: TestClient) -> None: - token = create_api_access_token(amap_user) + token = create_api_access_token(admin_user) response = client.post( - f"/amap/users/{amap_user.id}/cash", - json={"balance": 50, "user_id": amap_user.id}, + f"/amap/users/{admin_user.id}/cash", + json={"balance": 50, "user_id": admin_user.id}, headers={"Authorization": f"Bearer {token}"}, ) response = client.patch( - f"/amap/users/{amap_user.id}/cash", + f"/amap/users/{admin_user.id}/cash", json={"balance": 45}, headers={"Authorization": f"Bearer {token}"}, ) diff --git a/tests/modules/test_booking.py b/tests/modules/test_booking.py index db95c6704b..ddf2c5a6e0 100644 --- a/tests/modules/test_booking.py +++ b/tests/modules/test_booking.py @@ -4,16 +4,23 @@ import pytest_asyncio from fastapi.testclient import TestClient -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups from app.core.users import models_users from app.modules.booking import models_booking +from app.modules.booking.endpoints_booking import BookingPermissions from app.modules.booking.types_booking import Decision from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) +admin_group: models_groups.CoreGroup +manager_group: models_groups.CoreGroup +manager_to_delete_group: models_groups.CoreGroup +dummy_group: models_groups.CoreGroup + booking_id: str booking: models_booking.Booking @@ -33,15 +40,24 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: + global admin_group, manager_group, manager_to_delete_group, dummy_group + admin_group = await create_groups_with_permissions( + [BookingPermissions.manage_managers, BookingPermissions.manage_rooms], + "booking_manager", + ) + manager_group = await create_groups_with_permissions([], "BDE") + manager_to_delete_group = await create_groups_with_permissions([], "amap") + dummy_group = await create_groups_with_permissions([], "dummy") + global admin_user - admin_user = await create_user_with_groups([GroupType.admin]) + admin_user = await create_user_with_groups([admin_group.id]) global token_admin token_admin = create_api_access_token(admin_user) global manager_user manager_user = await create_user_with_groups( - [GroupType.BDE], + [manager_group.id], ) global token_manager @@ -57,7 +73,7 @@ async def init_objects() -> None: manager = models_booking.Manager( id=str(uuid.uuid4()), name="BDE", - group_id=GroupType.BDE, + group_id=manager_group.id, ) await add_object_to_db(manager) @@ -65,7 +81,7 @@ async def init_objects() -> None: manager_to_delete = models_booking.Manager( id=str(uuid.uuid4()), name="Planet", - group_id=GroupType.amap, + group_id=manager_to_delete_group.id, ) await add_object_to_db(manager_to_delete) @@ -137,14 +153,15 @@ def test_post_manager(client: TestClient) -> None: json={ "id": str(uuid.uuid4()), "name": "Admin", - "group_id": GroupType.admin.value, + "group_id": dummy_group.id, }, headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 201 -def test_edit_manager(client: TestClient) -> None: +async def test_edit_manager(client: TestClient) -> None: + temp_group = await create_groups_with_permissions([], "temp") response = client.patch( f"/booking/managers/{manager_to_delete.id}", json={"name": "Test"}, @@ -153,7 +170,7 @@ def test_edit_manager(client: TestClient) -> None: assert response.status_code == 204 response = client.patch( f"/booking/managers/{manager_to_delete.id}", - json={"group_id": GroupType.cinema.value}, + json={"group_id": temp_group.id}, headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 204 diff --git a/tests/modules/test_calendar.py b/tests/modules/test_calendar.py index 6440c07caf..4a17966726 100644 --- a/tests/modules/test_calendar.py +++ b/tests/modules/test_calendar.py @@ -4,34 +4,43 @@ import pytest_asyncio from fastapi.testclient import TestClient -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups from app.core.users import models_users from app.modules.booking.types_booking import Decision from app.modules.calendar import models_calendar +from app.modules.calendar.endpoints_calendar import CalendarPermissions from app.modules.calendar.types_calendar import CalendarEventType from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) +admin_group: models_groups.CoreGroup + calendar_event: models_calendar.Event calendar_event_to_delete: models_calendar.Event -calendar_user_bde: models_users.CoreUser +calendar_user_admin: models_users.CoreUser calendar_user_simple: models_users.CoreUser -token_bde: str +token_admin: str token_simple: str @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: - global calendar_user_bde - calendar_user_bde = await create_user_with_groups( - [GroupType.BDE], + global admin_group + admin_group = await create_groups_with_permissions( + [CalendarPermissions.manage_events], + "calendar_manager", + ) + global calendar_user_admin + calendar_user_admin = await create_user_with_groups( + [admin_group.id], ) - global token_bde - token_bde = create_api_access_token(calendar_user_bde) + global token_admin + token_admin = create_api_access_token(calendar_user_admin) global calendar_user_simple calendar_user_simple = await create_user_with_groups([]) @@ -44,7 +53,7 @@ async def init_objects() -> None: id=str(uuid.uuid4()), name="Dojo", organizer="Eclair", - applicant_id=calendar_user_bde.id, + applicant_id=calendar_user_admin.id, start=datetime.datetime.fromisoformat("2022-09-22T20:00:00Z"), end=datetime.datetime.fromisoformat("2022-09-22T23:00:00Z"), all_day=False, @@ -77,7 +86,7 @@ async def init_objects() -> None: def test_get_all_events(client: TestClient) -> None: response = client.get( "/calendar/events/", - headers={"Authorization": f"Bearer {token_bde}"}, + headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -85,7 +94,7 @@ def test_get_all_events(client: TestClient) -> None: def test_get_event(client: TestClient) -> None: response = client.get( f"/calendar/events/{calendar_event.id}", - headers={"Authorization": f"Bearer {token_bde}"}, + headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 200 @@ -93,7 +102,7 @@ def test_get_event(client: TestClient) -> None: def test_get_nonexistent_event(client: TestClient) -> None: response = client.get( "/calendar/events/bad_id", - headers={"Authorization": f"Bearer {token_bde}"}, + headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 404 @@ -111,7 +120,7 @@ def test_add_event(client: TestClient) -> None: "type": "Event AE", "description": "Apprendre à coder !", }, - headers={"Authorization": f"Bearer {token_bde}"}, + headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 201 @@ -128,7 +137,7 @@ def test_add_event_missing_parameter(client: TestClient) -> None: "type": "Event AE", "description": "Apprendre à coder !", }, - headers={"Authorization": f"Bearer {token_bde}"}, + headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 422 @@ -137,7 +146,7 @@ def test_edit_event(client: TestClient) -> None: response = client.patch( f"/calendar/events/{calendar_event.id}", json={"description": "Apprendre à programmer"}, - headers={"Authorization": f"Bearer {token_bde}"}, + headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 204 @@ -146,7 +155,7 @@ def test_delete_event(client: TestClient) -> None: """Test if an admin can delete an event.""" response = client.delete( f"/calendar/events/{calendar_event_to_delete.id}", - headers={"Authorization": f"Bearer {token_bde}"}, + headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 204 @@ -163,7 +172,7 @@ def test_delete_event_unauthorized_user(client: TestClient) -> None: def test_decline_event(client: TestClient) -> None: response = client.patch( f"/calendar/events/{calendar_event.id}/reply/declined", - headers={"Authorization": f"Bearer {token_bde}"}, + headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 204 @@ -171,6 +180,6 @@ def test_decline_event(client: TestClient) -> None: def test_approve_event(client: TestClient) -> None: response = client.patch( f"/calendar/events/{calendar_event.id}/reply/approved", - headers={"Authorization": f"Bearer {token_bde}"}, + headers={"Authorization": f"Bearer {token_admin}"}, ) assert response.status_code == 204 diff --git a/tests/modules/test_campaign.py b/tests/modules/test_campaign.py index a96e0296fe..5fc38e9abb 100644 --- a/tests/modules/test_campaign.py +++ b/tests/modules/test_campaign.py @@ -4,18 +4,25 @@ import pytest_asyncio from fastapi.testclient import TestClient -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups +from app.core.permissions import models_permissions from app.core.users import models_users from app.modules.campaign import models_campaign +from app.modules.campaign.endpoints_campaign import CampaignPermissions from app.modules.campaign.types_campaign import ListType from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) -CAA_user: models_users.CoreUser -AE_user: models_users.CoreUser +admin_group: models_groups.CoreGroup +voters_group: models_groups.CoreGroup +dummy_group: models_groups.CoreGroup + +admin_user: models_users.CoreUser +voter_user: models_users.CoreUser section: models_campaign.Sections campaign_list: models_campaign.Lists @@ -26,12 +33,26 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: - global CAA_user, AE_user + global admin_group, voters_group, dummy_group + admin_group = await create_groups_with_permissions( + [CampaignPermissions.manage_campaign, CampaignPermissions.vote], + "campaign_manager", + ) + voters_group = await create_groups_with_permissions( + [CampaignPermissions.vote], + "voters", + ) + dummy_group = await create_groups_with_permissions( + [], + "dummy", + ) - CAA_user = await create_user_with_groups( - [GroupType.CAA, GroupType.AE], + global admin_user, voter_user + + admin_user = await create_user_with_groups( + [admin_group.id, voters_group.id], ) - AE_user = await create_user_with_groups([GroupType.AE]) + voter_user = await create_user_with_groups([voters_group.id]) global section global campaign_list @@ -53,12 +74,12 @@ async def init_objects() -> None: type=ListType.serio, members=[ models_campaign.ListMemberships( - user_id=CAA_user.id, + user_id=admin_user.id, list_id=list_id, role="Prez", ), models_campaign.ListMemberships( - user_id=AE_user.id, + user_id=voter_user.id, list_id=list_id, role="SG", ), @@ -67,18 +88,9 @@ async def init_objects() -> None: ) await add_object_to_db(campaign_list) - voters_AE = models_campaign.VoterGroups( - group_id=GroupType.AE, - ) - await add_object_to_db(voters_AE) - voters_CAA = models_campaign.VoterGroups( - group_id=GroupType.CAA, - ) - await add_object_to_db(voters_CAA) - def test_get_sections(client: TestClient) -> None: - token = create_api_access_token(AE_user) + token = create_api_access_token(voter_user) response = client.get( "/campaign/sections", headers={ @@ -89,7 +101,7 @@ def test_get_sections(client: TestClient) -> None: def test_add_sections(client: TestClient) -> None: - token = create_api_access_token(CAA_user) + token = create_api_access_token(admin_user) response = client.post( "/campaign/sections", headers={"Authorization": f"Bearer {token}"}, @@ -104,7 +116,7 @@ def test_add_sections(client: TestClient) -> None: def test_delete_section(client: TestClient) -> None: - token = create_api_access_token(CAA_user) + token = create_api_access_token(admin_user) response = client.delete( f"/campaign/sections/{section2id}", headers={"Authorization": f"Bearer {token}"}, @@ -113,7 +125,7 @@ def test_delete_section(client: TestClient) -> None: def test_get_voters(client: TestClient) -> None: - token = create_api_access_token(AE_user) + token = create_api_access_token(voter_user) response = client.get( "/campaign/voters", headers={ @@ -123,38 +135,36 @@ def test_get_voters(client: TestClient) -> None: assert response.status_code == 200 -def test_delete_voter_by_group_id(client: TestClient) -> None: - token = create_api_access_token(CAA_user) - response = client.delete( - f"/campaign/voters/{GroupType.CAA}", - headers={"Authorization": f"Bearer {token}"}, +async def test_delete_voter_by_group_id(client: TestClient) -> None: + permission = models_permissions.CorePermissionGroup( + permission_name=CampaignPermissions.vote, + group_id=dummy_group.id, ) - assert response.status_code == 204 + await add_object_to_db(permission) - -def test_delete_voters(client: TestClient) -> None: - token = create_api_access_token(CAA_user) + token = create_api_access_token(admin_user) response = client.delete( - "/campaign/voters", + f"/campaign/voters/{dummy_group.id}", headers={"Authorization": f"Bearer {token}"}, ) assert response.status_code == 204 -def test_add_voters(client: TestClient) -> None: - token = create_api_access_token(CAA_user) +async def test_add_voters(client: TestClient) -> None: + group = await create_groups_with_permissions( + [], + "test", + ) + token = create_api_access_token(admin_user) response = client.post( - "/campaign/voters", + f"/campaign/voters/{group.id}", headers={"Authorization": f"Bearer {token}"}, - json={ - "group_id": GroupType.AE, - }, ) - assert response.status_code == 201 + assert response.status_code == 204 def test_get_lists(client: TestClient) -> None: - token = create_api_access_token(AE_user) + token = create_api_access_token(voter_user) response = client.get( "/campaign/lists", headers={"Authorization": f"Bearer {token}"}, @@ -163,7 +173,7 @@ def test_get_lists(client: TestClient) -> None: def test_add_list(client: TestClient) -> None: - token = create_api_access_token(CAA_user) + token = create_api_access_token(admin_user) response = client.post( "/campaign/lists", headers={"Authorization": f"Bearer {token}"}, @@ -172,7 +182,7 @@ def test_add_list(client: TestClient) -> None: "description": "Probablement la meilleure liste disponible", "type": "Serio", "section_id": section.id, - "members": [{"user_id": CAA_user.id, "role": "Prez"}], + "members": [{"user_id": admin_user.id, "role": "Prez"}], "program": "Contacter la DSI", }, ) @@ -182,7 +192,7 @@ def test_add_list(client: TestClient) -> None: def test_delete_list(client: TestClient) -> None: - token = create_api_access_token(CAA_user) + token = create_api_access_token(admin_user) response = client.delete( f"/campaign/lists/{list2id}", headers={"Authorization": f"Bearer {token}"}, @@ -191,20 +201,20 @@ def test_delete_list(client: TestClient) -> None: def test_update_list(client: TestClient) -> None: - token = create_api_access_token(CAA_user) + token = create_api_access_token(admin_user) response = client.patch( f"/campaign/lists/{campaign_list.id}", headers={"Authorization": f"Bearer {token}"}, json={ "name": "Liste 1 Update", - "members": [{"user_id": CAA_user.id, "role": "Prez"}], + "members": [{"user_id": admin_user.id, "role": "Prez"}], }, ) assert response.status_code == 204 def test_create_campaigns_logo(client: TestClient) -> None: - token = create_api_access_token(CAA_user) + token = create_api_access_token(admin_user) with Path("assets/images/default_campaigns_logo.png").open("rb") as image: response = client.post( @@ -218,7 +228,7 @@ def test_create_campaigns_logo(client: TestClient) -> None: def test_vote_if_not_opened(client: TestClient) -> None: # An user should be able to vote if the status is not opened - token = create_api_access_token(AE_user) + token = create_api_access_token(voter_user) response = client.post( "/campaign/votes", headers={"Authorization": f"Bearer {token}"}, @@ -228,7 +238,7 @@ def test_vote_if_not_opened(client: TestClient) -> None: def test_open_vote(client: TestClient) -> None: - token = create_api_access_token(CAA_user) + token = create_api_access_token(admin_user) response = client.post( "/campaign/status/open", headers={ @@ -239,7 +249,7 @@ def test_open_vote(client: TestClient) -> None: def test_read_campaigns_logo(client: TestClient) -> None: - token = create_api_access_token(AE_user) + token = create_api_access_token(voter_user) response = client.get( f"/campaign/lists/{campaign_list.id}/logo", @@ -251,7 +261,7 @@ def test_read_campaigns_logo(client: TestClient) -> None: def test_vote_if_opened(client: TestClient) -> None: # As the status is now opened, the user should be able to vote - token = create_api_access_token(AE_user) + token = create_api_access_token(voter_user) response = client.post( "/campaign/votes", headers={"Authorization": f"Bearer {token}"}, @@ -262,7 +272,7 @@ def test_vote_if_opened(client: TestClient) -> None: def test_vote_a_second_time_for_the_same_section(client: TestClient) -> None: # An user should not be able to vote twice for the same section - token = create_api_access_token(AE_user) + token = create_api_access_token(voter_user) response = client.post( "/campaign/votes", headers={"Authorization": f"Bearer {token}"}, @@ -272,7 +282,7 @@ def test_vote_a_second_time_for_the_same_section(client: TestClient) -> None: def test_get_sections_already_voted(client: TestClient) -> None: - token = create_api_access_token(AE_user) + token = create_api_access_token(voter_user) response = client.get( "/campaign/votes", headers={"Authorization": f"Bearer {token}"}, @@ -281,7 +291,7 @@ def test_get_sections_already_voted(client: TestClient) -> None: def test_get_stats_for_section(client: TestClient) -> None: - token = create_api_access_token(CAA_user) + token = create_api_access_token(admin_user) response = client.get( f"/campaign/stats/{section.id}", headers={"Authorization": f"Bearer {token}"}, @@ -291,7 +301,7 @@ def test_get_stats_for_section(client: TestClient) -> None: def test_get_results_while_open(client: TestClient) -> None: # As the status is open, nobody should be able to access results - token = create_api_access_token(CAA_user) + token = create_api_access_token(admin_user) response = client.get( "/campaign/results", headers={"Authorization": f"Bearer {token}"}, @@ -300,7 +310,7 @@ def test_get_results_while_open(client: TestClient) -> None: def test_close_vote(client: TestClient) -> None: - token = create_api_access_token(CAA_user) + token = create_api_access_token(admin_user) response = client.post( "/campaign/status/close", headers={"Authorization": f"Bearer {token}"}, @@ -309,7 +319,7 @@ def test_close_vote(client: TestClient) -> None: def test_count_vote(client: TestClient) -> None: - token = create_api_access_token(CAA_user) + token = create_api_access_token(admin_user) response = client.post( "/campaign/status/counting", headers={"Authorization": f"Bearer {token}"}, @@ -319,7 +329,7 @@ def test_count_vote(client: TestClient) -> None: def test_get_results_while_counting(client: TestClient) -> None: # As the status is counting, only CAA user should be able to access results - token = create_api_access_token(CAA_user) + token = create_api_access_token(admin_user) response = client.get( "/campaign/results", headers={"Authorization": f"Bearer {token}"}, @@ -328,7 +338,7 @@ def test_get_results_while_counting(client: TestClient) -> None: def test_publish_vote(client: TestClient) -> None: - token = create_api_access_token(CAA_user) + token = create_api_access_token(admin_user) response = client.post( "/campaign/status/published", headers={"Authorization": f"Bearer {token}"}, @@ -337,7 +347,7 @@ def test_publish_vote(client: TestClient) -> None: def test_get_results_while_published(client: TestClient) -> None: - token = create_api_access_token(AE_user) + token = create_api_access_token(voter_user) response = client.get( "/campaign/results", headers={"Authorization": f"Bearer {token}"}, @@ -346,9 +356,18 @@ def test_get_results_while_published(client: TestClient) -> None: def test_reset_votes(client: TestClient) -> None: - token = create_api_access_token(CAA_user) + token = create_api_access_token(admin_user) response = client.post( "/campaign/status/reset", headers={"Authorization": f"Bearer {token}"}, ) assert response.status_code == 204 + + +def test_delete_voters(client: TestClient) -> None: + token = create_api_access_token(admin_user) + response = client.delete( + "/campaign/voters", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 204 diff --git a/tests/modules/test_cinema.py b/tests/modules/test_cinema.py index 8a1bdbf400..49de569ced 100644 --- a/tests/modules/test_cinema.py +++ b/tests/modules/test_cinema.py @@ -4,15 +4,19 @@ import pytest_asyncio from fastapi.testclient import TestClient -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups from app.core.users import models_users from app.modules.cinema import models_cinema +from app.modules.cinema.endpoints_cinema import CinemaPermissions from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) +admin_group: models_groups.CoreGroup + session: models_cinema.Session cinema_user_cinema: models_users.CoreUser cinema_user_simple: models_users.CoreUser @@ -22,8 +26,14 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: + global admin_group + admin_group = await create_groups_with_permissions( + [CinemaPermissions.manage_sessions], + "cinema_admin", + ) + global cinema_user_cinema - cinema_user_cinema = await create_user_with_groups([GroupType.cinema]) + cinema_user_cinema = await create_user_with_groups([admin_group.id]) global token_cinema token_cinema = create_api_access_token(cinema_user_cinema) diff --git a/tests/modules/test_flappybird.py b/tests/modules/test_flappybird.py index 6b06ed66e5..f087de6d60 100644 --- a/tests/modules/test_flappybird.py +++ b/tests/modules/test_flappybird.py @@ -4,15 +4,18 @@ import pytest_asyncio from fastapi.testclient import TestClient -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups from app.core.users import models_users from app.modules.flappybird import models_flappybird +from app.modules.flappybird.endpoints_flappybird import FlappyBirdPermissions from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) +admin_group: models_groups.CoreGroup user: models_users.CoreUser token: str = "" admin_user: models_users.CoreUser @@ -21,13 +24,18 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: + global admin_group + admin_group = await create_groups_with_permissions( + [FlappyBirdPermissions.manage_flappybird], + "flappybird_admin", + ) global user user = await create_user_with_groups( [], ) global admin_user - admin_user = await create_user_with_groups([GroupType.admin]) + admin_user = await create_user_with_groups([admin_group.id]) global token token = create_api_access_token(user=user) diff --git a/tests/modules/test_loan.py b/tests/modules/test_loan.py index 193694da70..df78fd6edc 100644 --- a/tests/modules/test_loan.py +++ b/tests/modules/test_loan.py @@ -5,15 +5,22 @@ import pytest_asyncio from fastapi.testclient import TestClient -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups from app.core.users import models_users from app.modules.loan import models_loan +from app.modules.loan.endpoints_loan import LoanPermissions from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) +admin_group: models_groups.CoreGroup +loaner_group: models_groups.CoreGroup +loaner_group_to_delete: models_groups.CoreGroup +loaner_group_to_create: models_groups.CoreGroup + admin_user: models_users.CoreUser loan_user_loaner: models_users.CoreUser loan_user_simple: models_users.CoreUser @@ -29,6 +36,7 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: + global admin_group, loaner_group, loaner_group_to_delete, loaner_group_to_create global admin_user global loan_user_loaner global loaner @@ -37,26 +45,44 @@ async def init_objects() -> None: global loan global item global item_to_delete - admin_user = await create_user_with_groups([GroupType.admin]) + + admin_group = await create_groups_with_permissions( + [LoanPermissions.manage_loaners], + "loaner", + ) + loaner_group = await create_groups_with_permissions( + [], + "BDE", + ) + loaner_group_to_delete = await create_groups_with_permissions( + [], + "cinema", + ) + loaner_group_to_create = await create_groups_with_permissions( + [], + "ECLAIR", + ) + + admin_user = await create_user_with_groups([admin_group.id]) loan_user_loaner = await create_user_with_groups( - [GroupType.CAA], + [loaner_group.id], ) loaner = models_loan.Loaner( id=str(uuid.uuid4()), - name="CAA", - group_manager_id="6c6d7e88-fdb8-4e42-b2b5-3d3cfd12e7d6", + name="BDE", + group_manager_id=loaner_group.id, ) await add_object_to_db(loaner) loan_user_simple = await create_user_with_groups( - [GroupType.amap], + [], ) loaner_to_delete = models_loan.Loaner( id=str(uuid.uuid4()), name="cinema", - group_manager_id="ce5f36e6-5377-489f-9696-de70e2477300", + group_manager_id=loan_user_simple.id, ) await add_object_to_db(loaner_to_delete) @@ -118,8 +144,8 @@ def test_create_loaners(client: TestClient) -> None: response = client.post( "/loans/loaners/", json={ - "name": "BDE", - "group_manager_id": "ce5f36e6-5377-489f-9696-de70e2477300", + "name": "ECLAIR", + "group_manager_id": loaner_group_to_create.id, }, headers={"Authorization": f"Bearer {token_admin}"}, ) @@ -131,7 +157,7 @@ def test_update_loaners(client: TestClient) -> None: f"/loans/loaners/{loaner_to_delete.id}", json={ "name": "AE", - "group_manager_id": "45649735-866a-49df-b04b-a13c74fd5886", + "group_manager_id": loaner_group_to_delete.id, }, headers={"Authorization": f"Bearer {token_admin}"}, ) diff --git a/tests/modules/test_ph.py b/tests/modules/test_ph.py index b652f1e5f9..44eee86587 100644 --- a/tests/modules/test_ph.py +++ b/tests/modules/test_ph.py @@ -5,15 +5,19 @@ import pytest_asyncio from fastapi.testclient import TestClient -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups from app.core.users import models_users from app.modules.ph import models_ph +from app.modules.ph.endpoints_ph import PHPermissions from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) +admin_group: models_groups.CoreGroup + ph_user_ph: models_users.CoreUser ph_user_simple: models_users.CoreUser @@ -26,8 +30,13 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: + global admin_group + admin_group = await create_groups_with_permissions( + [PHPermissions.manage_ph], + "ph_admin", + ) global ph_user_ph - ph_user_ph = await create_user_with_groups([GroupType.ph]) + ph_user_ph = await create_user_with_groups([admin_group.id]) global token_ph token_ph = create_api_access_token(ph_user_ph) diff --git a/tests/modules/test_phonebook.py b/tests/modules/test_phonebook.py index acb5a5c47b..459aea95fb 100644 --- a/tests/modules/test_phonebook.py +++ b/tests/modules/test_phonebook.py @@ -7,13 +7,17 @@ from app.core.groups.groups_type import GroupType from app.core.users import models_users from app.modules.phonebook import models_phonebook +from app.modules.phonebook.endpoints_phonebook import PhonebookPermissions from app.modules.phonebook.types_phonebook import Kinds, RoleTags from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) +admin_group: models_groups.CoreGroup + association1: models_phonebook.Association association2: models_phonebook.Association association3: models_phonebook.Association @@ -43,6 +47,8 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects(): + global admin_group + global phonebook_user_BDE global token_BDE global phonebook_user_president @@ -69,8 +75,13 @@ async def init_objects(): global association2_group2 + admin_group = await create_groups_with_permissions( + [PhonebookPermissions.manage_phonebook], + "phonebook_admin", + ) + phonebook_user_BDE = await create_user_with_groups( - [GroupType.BDE], + [admin_group.id], ) token_BDE = create_api_access_token(phonebook_user_BDE) diff --git a/tests/modules/test_raffle.py b/tests/modules/test_raffle.py index 010458a7eb..4f9a574971 100644 --- a/tests/modules/test_raffle.py +++ b/tests/modules/test_raffle.py @@ -4,16 +4,21 @@ import pytest_asyncio from fastapi.testclient import TestClient -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups from app.core.users import models_users from app.modules.raffle import models_raffle +from app.modules.raffle.endpoints_raffle import RafflePermissions from app.modules.raffle.types_raffle import RaffleStatusType from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) +admin_group: models_groups.CoreGroup +raffle_group: models_groups.CoreGroup + BDE_user: models_users.CoreUser admin_user: models_users.CoreUser student_user: models_users.CoreUser @@ -33,6 +38,8 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: global \ + admin_group, \ + raffle_group, \ admin_user, \ BDE_user, \ student_user, \ @@ -48,17 +55,26 @@ async def init_objects() -> None: raffle_to_delete, \ packticket_to_delete - BDE_user = await create_user_with_groups([GroupType.BDE]) + admin_group = await create_groups_with_permissions( + [RafflePermissions.manage_raffle], + "raffle_admin", + ) + raffle_group = await create_groups_with_permissions( + [], + "BDE", + ) + + BDE_user = await create_user_with_groups([raffle_group.id]) student_user = await create_user_with_groups( [], ) - admin_user = await create_user_with_groups([GroupType.admin]) + admin_user = await create_user_with_groups([admin_group.id]) raffle_to_delete = models_raffle.Raffle( id=str(uuid.uuid4()), name="Antoine's raffle", status=RaffleStatusType.creation, - group_id=GroupType.BDE, + group_id=raffle_group.id, description=None, ) await add_object_to_db(raffle_to_delete) @@ -66,7 +82,7 @@ async def init_objects() -> None: id=str(uuid.uuid4()), name="The best raffle", status=RaffleStatusType.creation, - group_id=GroupType.BDE, + group_id=raffle_group.id, description="Description of the raffle", ) await add_object_to_db(raffle) @@ -75,7 +91,7 @@ async def init_objects() -> None: id=str(uuid.uuid4()), name="The best raffle to draw", status=RaffleStatusType.lock, - group_id=GroupType.BDE, + group_id=raffle_group.id, description="Description of the raffle", ) await add_object_to_db(raffle_to_draw) @@ -157,7 +173,7 @@ def test_create_raffle(client: TestClient) -> None: "/tombola/raffles", json={ "name": "test", - "group_id": GroupType.BDE, + "group_id": raffle_group.id, "description": "Raffle's description", }, headers={"Authorization": f"Bearer {token}"}, diff --git a/tests/modules/test_raid.py b/tests/modules/test_raid.py index a9dcfdc059..72c5afc70b 100644 --- a/tests/modules/test_raid.py +++ b/tests/modules/test_raid.py @@ -9,9 +9,10 @@ from fastapi.testclient import TestClient from pytest_mock import MockerFixture -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups from app.core.users import models_users from app.modules.raid import coredata_raid, models_raid +from app.modules.raid.endpoints_raid import RaidPermissions from app.modules.raid.models_raid import ( RaidParticipant, RaidTeam, @@ -29,10 +30,12 @@ add_coredata_to_db, add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) participant: models_raid.RaidParticipant +admin_group: models_groups.CoreGroup team: models_raid.RaidTeam validated_team: models_raid.RaidTeam @@ -57,8 +60,13 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: + global admin_group + admin_group = await create_groups_with_permissions( + [RaidPermissions.manage_raid], + "raid_admin", + ) global raid_admin_user, token_raid_admin - raid_admin_user = await create_user_with_groups([GroupType.raid_admin]) + raid_admin_user = await create_user_with_groups([admin_group.id]) token_raid_admin = create_api_access_token(raid_admin_user) global simple_user, token_simple @@ -745,7 +753,7 @@ async def test_set_team_number_utility_empty_database( # Assert update_team was called with correct parameters mock_update_team.assert_called_once() - args, kwargs = mock_update_team.call_args + args, _ = mock_update_team.call_args assert args[0] == mock_team.id assert args[1].number == 101 # 100 (sports separator) + 1 @@ -778,7 +786,7 @@ async def test_set_team_number_utility_existing_teams( # Assert update_team was called with correct parameters mock_update_team.assert_called_once() - args, kwargs = mock_update_team.call_args + args, _ = mock_update_team.call_args assert args[0] == mock_team.id assert args[1].number == 221 # 220 + 1 @@ -834,7 +842,7 @@ async def test_set_team_number_utility_discovery_difficulty( # Assert update_team was called with correct parameters mock_update_team.assert_called_once() - args, kwargs = mock_update_team.call_args + args, _ = mock_update_team.call_args assert args[0] == mock_team.id assert args[1].number == 6 # discovery (0) + 5 + 1 diff --git a/tests/modules/test_recommendation.py b/tests/modules/test_recommendation.py index defcb50523..01e053ed83 100644 --- a/tests/modules/test_recommendation.py +++ b/tests/modules/test_recommendation.py @@ -5,14 +5,20 @@ import pytest_asyncio from fastapi.testclient import TestClient -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups from app.modules.recommendation import models_recommendation +from app.modules.recommendation.endpoints_recommendation import ( + RecommendationPermissions, +) from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) +admin_group: models_groups.CoreGroup + token_simple: str token_BDE: str recommendation: models_recommendation.Recommendation @@ -20,6 +26,11 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: + global admin_group + admin_group = await create_groups_with_permissions( + [RecommendationPermissions.manage_recommendation], + "recommendation_admin", + ) user_simple = await create_user_with_groups( [], ) @@ -27,7 +38,7 @@ async def init_objects() -> None: global token_simple token_simple = create_api_access_token(user_simple) - user_BDE = await create_user_with_groups([GroupType.BDE]) + user_BDE = await create_user_with_groups([admin_group.id]) global token_BDE token_BDE = create_api_access_token(user_BDE) diff --git a/tests/modules/test_seed_library.py b/tests/modules/test_seed_library.py index 634f63eb12..f39da1bb3b 100644 --- a/tests/modules/test_seed_library.py +++ b/tests/modules/test_seed_library.py @@ -4,15 +4,19 @@ import pytest_asyncio from fastapi.testclient import TestClient -from app.core.groups.groups_type import GroupType +from app.core.groups import models_groups from app.core.users import models_users from app.modules.seed_library import models_seed_library, types_seed_library +from app.modules.seed_library.endpoints_seed_library import SeedLibraryPermissions from tests.commons import ( add_object_to_db, create_api_access_token, + create_groups_with_permissions, create_user_with_groups, ) +admin_group: models_groups.CoreGroup + admin_user: models_users.CoreUser simple_user: models_users.CoreUser species1: models_seed_library.Species @@ -31,6 +35,7 @@ @pytest_asyncio.fixture(scope="module", autouse=True) async def init_objects() -> None: global \ + admin_group, \ admin_user, \ simple_user, \ species1, \ @@ -43,7 +48,12 @@ async def init_objects() -> None: token_simple, \ token_admin - admin_user = await create_user_with_groups([GroupType.seed_library]) + admin_group = await create_groups_with_permissions( + [SeedLibraryPermissions.manage_seed_library], + "Seed Library Admin Group", + ) + + admin_user = await create_user_with_groups([admin_group.id]) simple_user = await create_user_with_groups( [], @@ -836,7 +846,7 @@ def test_borrow(client: TestClient): f"/seed_library/plants/{plant_from_1_update_test.id}/borrow", headers={"Authorization": f"Bearer {token_simple}"}, ) - assert response.status_code == 204 + assert response.status_code == 204, response.text ############## Check Update ############### response_updated_get = client.get( f"/seed_library/plants/{plant_from_1_update_test.id}", diff --git a/tests/test_permissions.py b/tests/test_permissions.py new file mode 100644 index 0000000000..ddf9fb4c3b --- /dev/null +++ b/tests/test_permissions.py @@ -0,0 +1,110 @@ +import pytest_asyncio +from fastapi.testclient import TestClient + +from app.core.groups import models_groups +from app.core.groups.groups_type import GroupType +from app.core.permissions import models_permissions +from app.core.users import models_users +from app.module import permissions_list +from app.modules.booking.endpoints_booking import BookingPermissions +from app.modules.cinema.endpoints_cinema import CinemaPermissions +from tests.commons import ( + add_object_to_db, + create_api_access_token, + create_groups_with_permissions, + create_user_with_groups, +) + +group1: models_groups.CoreGroup +group2: models_groups.CoreGroup +group3: models_groups.CoreGroup + +admin_user: models_users.CoreUser +admin_token: str + + +@pytest_asyncio.fixture(scope="module", autouse=True) +async def init_objects() -> None: + global group1, group2, group3 + group1 = await create_groups_with_permissions( + [BookingPermissions.manage_managers, CinemaPermissions.manage_sessions], + "group1", + ) + group2 = await create_groups_with_permissions( + [BookingPermissions.manage_managers, BookingPermissions.manage_rooms], + "group2", + ) + group3 = await create_groups_with_permissions( + [], + "group3", + ) + + global admin_user + admin_user = await create_user_with_groups([GroupType.admin]) + global admin_token + admin_token = create_api_access_token(admin_user) + + +def test_read_permissions_list(client: TestClient) -> None: + response = client.get( + "/permissions/list", + headers={"Authorization": f"Bearer {admin_token}"}, + ) + assert response.status_code == 200 + assert ( + len(response.json()) == len(permissions_list) + ) # We ensure that the number of permissions in full_name_permissions_list is the same as the number of permissions in the permissions_list + + +def test_read_permissions(client: TestClient) -> None: + response = client.get( + "/permissions/", + headers={"Authorization": f"Bearer {admin_token}"}, + ) + assert response.status_code == 200 + + +def test_read_permission_by_name(client: TestClient) -> None: + response = client.get( + f"/permissions/{BookingPermissions.manage_managers.value}", + headers={"Authorization": f"Bearer {admin_token}"}, + ) + assert response.status_code == 200 + assert len(response.json()["group_permissions"]) == 2 + + +def test_create_permission(client: TestClient) -> None: + response = client.post( + "/permissions/", + json={ + "permission_name": BookingPermissions.manage_managers.value, + "group_id": group3.id, + }, + headers={"Authorization": f"Bearer {admin_token}"}, + ) + assert response.status_code == 201 + + response = client.get( + f"/permissions/{BookingPermissions.manage_managers.value}", + headers={"Authorization": f"Bearer {admin_token}"}, + ) + assert response.status_code == 200 + assert len(response.json()["group_permissions"]) == 3 + + +async def test_delete_permission(client: TestClient) -> None: + permission = models_permissions.CorePermissionGroup( + permission_name=BookingPermissions.manage_rooms, + group_id=group3.id, + ) + await add_object_to_db(permission) + response = client.request( + method="DELETE", + url="/permissions/", + json={ + "permission_name": BookingPermissions.manage_rooms.value, + "group_id": group3.id, + }, + headers={"Authorization": f"Bearer {admin_token}"}, + ) + assert response.status_code == 204 From 96c83b692c26c854fb6bf862c839adf031b1310f Mon Sep 17 00:00:00 2001 From: Thonyk Date: Fri, 19 Dec 2025 21:51:57 +0100 Subject: [PATCH 02/14] fix: rebase --- app/modules/campaign/cruds_campaign.py | 1 - app/modules/sport_competition/endpoints_sport_competition.py | 1 - tests/modules/sport_competition/test_purchases.py | 1 - tests/modules/sport_competition/test_sport_inscription.py | 2 +- 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/modules/campaign/cruds_campaign.py b/app/modules/campaign/cruds_campaign.py index 48df1c7ba8..295313a350 100644 --- a/app/modules/campaign/cruds_campaign.py +++ b/app/modules/campaign/cruds_campaign.py @@ -4,7 +4,6 @@ from fastapi import HTTPException from sqlalchemy import delete, select, update -from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload diff --git a/app/modules/sport_competition/endpoints_sport_competition.py b/app/modules/sport_competition/endpoints_sport_competition.py index dd37b15b32..df50234ffe 100644 --- a/app/modules/sport_competition/endpoints_sport_competition.py +++ b/app/modules/sport_competition/endpoints_sport_competition.py @@ -27,7 +27,6 @@ has_user_competition_access, is_competition_user, ) -from app.modules.sport_competition.models_sport_competition import Sport from app.modules.sport_competition.permissions_sport_competition import ( SportCompetitionPermissions, ) diff --git a/tests/modules/sport_competition/test_purchases.py b/tests/modules/sport_competition/test_purchases.py index 7353997091..94ef7f1cdb 100644 --- a/tests/modules/sport_competition/test_purchases.py +++ b/tests/modules/sport_competition/test_purchases.py @@ -5,7 +5,6 @@ import pytest_asyncio from fastapi.testclient import TestClient -from app.core.groups.groups_type import GroupType from app.core.payment import models_payment from app.core.schools import models_schools from app.core.schools.schools_type import SchoolType diff --git a/tests/modules/sport_competition/test_sport_inscription.py b/tests/modules/sport_competition/test_sport_inscription.py index 478cf2e6a0..d6ce4c2c5b 100644 --- a/tests/modules/sport_competition/test_sport_inscription.py +++ b/tests/modules/sport_competition/test_sport_inscription.py @@ -6,7 +6,7 @@ from sqlalchemy import delete, update from app.core.groups import models_groups -from app.core.groups.groups_type import AccountType, GroupType +from app.core.groups.groups_type import AccountType from app.core.schools import models_schools from app.core.schools.schools_type import SchoolType from app.core.users import models_users From 987060d827ab488c6e35964688ac19f355f478d6 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Fri, 19 Dec 2025 21:59:47 +0100 Subject: [PATCH 03/14] fix : lint --- tests/modules/sport_competition/test_purchases.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/modules/sport_competition/test_purchases.py b/tests/modules/sport_competition/test_purchases.py index 94ef7f1cdb..582f1eeed6 100644 --- a/tests/modules/sport_competition/test_purchases.py +++ b/tests/modules/sport_competition/test_purchases.py @@ -5,6 +5,7 @@ import pytest_asyncio from fastapi.testclient import TestClient +from app.core.groups import models_groups from app.core.payment import models_payment from app.core.schools import models_schools from app.core.schools.schools_type import SchoolType @@ -31,6 +32,8 @@ mocked_checkout_id, ) +admin_group: models_groups.CoreGroup + school_from_lyon: models_schools.CoreSchool school_others: models_schools.CoreSchool From d7b8e57bf3320ef63e44a27da3bd9f1b3fd40bc1 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 28 Dec 2025 16:57:35 +0100 Subject: [PATCH 04/14] refacto: use simpler data structure --- app/core/permissions/cruds_permissions.py | 52 ++++++++----------- app/core/permissions/endpoints_permissions.py | 5 +- app/core/permissions/schemas_permissions.py | 5 +- tests/{ => core}/test_permissions.py | 4 +- 4 files changed, 29 insertions(+), 37 deletions(-) rename tests/{ => core}/test_permissions.py (96%) diff --git a/app/core/permissions/cruds_permissions.py b/app/core/permissions/cruds_permissions.py index 2d091c17bc..1292a8bcb3 100644 --- a/app/core/permissions/cruds_permissions.py +++ b/app/core/permissions/cruds_permissions.py @@ -10,8 +10,9 @@ async def get_permissions( + permission_list: list[str], db: AsyncSession, -) -> schemas_permissions.CorePermissions: +) -> list[schemas_permissions.CorePermissions]: """Return all permissions from database""" result_group = ( @@ -25,22 +26,22 @@ async def get_permissions( .all() ) - return schemas_permissions.CorePermissions( - group_permissions=[ - schemas_permissions.CoreGroupPermission( - permission_name=permission.permission_name, - group_id=permission.group_id, - ) - for permission in result_group - ], - account_type_permissions=[ - schemas_permissions.CoreAccountTypePermission( - permission_name=permission.permission_name, - account_type=permission.account_type, - ) - for permission in result_account_type - ], - ) + return [ + schemas_permissions.CorePermissions( + permission_name=permission_name, + groups=[ + permission.group_id + for permission in result_group + if permission.permission_name == permission_name + ], + account_types=[ + permission.account_type + for permission in result_account_type + if permission.permission_name == permission_name + ], + ) + for permission_name in permission_list + ] async def get_permissions_by_permission_name( @@ -73,20 +74,9 @@ async def get_permissions_by_permission_name( .all() ) return schemas_permissions.CorePermissions( - group_permissions=[ - schemas_permissions.CoreGroupPermission( - permission_name=permission.permission_name, - group_id=permission.group_id, - ) - for permission in result_group - ], - account_type_permissions=[ - schemas_permissions.CoreAccountTypePermission( - permission_name=permission.permission_name, - account_type=permission.account_type, - ) - for permission in result_account_type - ], + permission_name=permission_name, + groups=[permission.group_id for permission in result_group], + account_types=[permission.account_type for permission in result_account_type], ) diff --git a/app/core/permissions/endpoints_permissions.py b/app/core/permissions/endpoints_permissions.py index c3eff8fed5..832af254b0 100644 --- a/app/core/permissions/endpoints_permissions.py +++ b/app/core/permissions/endpoints_permissions.py @@ -53,7 +53,7 @@ async def read_permissions_list( @router.get( "/permissions/", - response_model=schemas_permissions.CorePermissions, + response_model=list[schemas_permissions.CorePermissions], status_code=200, ) async def read_permissions( @@ -63,8 +63,9 @@ async def read_permissions( """ Return all permissions from database """ + from app.module import permissions_list - return await cruds_permissions.get_permissions(db) + return await cruds_permissions.get_permissions(permissions_list, db) @router.get( diff --git a/app/core/permissions/schemas_permissions.py b/app/core/permissions/schemas_permissions.py index 6a770a0ab7..c05fc497ba 100644 --- a/app/core/permissions/schemas_permissions.py +++ b/app/core/permissions/schemas_permissions.py @@ -14,5 +14,6 @@ class CoreAccountTypePermission(BaseModel): class CorePermissions(BaseModel): - group_permissions: list[CoreGroupPermission] = [] - account_type_permissions: list[CoreAccountTypePermission] = [] + permission_name: str + groups: list[str] + account_types: list[AccountType] diff --git a/tests/test_permissions.py b/tests/core/test_permissions.py similarity index 96% rename from tests/test_permissions.py rename to tests/core/test_permissions.py index ddf9fb4c3b..3560e6d037 100644 --- a/tests/test_permissions.py +++ b/tests/core/test_permissions.py @@ -70,7 +70,7 @@ def test_read_permission_by_name(client: TestClient) -> None: headers={"Authorization": f"Bearer {admin_token}"}, ) assert response.status_code == 200 - assert len(response.json()["group_permissions"]) == 2 + assert len(response.json()["groups"]) == 2 def test_create_permission(client: TestClient) -> None: @@ -89,7 +89,7 @@ def test_create_permission(client: TestClient) -> None: headers={"Authorization": f"Bearer {admin_token}"}, ) assert response.status_code == 200 - assert len(response.json()["group_permissions"]) == 3 + assert len(response.json()["groups"]) == 3 async def test_delete_permission(client: TestClient) -> None: From 0e2aebbf7df973e295913e3a0902f57a09ac99f3 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 28 Dec 2025 16:58:49 +0100 Subject: [PATCH 05/14] fix: rename --- app/core/permissions/cruds_permissions.py | 8 ++++---- app/core/permissions/endpoints_permissions.py | 4 ++-- app/core/permissions/schemas_permissions.py | 2 +- app/modules/campaign/endpoints_campaign.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/core/permissions/cruds_permissions.py b/app/core/permissions/cruds_permissions.py index 1292a8bcb3..feefc9f6a8 100644 --- a/app/core/permissions/cruds_permissions.py +++ b/app/core/permissions/cruds_permissions.py @@ -12,7 +12,7 @@ async def get_permissions( permission_list: list[str], db: AsyncSession, -) -> list[schemas_permissions.CorePermissions]: +) -> list[schemas_permissions.CorePermission]: """Return all permissions from database""" result_group = ( @@ -27,7 +27,7 @@ async def get_permissions( ) return [ - schemas_permissions.CorePermissions( + schemas_permissions.CorePermission( permission_name=permission_name, groups=[ permission.group_id @@ -47,7 +47,7 @@ async def get_permissions( async def get_permissions_by_permission_name( db: AsyncSession, permission_name: ModulePermissions, -) -> schemas_permissions.CorePermissions: +) -> schemas_permissions.CorePermission: """Return permissions with name from database""" result_group = ( ( @@ -73,7 +73,7 @@ async def get_permissions_by_permission_name( .scalars() .all() ) - return schemas_permissions.CorePermissions( + return schemas_permissions.CorePermission( permission_name=permission_name, groups=[permission.group_id for permission in result_group], account_types=[permission.account_type for permission in result_account_type], diff --git a/app/core/permissions/endpoints_permissions.py b/app/core/permissions/endpoints_permissions.py index 832af254b0..ad427c8014 100644 --- a/app/core/permissions/endpoints_permissions.py +++ b/app/core/permissions/endpoints_permissions.py @@ -53,7 +53,7 @@ async def read_permissions_list( @router.get( "/permissions/", - response_model=list[schemas_permissions.CorePermissions], + response_model=list[schemas_permissions.CorePermission], status_code=200, ) async def read_permissions( @@ -70,7 +70,7 @@ async def read_permissions( @router.get( "/permissions/{permission_name}", - response_model=schemas_permissions.CorePermissions, + response_model=schemas_permissions.CorePermission, status_code=200, ) async def read_permission( diff --git a/app/core/permissions/schemas_permissions.py b/app/core/permissions/schemas_permissions.py index c05fc497ba..caf6e76e69 100644 --- a/app/core/permissions/schemas_permissions.py +++ b/app/core/permissions/schemas_permissions.py @@ -13,7 +13,7 @@ class CoreAccountTypePermission(BaseModel): account_type: AccountType -class CorePermissions(BaseModel): +class CorePermission(BaseModel): permission_name: str groups: list[str] account_types: list[AccountType] diff --git a/app/modules/campaign/endpoints_campaign.py b/app/modules/campaign/endpoints_campaign.py index b24e2ff481..9ccc370a58 100644 --- a/app/modules/campaign/endpoints_campaign.py +++ b/app/modules/campaign/endpoints_campaign.py @@ -343,7 +343,7 @@ async def update_list( @module.router.get( "/campaign/voters", - response_model=schemas_permissions.CorePermissions, + response_model=schemas_permissions.CorePermission, status_code=200, ) async def get_voters( From 4b4333468b4365731e1bef4a0833307769230254 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 28 Dec 2025 18:12:26 +0100 Subject: [PATCH 06/14] fix: correct dependencies and tools --- app/dependencies.py | 29 ++++++++++++++--------------- app/utils/tools.py | 13 +++++++------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/dependencies.py b/app/dependencies.py index daf0d72b95..f1ee6e918f 100644 --- a/app/dependencies.py +++ b/app/dependencies.py @@ -525,30 +525,29 @@ async def is_user_allowed_to( if GroupType.admin in [group.id for group in user.groups]: return user - group_permissions: list[schemas_permissions.CoreGroupPermission] = [] - account_type_permissions: list[ - schemas_permissions.CoreAccountTypePermission - ] = [] + allowed_group_ids: list[str] = [] + allowed_account_types: list[AccountType] = [] for permission_name in permissions_name: - group_permissions += ( + allowed_group_ids += ( await cruds_permissions.get_permissions_by_permission_name( db, permission_name, ) - ).group_permissions - account_type_permissions += ( + ).groups + allowed_account_types += ( await cruds_permissions.get_permissions_by_permission_name( db, permission_name, ) - ).account_type_permissions - - if not any( - permission.group_id in [group.id for group in user.groups] - for permission in group_permissions - ) and user.account_type not in [ - permission.account_type for permission in account_type_permissions - ]: + ).account_types + + if ( + not any( + group_id in [group.id for group in user.groups] + for group_id in allowed_group_ids + ) + and user.account_type not in allowed_account_types + ): raise HTTPException( status_code=403, detail="Unauthorized, user does not have the required permission", diff --git a/app/utils/tools.py b/app/utils/tools.py index e31920104c..e1d2ab9576 100644 --- a/app/utils/tools.py +++ b/app/utils/tools.py @@ -146,12 +146,13 @@ async def has_user_permission( permission_name=permission_name, db=db, ) - return is_user_member_of_any_group( - user, - [perm.group_id for perm in permissions.group_permissions], - ) or user.account_type in [ - perm.account_type for perm in permissions.account_type_permissions - ] + return ( + is_user_member_of_any_group( + user, + permissions.groups, + ) + or user.account_type in permissions.account_types + ) async def save_file_as_data( From 6586df1bcc986272b900a386be626920f3855323 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 28 Dec 2025 18:14:30 +0100 Subject: [PATCH 07/14] fix: linting --- app/dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/dependencies.py b/app/dependencies.py index f1ee6e918f..55fa32a42f 100644 --- a/app/dependencies.py +++ b/app/dependencies.py @@ -25,7 +25,7 @@ async def get_users(db: AsyncSession = Depends(get_db)): from app.core.groups.groups_type import AccountType, GroupType, get_ecl_account_types from app.core.payment.payment_tool import PaymentTool from app.core.payment.types_payment import HelloAssoConfigName -from app.core.permissions import cruds_permissions, schemas_permissions +from app.core.permissions import cruds_permissions from app.core.permissions.type_permissions import ModulePermissions from app.core.users import cruds_users, models_users from app.core.utils import security From 553ddb8f189297f3939bf0eb46d578585c9339ce Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 28 Dec 2025 18:38:16 +0100 Subject: [PATCH 08/14] feat: bump requirements --- app/app.py | 2 +- requirements.txt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/app.py b/app/app.py index 8f681a2e94..ec8572ee1e 100644 --- a/app/app.py +++ b/app/app.py @@ -716,7 +716,7 @@ async def validation_exception_handler( ) return JSONResponse( - status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}), ) diff --git a/requirements.txt b/requirements.txt index 70ab255765..50ed39651c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,10 +9,10 @@ broadcaster==0.3.1 # Working with websockets with multiple work calypsso==2.2.1 Faker==37.1.0 fastapi[standard]==0.122.0 -firebase-admin==6.5.0 # Firebase is used for push notification -google-auth-oauthlib==1.2.1 +firebase-admin==7.1.0 # Firebase is used for push notification +google-auth-oauthlib==1.2.3 helloasso-python==1.0.5 -httpx==0.27.0 +httpx==0.28.1 icalendar==5.0.13 jellyfish==1.2.1 # String Matching Jinja2==3.1.6 # template engine for html files @@ -21,7 +21,7 @@ psutil==7.0.0 # psutil is used to determine the number of psycopg[binary]==3.2.13 # PostgreSQL adapter for *synchronous* operations at startup (database initializations & migrations) pydantic-settings==2.3.4 pydantic==2.12.5 -pyjwt[crypto]==2.8.0 # generate and verify the JWT tokens, imported as `jwt` +pyjwt[crypto]==2.10.1 # generate and verify the JWT tokens, imported as `jwt` PyMuPDF==1.26.6 # PDF processing, imported as `fitz` pypdf==6.4.0 python-multipart==0.0.18 # a form data parser, as oauth flow requires form-data parameters From 51a0e2eec8a3a7afe8b53a1c1f68f92a6986330a Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 28 Dec 2025 22:40:58 +0100 Subject: [PATCH 09/14] fix: dependencies --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 50ed39651c..e6b1629564 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,8 @@ calypsso==2.2.1 Faker==37.1.0 fastapi[standard]==0.122.0 firebase-admin==7.1.0 # Firebase is used for push notification -google-auth-oauthlib==1.2.3 +google-api-python-client==2.187.0 +google-auth-oauthlib==1.2.1 helloasso-python==1.0.5 httpx==0.28.1 icalendar==5.0.13 From 0b590143f02aaa06bb38b1759bc76cac91d27127 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 28 Dec 2025 22:47:02 +0100 Subject: [PATCH 10/14] fix: ignore deprecation --- pyproject.toml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 49ca40c61d..05516f038a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -174,7 +174,12 @@ ignore_missing_imports = true asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "module" asyncio_default_test_loop_scope = "module" -filterwarnings = ["error"] +filterwarnings = [ + "error", + "ignore:'setName' deprecated:DeprecationWarning" +] + + [tool.coverage.run] source_pkgs = ["app"] From a5a1ae8c91edd4ab335a5c2133509aeafe050190 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 28 Dec 2025 22:48:52 +0100 Subject: [PATCH 11/14] fix: better filter --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 05516f038a..38652615cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -176,7 +176,7 @@ asyncio_default_fixture_loop_scope = "module" asyncio_default_test_loop_scope = "module" filterwarnings = [ "error", - "ignore:'setName' deprecated:DeprecationWarning" + "ignore::DeprecationWarning:pyparsing.*", ] From c274e05b7a9b07d022a0fc447cc31b5f8f57b133 Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 28 Dec 2025 22:50:45 +0100 Subject: [PATCH 12/14] fix: better filter --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 38652615cd..70b37bd44b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -176,7 +176,7 @@ asyncio_default_fixture_loop_scope = "module" asyncio_default_test_loop_scope = "module" filterwarnings = [ "error", - "ignore::DeprecationWarning:pyparsing.*", + "ignore::DeprecationWarning:pyparsing", ] From b585845b5ab38bf3f7d2fe23234aeafd8eb4bf6f Mon Sep 17 00:00:00 2001 From: Thonyk Date: Sun, 28 Dec 2025 22:53:48 +0100 Subject: [PATCH 13/14] fix: better filter --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 70b37bd44b..b7fdb6a101 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -176,7 +176,7 @@ asyncio_default_fixture_loop_scope = "module" asyncio_default_test_loop_scope = "module" filterwarnings = [ "error", - "ignore::DeprecationWarning:pyparsing", + "ignore::DeprecationWarning", ] From 3ea1284e2ea52316309bff9c45341b2715f98bbe Mon Sep 17 00:00:00 2001 From: Thonyk Date: Thu, 1 Jan 2026 22:29:47 +0100 Subject: [PATCH 14/14] fix: rebase --- migrations/versions/{48-permissions.py => 49-permissions.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename migrations/versions/{48-permissions.py => 49-permissions.py} (98%) diff --git a/migrations/versions/48-permissions.py b/migrations/versions/49-permissions.py similarity index 98% rename from migrations/versions/48-permissions.py rename to migrations/versions/49-permissions.py index 4fbf89ae12..dabad54844 100644 --- a/migrations/versions/48-permissions.py +++ b/migrations/versions/49-permissions.py @@ -16,7 +16,7 @@ # revision identifiers, used by Alembic. revision: str = "1051d705419e" -down_revision: str | None = "12ceba87cf" +down_revision: str | None = "8f97a0e41c9b" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None