Skip to content

Commit

Permalink
Вход в приложение через Твой ФФ (#84)
Browse files Browse the repository at this point in the history
* Auth with TvoyFF

* Fix tests

* CI
  • Loading branch information
dyakovri authored Mar 13, 2023
1 parent 1993f62 commit a374c74
Show file tree
Hide file tree
Showing 20 changed files with 121 additions and 128 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build_and_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}' \
Expand Down Expand Up @@ -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' \
Expand Down
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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

Expand Down
7 changes: 2 additions & 5 deletions calendar_backend/methods/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
from . import auth, utils
from . import utils


__all__ = [
"utils",
"auth",
]
__all__ = ("utils",)
41 changes: 0 additions & 41 deletions calendar_backend/methods/auth.py

This file was deleted.

27 changes: 0 additions & 27 deletions calendar_backend/routes/auth.py

This file was deleted.

11 changes: 2 additions & 9 deletions calendar_backend/routes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"ТокенАвторизацииТвойФФ"}
)
```
"""
Expand Down Expand Up @@ -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)
Expand Down
30 changes: 16 additions & 14 deletions calendar_backend/routes/event/comment.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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(
Expand All @@ -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:
Expand All @@ -39,18 +39,20 @@ 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:
raise ObjectNotFound(DbCommentEvent, id)
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)
Expand All @@ -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:
Expand Down
6 changes: 3 additions & 3 deletions calendar_backend/routes/event/comment_review.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
Expand All @@ -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:
Expand Down
17 changes: 11 additions & 6 deletions calendar_backend/routes/event/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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", [])]
Expand All @@ -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()
Expand All @@ -128,21 +131,23 @@ 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)


@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()
8 changes: 4 additions & 4 deletions calendar_backend/routes/group/group.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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())
Expand All @@ -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()
6 changes: 4 additions & 2 deletions calendar_backend/routes/lecturer/comment.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit a374c74

Please sign in to comment.