From a374c74cd960941100f6c923ff9c3ff706a1ed09 Mon Sep 17 00:00:00 2001 From: Dyakov Roman Date: Mon, 13 Mar 2023 22:17:32 +0300 Subject: [PATCH] =?UTF-8?q?=D0=92=D1=85=D0=BE=D0=B4=20=D0=B2=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=87?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B7=20=D0=A2=D0=B2=D0=BE=D0=B9=20=D0=A4?= =?UTF-8?q?=D0=A4=20(#84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Auth with TvoyFF * Fix tests * CI --- .github/workflows/build_and_publish.yml | 2 + Makefile | 11 +++++ calendar_backend/methods/__init__.py | 7 +--- calendar_backend/methods/auth.py | 41 ------------------- calendar_backend/routes/auth.py | 27 ------------ calendar_backend/routes/base.py | 11 +---- calendar_backend/routes/event/comment.py | 30 +++++++------- .../routes/event/comment_review.py | 6 +-- calendar_backend/routes/event/event.py | 17 +++++--- calendar_backend/routes/group/group.py | 8 ++-- calendar_backend/routes/lecturer/comment.py | 6 ++- .../routes/lecturer/comment_review.py | 6 +-- calendar_backend/routes/lecturer/lecturer.py | 10 +++-- .../routes/lecturer/photo_review.py | 8 ++-- calendar_backend/routes/models/__init__.py | 33 +++++++++++++++ calendar_backend/routes/room/room.py | 8 ++-- calendar_backend/settings.py | 3 +- requirements.dev.txt | 3 ++ requirements.txt | 1 + tests/conftest.py | 11 ++++- 20 files changed, 121 insertions(+), 128 deletions(-) delete mode 100644 calendar_backend/methods/auth.py delete mode 100644 calendar_backend/routes/auth.py diff --git a/.github/workflows/build_and_publish.yml b/.github/workflows/build_and_publish.yml index 97eb29a0..55f24e64 100644 --- a/.github/workflows/build_and_publish.yml +++ b/.github/workflows/build_and_publish.yml @@ -87,6 +87,7 @@ jobs: --network=web \ --volume com_profcomff_api_timetable_test_static:/app/static \ --env DB_DSN=${{ secrets.DB_DSN }} \ + --env AUTH_URL='https://api.test.profcomff.com/auth' \ --env ROOT_PATH='/timetable' \ --env GUNICORN_CMD_ARGS='--log-config logging_test.conf' \ --env GOOGLE_CLIENT_SECRET='${{ secrets.GOOGLE_CLIENT_SECRET }}' \ @@ -132,6 +133,7 @@ jobs: --network=web \ --volume com_profcomff_api_timetable_static:/app/static \ --env DB_DSN='${{ secrets.DB_DSN }}' \ + --env AUTH_URL='https://api.profcomff.com/auth' \ --env ROOT_PATH='/timetable' \ --env ADMIN_SECRET='${{ secrets.ADMIN_SECRET }}' \ --env REDIRECT_URL='https://www.profcomff.com/timetable/google' \ diff --git a/Makefile b/Makefile index 62018ac6..db633b1b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,17 @@ run: source ./venv/bin/activate && uvicorn --reload --log-level debug calendar_backend.routes.base:app +configure: venv + source ./venv/bin/activate && pip install -r requirements.dev.txt -r requirements.txt + +venv: + python3.11 -m venv venv + +format: + autoflake -r --in-place --remove-all-unused-imports ./calendar_backend + isort ./calendar_backend + black ./calendar_backend + db: docker run -d -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust --name db-timetable_api postgres:15 diff --git a/calendar_backend/methods/__init__.py b/calendar_backend/methods/__init__.py index 9fc6b6f1..e5f0de92 100644 --- a/calendar_backend/methods/__init__.py +++ b/calendar_backend/methods/__init__.py @@ -1,7 +1,4 @@ -from . import auth, utils +from . import utils -__all__ = [ - "utils", - "auth", -] +__all__ = ("utils",) diff --git a/calendar_backend/methods/auth.py b/calendar_backend/methods/auth.py deleted file mode 100644 index 40f227b5..00000000 --- a/calendar_backend/methods/auth.py +++ /dev/null @@ -1,41 +0,0 @@ -import logging - -from fastapi import Depends, HTTPException, status -from fastapi.security import OAuth2PasswordBearer -from pydantic import BaseModel - -from calendar_backend.settings import get_settings - - -logger = logging.getLogger(__name__) -settings = get_settings() -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - - -class User(BaseModel): - username: str - - -class UserInDB(User): - password: str - - -def get_user(db, token: str): - if token in db.values(): - password = token - for k, v in db.items(): - if v == token: - username = k - return UserInDB(username=username, password=password) - - -async def get_current_user(token: str = Depends(oauth2_scheme)): - user = get_user(settings.ADMIN_SECRET, token) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid authentication credentials", - headers={"WWW-Authenticate": "Bearer"}, - ) - logger.info("User auth successfull %s", user.username) - return user.username diff --git a/calendar_backend/routes/auth.py b/calendar_backend/routes/auth.py deleted file mode 100644 index b86ebd7c..00000000 --- a/calendar_backend/routes/auth.py +++ /dev/null @@ -1,27 +0,0 @@ -"""DEPRICATED TODO: Drop 2023-04-01 -""" -import logging - -from fastapi import APIRouter, Depends, HTTPException -from fastapi.security import OAuth2PasswordRequestForm - -from calendar_backend.methods import auth -from calendar_backend.settings import get_settings - - -settings = get_settings() -logger = logging.getLogger(__name__) -# DEPRICATED TODO: Drop 2023-04-01 -auth_router = APIRouter(prefix="", tags=["Utils: Auth"], deprecated=True) - - -@auth_router.post("/token") -async def login(form_data: OAuth2PasswordRequestForm = Depends()): - password = settings.ADMIN_SECRET.get(form_data.username) - if not password: - raise HTTPException(status_code=400, detail="Incorrect username or password") - user = auth.UserInDB(username=form_data.username, password=password) - hashed_password = form_data.password - if not hashed_password == user.password: - raise HTTPException(status_code=400, detail="Incorrect username or password") - return {"access_token": user.password, "token_type": "bearer"} diff --git a/calendar_backend/routes/base.py b/calendar_backend/routes/base.py index 0a0c3721..ff3b06cd 100644 --- a/calendar_backend/routes/base.py +++ b/calendar_backend/routes/base.py @@ -17,7 +17,6 @@ from calendar_backend.exceptions import ForbiddenAction, NotEnoughCriteria, ObjectNotFound from calendar_backend.settings import get_settings -from .auth import auth_router # TODO: Replace with PKFF Auth from .event import event_comment_review_router as old_event_comment_review_router # DEPRICATED TODO: Drop 2023-04-01 from .event import event_comment_router as old_event_comment_router # DEPRICATED TODO: Drop 2023-04-01 from .event import event_router as old_event_router # DEPRICATED TODO: Drop 2023-04-01 @@ -52,22 +51,17 @@ description=dedent( """ API для работы с календарем физфака. + Пример работы на питоне(Создание Room): ```python import reqests, json url=f"https://api.test.profcomff.com/timetable" - # Авторизация - beaver = requests.post(f"{url}/token", {"username": "...", "password": "..."}) - - # Парсинг ответа - auth_data=json.loads(beaver.content) - # Создание create_room = requests.post( f"{url}/room", json={"name": "test", "direction": "South"}, - headers={"Authorization": f"Bearer {auth_data.get('access_token')}"} + headers={"Authorization": f"ТокенАвторизацииТвойФФ"} ) ``` """ @@ -141,7 +135,6 @@ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) - # region DEPRICATED # TODO: Drop 2023-04-01 app.include_router(gcal) -app.include_router(auth_router) app.include_router(old_lecturer_router) app.include_router(old_lecturer_comment_router) app.include_router(old_lecturer_comment_review_router) diff --git a/calendar_backend/routes/event/comment.py b/calendar_backend/routes/event/comment.py index d0bc4089..d5df64b5 100644 --- a/calendar_backend/routes/event/comment.py +++ b/calendar_backend/routes/event/comment.py @@ -1,8 +1,8 @@ +from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends from fastapi_sqlalchemy import db from calendar_backend.exceptions import ForbiddenAction, ObjectNotFound -from calendar_backend.methods import auth from calendar_backend.models import ApproveStatuses from calendar_backend.models import CommentEvent as DbCommentEvent from calendar_backend.routes.models.event import CommentEventGet, EventCommentPatch, EventCommentPost, EventComments @@ -11,12 +11,12 @@ settings = get_settings() # DEPRICATED TODO: Drop 2023-04-01 -event_comment_router = APIRouter(prefix="/timetable/event/{event_id}", tags=["Event: Comment"], deprecated=True) -router = APIRouter(prefix="/event/{event_id}", tags=["Event: Comment"]) +event_comment_router = APIRouter(prefix="/timetable/event/{event_id}/comment", tags=["Event: Comment"], deprecated=True) +router = APIRouter(prefix="/event/{event_id}/comment", tags=["Event: Comment"]) -@event_comment_router.post("/comment/", response_model=CommentEventGet) # DEPRICATED TODO: Drop 2023-04-01 -@router.post("/comment/", response_model=CommentEventGet) +@event_comment_router.post("/", response_model=CommentEventGet) # DEPRICATED TODO: Drop 2023-04-01 +@router.post("/", response_model=CommentEventGet) async def comment_event(event_id: int, comment: EventCommentPost) -> CommentEventGet: approve_status = ApproveStatuses.APPROVED if not settings.REQUIRE_REVIEW_EVENT_COMMENT else ApproveStatuses.PENDING comment_event = DbCommentEvent.create( @@ -26,8 +26,8 @@ async def comment_event(event_id: int, comment: EventCommentPost) -> CommentEven return CommentEventGet.from_orm(comment_event) -@event_comment_router.patch("/comment/{id}", response_model=CommentEventGet) # DEPRICATED TODO: Drop 2023-04-01 -@router.patch("/comment/{id}", response_model=CommentEventGet) +@event_comment_router.patch("/{id}", response_model=CommentEventGet) # DEPRICATED TODO: Drop 2023-04-01 +@router.patch("/{id}", response_model=CommentEventGet) async def update_comment(id: int, event_id: int, comment_inp: EventCommentPatch) -> CommentEventGet: comment = DbCommentEvent.get(id, only_approved=False, session=db.session) if comment.event_id != event_id: @@ -39,8 +39,8 @@ async def update_comment(id: int, event_id: int, comment_inp: EventCommentPatch) return CommentEventGet.from_orm(comment_event) -@event_comment_router.get("/comment/{id}", response_model=CommentEventGet) # DEPRICATED TODO: Drop 2023-04-01 -@router.get("/comment/{id}", response_model=CommentEventGet) +@event_comment_router.get("/{id}", response_model=CommentEventGet) # DEPRICATED TODO: Drop 2023-04-01 +@router.get("/{id}", response_model=CommentEventGet) async def get_comment(id: int, event_id: int) -> CommentEventGet: comment = DbCommentEvent.get(id, session=db.session) if not comment.event_id == event_id or comment.approve_status != ApproveStatuses.APPROVED: @@ -48,9 +48,11 @@ async def get_comment(id: int, event_id: int) -> CommentEventGet: return CommentEventGet.from_orm(comment) -@event_comment_router.delete("/comment/{id}", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 -@router.delete("/comment/{id}", response_model=None) -async def delete_comment(id: int, event_id: int, _: auth.User = Depends(auth.get_current_user)) -> None: +@event_comment_router.delete("/{id}", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 +@router.delete("/{id}", response_model=None) +async def delete_comment( + id: int, event_id: int, _=Depends(UnionAuth(scopes=["timetable.event.comment.delete"])) +) -> None: comment = DbCommentEvent.get(id, only_approved=False, session=db.session) if comment.event_id != event_id or comment.approve_status != ApproveStatuses.APPROVED: raise ObjectNotFound(DbCommentEvent, id) @@ -59,8 +61,8 @@ async def delete_comment(id: int, event_id: int, _: auth.User = Depends(auth.get return None -@event_comment_router.get("/comment/", response_model=EventComments) # DEPRICATED TODO: Drop 2023-04-01 -@router.get("/comment/", response_model=EventComments) +@event_comment_router.get("/", response_model=EventComments) # DEPRICATED TODO: Drop 2023-04-01 +@router.get("/", response_model=EventComments) async def get_event_comments(event_id: int, limit: int = 10, offset: int = 0) -> EventComments: res = DbCommentEvent.get_all(session=db.session).filter(DbCommentEvent.event_id == event_id) if limit: diff --git a/calendar_backend/routes/event/comment_review.py b/calendar_backend/routes/event/comment_review.py index 81b8b308..e5ad4743 100644 --- a/calendar_backend/routes/event/comment_review.py +++ b/calendar_backend/routes/event/comment_review.py @@ -1,11 +1,11 @@ from typing import Literal +from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends from fastapi_sqlalchemy import db from pydantic import parse_obj_as from calendar_backend.exceptions import ObjectNotFound -from calendar_backend.methods import auth from calendar_backend.models import ApproveStatuses from calendar_backend.models import CommentEvent as DbCommentEvent from calendar_backend.routes.models.event import CommentEventGet @@ -23,7 +23,7 @@ @event_comment_review_router.get("/review/", response_model=list[CommentEventGet]) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/review/", response_model=list[CommentEventGet]) async def get_unreviewed_comments( - event_id: int, _: auth.User = Depends(auth.get_current_user) + event_id: int, _=Depends(UnionAuth(scopes=["timetable.event.comment.review"])) ) -> list[CommentEventGet]: comments = ( DbCommentEvent.get_all(session=db.session, only_approved=False) @@ -39,7 +39,7 @@ async def review_comment( id: int, event_id: int, action: Literal[ApproveStatuses.APPROVED, ApproveStatuses.DECLINED] = ApproveStatuses.DECLINED, - _: auth.User = Depends(auth.get_current_user), + _=Depends(UnionAuth(scopes=["timetable.event.comment.review"])), ) -> CommentEventGet: comment = DbCommentEvent.get(id, only_approved=False, session=db.session) if comment.event_id != event_id or comment.approve_status is not ApproveStatuses.PENDING: diff --git a/calendar_backend/routes/event/event.py b/calendar_backend/routes/event/event.py index 16b22cc5..84a2cc8b 100644 --- a/calendar_backend/routes/event/event.py +++ b/calendar_backend/routes/event/event.py @@ -2,13 +2,14 @@ from datetime import date, timedelta from typing import Literal +from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends, Query from fastapi.responses import FileResponse from fastapi_sqlalchemy import db from pydantic import parse_obj_as from calendar_backend.exceptions import NotEnoughCriteria -from calendar_backend.methods import auth, list_calendar +from calendar_backend.methods import list_calendar from calendar_backend.models import Event, EventsLecturers, EventsRooms, Group, Lecturer, Room from calendar_backend.routes.models import EventGet from calendar_backend.routes.models.event import EventPatch, EventPost, GetListEvent @@ -88,7 +89,7 @@ async def get_events( @event_router.post("/", response_model=EventGet) # DEPRICATED TODO: Drop 2023-04-01 @router.post("/", response_model=EventGet) -async def create_event(event: EventPost, _: auth.User = Depends(auth.get_current_user)) -> EventGet: +async def create_event(event: EventPost, _=Depends(UnionAuth(scopes=["timetable.event.create"]))) -> EventGet: event_dict = event.dict() rooms = [Room.get(room_id, session=db.session) for room_id in event_dict.pop("room_id", [])] lecturers = [Lecturer.get(lecturer_id, session=db.session) for lecturer_id in event_dict.pop("lecturer_id", [])] @@ -106,7 +107,9 @@ async def create_event(event: EventPost, _: auth.User = Depends(auth.get_current @event_router.post("/bulk", response_model=list[EventGet]) # DEPRICATED TODO: Drop 2023-04-01 @router.post("/bulk", response_model=list[EventGet]) -async def create_events(events: list[EventPost], _: auth.User = Depends(auth.get_current_user)) -> list[EventGet]: +async def create_events( + events: list[EventPost], _=Depends(UnionAuth(scopes=["timetable.event.create"])) +) -> list[EventGet]: result = [] for event in events: event_dict = event.dict() @@ -128,7 +131,9 @@ async def create_events(events: list[EventPost], _: auth.User = Depends(auth.get @event_router.patch("/{id}", response_model=EventGet) # DEPRICATED TODO: Drop 2023-04-01 @router.patch("/{id}", response_model=EventGet) -async def patch_event(id: int, event_inp: EventPatch, _: auth.User = Depends(auth.get_current_user)) -> EventGet: +async def patch_event( + id: int, event_inp: EventPatch, _=Depends(UnionAuth(scopes=["timetable.event.update"])) +) -> EventGet: patched = Event.update(id, session=db.session, **event_inp.dict(exclude_unset=True)) db.session.commit() return EventGet.from_orm(patched) @@ -136,13 +141,13 @@ async def patch_event(id: int, event_inp: EventPatch, _: auth.User = Depends(aut @event_router.delete("/bulk", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 @router.delete("/bulk", response_model=None) -async def delete_events(start: date, end: date, _: auth.User = Depends(auth.get_current_user)) -> None: +async def delete_events(start: date, end: date, _=Depends(UnionAuth(scopes=["timetable.event.delete"]))) -> None: db.session.query(Event).filter(Event.start_ts >= start, Event.end_ts < end).update(values={"is_deleted": True}) db.session.commit() @event_router.delete("/{id}", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 @router.delete("/{id}", response_model=None) -async def delete_event(id: int, _: auth.User = Depends(auth.get_current_user)) -> None: +async def delete_event(id: int, _=Depends(UnionAuth(scopes=["timetable.event.delete"]))) -> None: Event.delete(id, session=db.session) db.session.commit() diff --git a/calendar_backend/routes/group/group.py b/calendar_backend/routes/group/group.py index 996a826e..275f2cdc 100644 --- a/calendar_backend/routes/group/group.py +++ b/calendar_backend/routes/group/group.py @@ -1,9 +1,9 @@ import logging +from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends, HTTPException from fastapi_sqlalchemy import db -from calendar_backend.methods import auth from calendar_backend.models import Group from calendar_backend.routes.models import GetListGroup, GroupGet, GroupPatch, GroupPost from calendar_backend.settings import get_settings @@ -42,7 +42,7 @@ async def get_groups(query: str = "", limit: int = 10, offset: int = 0) -> GetLi @group_router.post("/", response_model=GroupGet) # DEPRICATED TODO: Drop 2023-04-01 @router.post("/", response_model=GroupGet) -async def create_group(group: GroupPost, _: auth.User = Depends(auth.get_current_user)) -> GroupGet: +async def create_group(group: GroupPost, _=Depends(UnionAuth(scopes=["timetable.group.create"]))) -> GroupGet: if db.session.query(Group).filter(Group.number == group.number).one_or_none(): raise HTTPException(status_code=423, detail="Already exists") group = Group.create(**group.dict(), session=db.session) @@ -55,7 +55,7 @@ async def create_group(group: GroupPost, _: auth.User = Depends(auth.get_current async def patch_group( id: int, group_inp: GroupPatch, - _: auth.User = Depends(auth.get_current_user), + _=Depends(UnionAuth(scopes=["timetable.group.update"])), ) -> GroupGet: if ( bool(query := Group.get_all(session=db.session).filter(Group.number == group_inp.number).one_or_none()) @@ -69,6 +69,6 @@ async def patch_group( @group_router.delete("/{id}", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 @router.delete("/{id}", response_model=None) -async def delete_group(id: int, _: auth.User = Depends(auth.get_current_user)) -> None: +async def delete_group(id: int, _=Depends(UnionAuth(scopes=["timetable.group.delete"]))) -> None: Group.delete(id, session=db.session) db.session.commit() diff --git a/calendar_backend/routes/lecturer/comment.py b/calendar_backend/routes/lecturer/comment.py index 300ebdd2..c409043b 100644 --- a/calendar_backend/routes/lecturer/comment.py +++ b/calendar_backend/routes/lecturer/comment.py @@ -1,8 +1,8 @@ +from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends from fastapi_sqlalchemy import db from calendar_backend.exceptions import ForbiddenAction, ObjectNotFound -from calendar_backend.methods import auth from calendar_backend.models.db import ApproveStatuses from calendar_backend.models.db import CommentLecturer as DbCommentLecturer from calendar_backend.routes.models import CommentLecturer, LecturerCommentPatch, LecturerCommentPost, LecturerComments @@ -48,7 +48,9 @@ async def update_comment_lecturer(id: int, lecturer_id: int, comment_inp: Lectur @lecturer_comment_router.delete("/comment/{id}", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 @router.delete("/comment/{id}", response_model=None) -async def delete_comment(id: int, lecturer_id: int, _: auth.User = Depends(auth.get_current_user)) -> None: +async def delete_comment( + id: int, lecturer_id: int, _=Depends(UnionAuth(scopes=["timetable.lecturer.comment.delete"])) +) -> None: comment = DbCommentLecturer.get(id, only_approved=False, session=db.session) if comment.lecturer_id != lecturer_id: raise ObjectNotFound(DbCommentLecturer, id) diff --git a/calendar_backend/routes/lecturer/comment_review.py b/calendar_backend/routes/lecturer/comment_review.py index e7f9be8d..f705afc5 100644 --- a/calendar_backend/routes/lecturer/comment_review.py +++ b/calendar_backend/routes/lecturer/comment_review.py @@ -1,11 +1,11 @@ from typing import Literal +from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends from fastapi_sqlalchemy import db from pydantic import parse_obj_as from calendar_backend.exceptions import ObjectNotFound -from calendar_backend.methods import auth from calendar_backend.models.db import ApproveStatuses from calendar_backend.models.db import CommentLecturer as DbCommentLecturer from calendar_backend.routes.models import CommentLecturer @@ -23,7 +23,7 @@ ) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/review/", response_model=list[CommentLecturer]) async def get_unreviewed_comments( - lecturer_id: int, _: auth.User = Depends(auth.get_current_user) + lecturer_id: int, _=Depends(UnionAuth(scopes=["timetable.lecturer.comment.review"])) ) -> list[CommentLecturer]: comments = ( DbCommentLecturer.get_all(session=db.session, only_approved=False) @@ -43,7 +43,7 @@ async def review_comment( id: int, lecturer_id: int, action: Literal[ApproveStatuses.APPROVED, ApproveStatuses.DECLINED] = ApproveStatuses.DECLINED, - _: auth.User = Depends(auth.get_current_user), + _=Depends(UnionAuth(scopes=["timetable.lecturer.comment.review"])), ) -> CommentLecturer: comment = DbCommentLecturer.get(id, only_approved=False, session=db.session) if comment.lecturer_id != lecturer_id or comment.approve_status is not ApproveStatuses.PENDING: diff --git a/calendar_backend/routes/lecturer/lecturer.py b/calendar_backend/routes/lecturer/lecturer.py index 121a18aa..606fad05 100644 --- a/calendar_backend/routes/lecturer/lecturer.py +++ b/calendar_backend/routes/lecturer/lecturer.py @@ -1,11 +1,11 @@ import logging from typing import Any +from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends from fastapi_sqlalchemy import db from calendar_backend.exceptions import ObjectNotFound -from calendar_backend.methods import auth from calendar_backend.models.db import ApproveStatuses, Lecturer from calendar_backend.models.db import Photo as DbPhoto from calendar_backend.routes.models import GetListLecturer, LecturerGet, LecturerPatch, LecturerPost @@ -53,7 +53,9 @@ async def get_lecturers( @lecturer_router.post("/", response_model=LecturerGet) # DEPRICATED TODO: Drop 2023-04-01 @router.post("/", response_model=LecturerGet) -async def create_lecturer(lecturer: LecturerPost, _: auth.User = Depends(auth.get_current_user)) -> LecturerGet: +async def create_lecturer( + lecturer: LecturerPost, _=Depends(UnionAuth(scopes=["timetable.lecturer.create"])) +) -> LecturerGet: dblecturer = Lecturer.create(session=db.session, **lecturer.dict()) db.session.commit() return LecturerGet.from_orm(dblecturer) @@ -62,7 +64,7 @@ async def create_lecturer(lecturer: LecturerPost, _: auth.User = Depends(auth.ge @lecturer_router.patch("/{id}", response_model=LecturerGet) # DEPRICATED TODO: Drop 2023-04-01 @router.patch("/{id}", response_model=LecturerGet) async def patch_lecturer( - id: int, lecturer_inp: LecturerPatch, _: auth.User = Depends(auth.get_current_user) + id: int, lecturer_inp: LecturerPatch, _=Depends(UnionAuth(scopes=["timetable.lecturer.update"])) ) -> LecturerGet: if lecturer_inp.avatar_id: photo = DbPhoto.get(lecturer_inp.avatar_id, session=db.session) @@ -79,6 +81,6 @@ async def patch_lecturer( @lecturer_router.delete("/{id}", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 @router.delete("/{id}", response_model=None) -async def delete_lecturer(id: int, _: auth.User = Depends(auth.get_current_user)) -> None: +async def delete_lecturer(id: int, _=Depends(UnionAuth(scopes=["timetable.lecturer.delete"]))) -> None: Lecturer.delete(id, session=db.session) db.session.commit() diff --git a/calendar_backend/routes/lecturer/photo_review.py b/calendar_backend/routes/lecturer/photo_review.py index e466f49d..3ad078db 100644 --- a/calendar_backend/routes/lecturer/photo_review.py +++ b/calendar_backend/routes/lecturer/photo_review.py @@ -1,9 +1,9 @@ +from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends from fastapi_sqlalchemy import db from pydantic import parse_obj_as from calendar_backend.exceptions import ObjectNotFound -from calendar_backend.methods import auth from calendar_backend.models.db import ApproveStatuses, Lecturer from calendar_backend.models.db import Photo as DbPhoto from calendar_backend.routes.models import Action, Photo @@ -18,7 +18,9 @@ @lecturer_photo_review_router.get("/review/", response_model=list[Photo]) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/review/", response_model=list[Photo]) -async def get_unreviewed_photos(lecturer_id: int, _: auth.User = Depends(auth.get_current_user)) -> list[Photo]: +async def get_unreviewed_photos( + lecturer_id: int, _=Depends(UnionAuth(scopes=["timetable.lecturer.photo.review"])) +) -> list[Photo]: photos = ( DbPhoto.get_all(session=db.session, only_approved=False) .filter(DbPhoto.lecturer_id == lecturer_id, DbPhoto.approve_status == ApproveStatuses.PENDING) @@ -33,7 +35,7 @@ async def review_photo( id: int, lecturer_id: int, action: Action, - _: auth.User = Depends(auth.get_current_user), + _=Depends(UnionAuth(scopes=["timetable.lecturer.photo.review"])), ) -> Photo: lecturer = Lecturer.get(lecturer_id, session=db.session) photo = DbPhoto.get(id, only_approved=False, session=db.session) diff --git a/calendar_backend/routes/models/__init__.py b/calendar_backend/routes/models/__init__.py index 90a5b9c5..bd80e638 100644 --- a/calendar_backend/routes/models/__init__.py +++ b/calendar_backend/routes/models/__init__.py @@ -14,3 +14,36 @@ Photo, ) from .room import GetListRoom, RoomEvents, RoomPatch, RoomPost + + +__all__ = ( + "CommentEventGet", + "CommentLecturer", + "EventGet", + "GroupGet", + "LecturerGet", + "RoomGet", + "Event", + "EventComments", + "EventPatch", + "EventPost", + "GetListEvent", + "GetListGroup", + "GroupEvents", + "GroupPatch", + "GroupPost", + "Action", + "GetListLecturer", + "LecturerCommentPatch", + "LecturerCommentPost", + "LecturerComments", + "LecturerEvents", + "LecturerPatch", + "LecturerPhotos", + "LecturerPost", + "Photo", + "GetListRoom", + "RoomEvents", + "RoomPatch", + "RoomPost", +) diff --git a/calendar_backend/routes/room/room.py b/calendar_backend/routes/room/room.py index 91e734a7..d21d8b5b 100644 --- a/calendar_backend/routes/room/room.py +++ b/calendar_backend/routes/room/room.py @@ -1,9 +1,9 @@ import logging +from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends, HTTPException from fastapi_sqlalchemy import db -from calendar_backend.methods import auth from calendar_backend.models import Room from calendar_backend.routes.models import GetListRoom, RoomGet, RoomPatch, RoomPost from calendar_backend.settings import get_settings @@ -42,7 +42,7 @@ async def get_rooms(query: str = "", limit: int = 10, offset: int = 0) -> GetLis @room_router.post("/", response_model=RoomGet) # DEPRICATED TODO: Drop 2023-04-01 @router.post("/", response_model=RoomGet) -async def create_room(room: RoomPost, _: auth.User = Depends(auth.get_current_user)) -> RoomGet: +async def create_room(room: RoomPost, _=Depends(UnionAuth(scopes=["timetable.room.create"]))) -> RoomGet: if bool( Room.get_all(session=db.session).filter(Room.name == room.name, Room.building == room.building).one_or_none() ): @@ -54,7 +54,7 @@ async def create_room(room: RoomPost, _: auth.User = Depends(auth.get_current_us @room_router.patch("/{id}", response_model=RoomGet) # DEPRICATED TODO: Drop 2023-04-01 @router.patch("/{id}", response_model=RoomGet) -async def patch_room(id: int, room_inp: RoomPatch, _: auth.User = Depends(auth.get_current_user)) -> RoomGet: +async def patch_room(id: int, room_inp: RoomPatch, _=Depends(UnionAuth(scopes=["timetable.room.upadte"]))) -> RoomGet: room = ( Room.get_all(session=db.session) .filter(Room.name == room_inp.name, Room.building == room_inp.building) @@ -69,6 +69,6 @@ async def patch_room(id: int, room_inp: RoomPatch, _: auth.User = Depends(auth.g @room_router.delete("/{id}", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 @router.delete("/{id}", response_model=None) -async def delete_room(id: int, _: auth.User = Depends(auth.get_current_user)) -> None: +async def delete_room(id: int, _=Depends(UnionAuth(scopes=["timetable.room.delete"]))) -> None: Room.delete(id, session=db.session) db.session.commit() diff --git a/calendar_backend/settings.py b/calendar_backend/settings.py index ec10723e..110380d7 100644 --- a/calendar_backend/settings.py +++ b/calendar_backend/settings.py @@ -1,10 +1,11 @@ import os from functools import lru_cache +from auth_lib.fastapi import UnionAuthSettings from pydantic import AnyHttpUrl, BaseSettings, DirectoryPath, Json, PostgresDsn -class Settings(BaseSettings): +class Settings(UnionAuthSettings, BaseSettings): """Application settings""" DB_DSN: PostgresDsn = 'postgresql://postgres@localhost:5432/postgres' diff --git a/requirements.dev.txt b/requirements.dev.txt index 831ad9fe..15533c0f 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -2,3 +2,6 @@ pytest pytest-cov requests pytest-mock +black +isort +autoflake diff --git a/requirements.txt b/requirements.txt index 506a4c4e..4164cd9e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,4 @@ python-multipart httpx Pillow logging-profcomff +auth-lib-profcomff[fastapi] diff --git a/tests/conftest.py b/tests/conftest.py index b4fa5516..12a663a3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,9 +22,16 @@ def client(): @pytest.fixture() def client_auth(mocker: MockerFixture): + user_mock = mocker.patch('auth_lib.fastapi.UnionAuth.__call__') + user_mock.return_value = { + "session_scopes": [{"id": 0, "name": "string", "comment": "string"}], + "user_scopes": [{"id": 0, "name": "string", "comment": "string"}], + "indirect_groups": [{"id": 0, "name": "string", "parent_id": 0}], + "groups": [{"id": 0, "name": "string", "parent_id": 0}], + "id": 0, + "email": "string", + } client = TestClient(app) - access_token = client.post(f"/token", data={"username": "admin", "password": "42"}).json()["access_token"] - client.headers = {"Authorization": f"Bearer {access_token}"} return client