diff --git a/app/modules/misc/__init__.py b/app/modules/misc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/modules/misc/core_data_misc.py b/app/modules/misc/core_data_misc.py new file mode 100644 index 0000000000..e851ff4e3b --- /dev/null +++ b/app/modules/misc/core_data_misc.py @@ -0,0 +1,8 @@ +from app.types.core_data import BaseCoreData + + +# <-- Contacts for PE5 SafetyCards 2025 --> +class ContactSafetyCards(BaseCoreData): + contacts: str = "" + +# <-- End of Contacts for PE5 SafetyCards 2025 --> diff --git a/app/modules/misc/endpoints_misc.py b/app/modules/misc/endpoints_misc.py new file mode 100644 index 0000000000..c1589c4a79 --- /dev/null +++ b/app/modules/misc/endpoints_misc.py @@ -0,0 +1,71 @@ +import json + +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.groups.groups_type import AccountType, GroupType +from app.core.users import models_users +from app.dependencies import get_db, is_user_a_member, is_user_in +from app.modules.misc import core_data_misc, schemas_misc +from app.types.module import Module +from app.utils import tools + +router = APIRouter() + + +# <-- Contacts for PE5 SafetyCards 2025 --> +module = Module( + root="contacts_safety_cards", + tag="Contact", + default_allowed_account_types=[AccountType.student, AccountType.staff], +) + + +@module.router.get( + "/contacts_safety_cards/contacts", + response_model=list[schemas_misc.ContactBase], + status_code=200, +) +async def get_contacts( + db: AsyncSession = Depends(get_db), + user: models_users.CoreUser = Depends(is_user_a_member), +): + """ + Get contacts. + + **The user must be authenticated to use this endpoint** + This the main purpose of this endpoint, because contacts (phone numbers, emails) should not be leaked in the prevention website + """ + + contacts_from_core_data = await tools.get_core_data( + core_data_misc.ContactSafetyCards, + db, + ) + + serialized_json_contacts = contacts_from_core_data.contacts + + return json.loads(serialized_json_contacts) + + +@module.router.put( + "/contacts_safety_cards/contacts", + status_code=201, +) +async def set_contacts( + contacts: list[schemas_misc.ContactBase], + db: AsyncSession = Depends(get_db), + user: models_users.CoreUser = Depends(is_user_in(GroupType.eclair)), +): + """ + Create a contact. + + **This endpoint is only usable by members of the group eclair** + """ + + contacts_serialized_json = json.dumps(contacts) + + await tools.set_core_data( + core_data_misc.ContactSafetyCards(contacts=contacts_serialized_json), + db, + ) +# <-- End of Contacts for PE5 SafetyCards 2025 --> diff --git a/app/modules/misc/schemas_misc.py b/app/modules/misc/schemas_misc.py new file mode 100644 index 0000000000..1542c73442 --- /dev/null +++ b/app/modules/misc/schemas_misc.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel + + +# <-- Contacts for PE5 SafetyCards 2025 --> +class ContactBase(BaseModel): + name: str + email: str | None = None + phone: str | None = None + location: str | None = None +# <--End of Contacts for PE5 SafetyCards 2025 --> diff --git a/app/utils/auth/providers.py b/app/utils/auth/providers.py index 33d29fad9e..049f0f287b 100644 --- a/app/utils/auth/providers.py +++ b/app/utils/auth/providers.py @@ -418,3 +418,26 @@ def get_userinfo(self, user: models_users.CoreUser) -> dict[str, Any]: "name": user.full_name, "email": user.email, } + + +class SafetyCardsAuthClient(BaseAuthClient): + # When set to `None`, users from any group can use the auth client + allowed_account_types: list[AccountType] | None = get_ecl_account_types() + + def get_userinfo(self, user: models_users.CoreUser) -> dict[str, Any]: + """ + See oidc specifications and `app.endpoints.auth.auth_get_userinfo` for more information: + https://openid.net/specs/openid-connect-core-1_0.html#UserInfo + + """ + # Override this method with custom information adapted for the client + # WARNING: The sub (subject) Claim MUST always be returned in the UserInfo Response. + return { + "sub": user.id, + "name": get_display_name( + firstname=user.firstname, + name=user.name, + nickname=user.nickname, + ), + "email": user.email, + } diff --git a/tests/test_contacts_safety_cards.py b/tests/test_contacts_safety_cards.py new file mode 100644 index 0000000000..0b2519bccd --- /dev/null +++ b/tests/test_contacts_safety_cards.py @@ -0,0 +1,53 @@ +import pytest_asyncio +from fastapi.testclient import TestClient + +from app.core.groups.groups_type import GroupType +from tests.commons import create_api_access_token, create_user_with_groups + +token_simple: str +token_eclair: str + + +@pytest_asyncio.fixture(scope="module", autouse=True) +async def init_objects() -> None: + user_simple = await create_user_with_groups( + [], + ) + + global token_simple + token_simple = create_api_access_token(user_simple) + + user_eclair = await create_user_with_groups([GroupType.eclair]) + + global token_eclair + token_eclair = create_api_access_token(user_eclair) + + +def test_get_contacts(client: TestClient) -> None: + response = client.get( + "/contacts_safety_cards/contacts", + headers={"Authorization": f"Bearer {token_simple}"}, + ) + assert response.status_code == 200 + + +def test_set_contacts(client: TestClient) -> None: + response = client.put( + "/contacts_safety_cards/contacts/", + json=[ + { + "name": "John Doe", + "phone": "123456789", + "email": "johndoe@example.com", + "location": "Lyon", + }, + { + "name": "John Doe bis", + "phone": "323456789", + "email": "johndoebis@example.com", + "location": "Ecully", + }, + ], + headers={"Authorization": f"Bearer {token_eclair}"}, + ) + assert response.status_code == 201