Skip to content

Commit 368e7bc

Browse files
committed
adjust CAS api usage & do some slight refactoring
1 parent c253fe1 commit 368e7bc

File tree

9 files changed

+81
-71
lines changed

9 files changed

+81
-71
lines changed

src/auth/types.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ class SessionType:
1111
ALUMNI = "alumni"
1212
SFU = "sfu"
1313

14+
@staticmethod
15+
def valid_session_type_list():
16+
# values taken from https://www.sfu.ca/information-systems/services/cas/cas-for-web-applications.html
17+
return [
18+
"faculty",
19+
"student",
20+
"alumni",
21+
"sfu"
22+
]
23+
1424
@dataclass
1525
class SiteUserData:
1626
computing_id: str

src/auth/urls.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@ async def login_user(
4747
# verify the ticket is valid
4848
service = urllib.parse.quote(f"{FRONTEND_ROOT_URL}/api/auth/login?redirect_path={redirect_path}&redirect_fragment={redirect_fragment}")
4949
service_validate_url = f"https://cas.sfu.ca/cas/serviceValidate?service={service}&ticket={ticket}"
50-
cas_response = xmltodict.parse(requests.get(service_validate_url).text)
50+
cas_response_text = requests.get(service_validate_url).text
51+
cas_response = xmltodict.parse(cas_response_text)
52+
53+
print("CAS RESPONSE ::")
54+
print(cas_response_text)
5155

5256
if "cas:authenticationFailure" in cas_response["cas:serviceResponse"]:
5357
_logger.info(f"User failed to login, with response {cas_response}")
@@ -65,14 +69,19 @@ async def login_user(
6569
if cas_response["cas:serviceResponse"]["cas:authenticationSuccess"]["cas:maillist"] == "cmpt-students":
6670
session_type = SessionType.CSSS_MEMBER
6771
else:
68-
raise HTTPException(status_code=500, details="malformed authentication response; this is an SFU CAS error")
72+
raise HTTPException(status_code=500, details="malformed cas:maillist authentication response; this is an SFU CAS error")
6973
elif "cas:authtype" in cas_response["cas:serviceResponse"]["cas:authenticationSuccess"]:
7074
# sfu, alumni, faculty, student
7175
session_type = cas_response["cas:serviceResponse"]["cas:authenticationSuccess"]["cas:authtype"]
72-
if session_type not in SessionType.value_list():
76+
if session_type not in SessionType.valid_session_type_list():
7377
raise HTTPException(status_code=500, detail=f"unexpected session type from SFU CAS of {session_type}")
78+
79+
if session_type == "alumni":
80+
if "@" not in computing_id:
81+
raise HTTPException(status_code=500, detail=f"invalid alumni computing_id response from CAS AUTH with value {session_type}")
82+
computing_id = computing_id.split("@")[0]
7483
else:
75-
raise HTTPException(status_code=500, detail="malformed authentication response; this is an SFU CAS error")
84+
raise HTTPException(status_code=500, detail="malformed unknown authentication response; this is an SFU CAS error")
7685

7786
await crud.create_user_session(db_session, session_id, computing_id, session_type)
7887
await db_session.commit()

src/auth/utils.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from fastapi import HTTPException, Request
2+
3+
import auth.crud
4+
import database
5+
6+
7+
async def logged_in_or_raise(
8+
request: Request,
9+
db_session: database.DBSession
10+
) -> tuple[str, str]:
11+
"""gets the user's computing_id, or raises an exception if the current request is not logged in"""
12+
session_id = request.cookies.get("session_id", None)
13+
if session_id is None:
14+
raise HTTPException(status_code=401)
15+
16+
session_computing_id = await auth.crud.get_computing_id(db_session, session_id)
17+
if session_computing_id is None:
18+
raise HTTPException(status_code=401)
19+
20+
return session_id, session_computing_id

src/exambank/crud.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ class ExamMetadata:
1010
course_id: str
1111

1212
async def create_exam():
13-
# TODO: these are for admins to run manually
13+
# for admins to run manually
14+
# TODO: implement this later; for now just upload data manually
1415
pass
1516

1617
async def update_exam():
17-
# TODO: these are for admins to run manually
18+
# for admins to run manually
19+
# TODO: implement this later; for now just upload data manually
1820
pass
1921

2022
async def all_exams(

src/exambank/tables.py

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from datetime import datetime
2+
from types import ExamKind
23

34
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text
45
from sqlalchemy.orm import relationship
@@ -8,15 +9,6 @@
89

910
# TODO: determine what info will need to be in the spreadsheet, then moved here
1011

11-
# TODO: move this to types.py
12-
class ExamKind:
13-
FINAL = "final"
14-
MIDTERM = "midterm"
15-
QUIZ = "quiz"
16-
ASSIGNMENT = "assignment"
17-
NOTES = "notes"
18-
MISC = "misc"
19-
2012
class ExamMetadata(Base):
2113
__tablename__ = "exam_metadata"
2214

@@ -41,16 +33,16 @@ class ExamMetadata(Base):
4133
class Professor(Base):
4234
__tablename__ = "professor"
4335

44-
professor_id = Column(Integer, primary_key=True, autoincrement=True)
45-
name = Column(String(64), nullable=False)
46-
info_url = Column(String(128), nullable=False)
47-
4836
computing_id = Column(
4937
String(COMPUTING_ID_LEN),
38+
ForeignKey("user_session.computing_id"),
39+
primary_key=True,
5040
# Foreign key constriant w/ users table
51-
#ForeignKey("user_session.computing_id"),
5241
nullable=True,
5342
)
5443

44+
name = Column(String(64), nullable=False)
45+
info_url = Column(String(128), nullable=False)
46+
5547
# TODO: eventually implement a table for courses & course info; hook it in with the rest of the site & coursys api
5648

src/exambank/types.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class ExamKind:
2+
FINAL = "final"
3+
MIDTERM = "midterm"
4+
QUIZ = "quiz"
5+
ASSIGNMENT = "assignment"
6+
NOTES = "notes"
7+
MISC = "misc"

src/exambank/urls.py

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import os
2-
from typing import Optional
32

43
from fastapi import APIRouter, HTTPException, JSONResponse, Request, Response
54

6-
import auth.crud
75
import database
86
import exambank.crud
7+
from auth.utils import logged_in_or_raise
98
from exambank.watermark import apply_watermark, create_watermark, raster_pdf
109
from permission.types import ExamBankAccess
1110
from utils import path_in_dir
@@ -17,7 +16,7 @@
1716
tags=["exam-bank"],
1817
)
1918

20-
# TODO: update endpoints to use crud functions
19+
# TODO: update endpoints to use crud functions -> don't use crud actually; refactor to do that later
2120

2221
@router.get(
2322
"/list/exams"
@@ -34,17 +33,6 @@ async def all_exams(
3433
exam_list = exambank.crud.all_exams(db_session, course_id_starts_with)
3534
return JSONResponse([exam.serializable_dict() for exam in exam_list])
3635

37-
@router.get(
38-
"/list/courses"
39-
)
40-
async def all_courses(
41-
_request: Request,
42-
_db_session: database.DBSession,
43-
):
44-
# TODO: replace this with a table eventually
45-
courses = [f.name for f in os.scandir(f"{EXAM_BANK_DIR}") if f.is_dir()]
46-
return JSONResponse(courses)
47-
4836
@router.get(
4937
"/get/{exam_id}"
5038
)
@@ -53,17 +41,8 @@ async def get_exam(
5341
db_session: database.DBSession,
5442
exam_id: int,
5543
):
56-
session_id = request.cookies.get("session_id", None)
57-
if session_id is None:
58-
raise HTTPException(status_code=401)
59-
60-
computing_id = await auth.crud.get_computing_id(db_session, session_id)
61-
if computing_id is None:
62-
raise HTTPException(status_code=401)
63-
64-
# TODO: clean this checking into one function & one computing_id check
65-
if not await ExamBankAccess.has_permission(request):
66-
raise HTTPException(status_code=401, detail="user must have exam bank access permission")
44+
_, session_computing_id = await logged_in_or_raise(request, db_session)
45+
await ExamBankAccess.has_permission_or_raise(request, errmsg="user must have exam bank access permission")
6746

6847
# number exams with an exam_id pkey
6948
# TODO: store resource locations in a db table & simply look them up
@@ -77,10 +56,9 @@ async def get_exam(
7756
raise HTTPException(status_code=500, detail="Found dangerous pdf path, exiting")
7857

7958
# TODO: test this works nicely
80-
watermark = create_watermark(computing_id, 20)
59+
watermark = create_watermark(session_computing_id, 20)
8160
watermarked_pdf = apply_watermark(exam_path, watermark)
8261
image_bytes = raster_pdf(watermarked_pdf)
8362

84-
headers = { "Content-Disposition": f'inline; filename="{meta.course_id}_{exam_id}_{computing_id}.pdf"' }
63+
headers = { "Content-Disposition": f'inline; filename="{meta.course_id}_{exam_id}_{session_computing_id}.pdf"' }
8564
return Response(content=image_bytes, headers=headers, media_type="application/pdf")
86-

src/officers/urls.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import database
88
import officers.crud
99
import utils
10+
from auth.utils import logged_in_or_raise
1011
from officers.tables import OfficerInfo, OfficerTerm
1112
from officers.types import InitialOfficerInfo, OfficerInfoUpload, OfficerTermUpload
1213
from permission.types import OfficerPrivateInfo, WebsiteAdmin
@@ -37,21 +38,6 @@ async def has_officer_private_info_access(
3738
has_private_access = await OfficerPrivateInfo.has_permission(db_session, computing_id)
3839
return session_id, computing_id, has_private_access
3940

40-
async def logged_in_or_raise(
41-
request: Request,
42-
db_session: database.DBSession
43-
) -> tuple[str, str]:
44-
"""gets the user's computing_id, or raises an exception if the current request is not logged in"""
45-
session_id = request.cookies.get("session_id", None)
46-
if session_id is None:
47-
raise HTTPException(status_code=401)
48-
49-
session_computing_id = await auth.crud.get_computing_id(db_session, session_id)
50-
if session_computing_id is None:
51-
raise HTTPException(status_code=401)
52-
53-
return session_id, session_computing_id
54-
5541
# ---------------------------------------- #
5642
# endpoints
5743

src/permission/types.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,18 @@ async def has_permission(db_session: database.DBSession, computing_id: str) -> b
5151
return False
5252

5353
@staticmethod
54-
async def validate_request(db_session: database.DBSession, request: Request) -> bool:
54+
async def validate_request(db_session: database.DBSession, request: Request):
5555
"""
5656
Checks if the provided request satisfies these permissions, and raises the neccessary
5757
exceptions if not
5858
"""
59-
# TODO: does this function return bool???
6059
session_id = request.cookies.get("session_id", None)
6160
if session_id is None:
6261
raise HTTPException(status_code=401, detail="must be logged in")
63-
else:
64-
computing_id = await auth.crud.get_computing_id(db_session, session_id)
65-
if not await WebsiteAdmin.has_permission(db_session, computing_id):
66-
raise HTTPException(status_code=401, detail="must have website admin permissions")
62+
63+
computing_id = await auth.crud.get_computing_id(db_session, session_id)
64+
if not await WebsiteAdmin.has_permission(db_session, computing_id):
65+
raise HTTPException(status_code=401, detail="must have website admin permissions")
6766

6867
@staticmethod
6968
async def has_permission_or_raise(
@@ -84,11 +83,18 @@ async def has_permission(
8483
if session_id is None:
8584
return False
8685

87-
# TODO: allow CSSS officers to access the exam bank, in addition to faculty
88-
8986
if await auth.crud.get_session_type(db_session, session_id) == SessionType.FACULTY:
9087
return True
9188

9289
# the only non-faculty who can view exams are website admins
9390
computing_id = await auth.crud.get_computing_id(db_session, session_id)
9491
return await WebsiteAdmin.has_permission(db_session, computing_id)
92+
93+
@staticmethod
94+
async def has_permission_or_raise(
95+
db_session: database.DBSession,
96+
request: Request,
97+
errmsg: str = "must have exam bank access permissions"
98+
):
99+
if not await ExamBankAccess.has_permission(db_session, request):
100+
raise HTTPException(status_code=401, detail=errmsg)

0 commit comments

Comments
 (0)