diff --git a/backend/src/appointment/database/repo/schedule.py b/backend/src/appointment/database/repo/schedule.py index 9a6716e94..a9dfcdac0 100644 --- a/backend/src/appointment/database/repo/schedule.py +++ b/backend/src/appointment/database/repo/schedule.py @@ -121,7 +121,6 @@ def verify_link(db: Session, url: str) -> models.Subscriber | None: """Verifies that an url belongs to a subscriber's schedule, and if so return the subscriber. Otherwise, return none.""" username, slug, clean_url = utils.retrieve_user_url_data(url) - subscriber = repo.subscriber.get_by_username(db, username) if not subscriber: return None diff --git a/backend/src/appointment/utils.py b/backend/src/appointment/utils.py index 52d489d07..a6d448f04 100644 --- a/backend/src/appointment/utils.py +++ b/backend/src/appointment/utils.py @@ -46,7 +46,7 @@ def setup_encryption_engine(): def retrieve_user_url_data(url): """Retrieves username, signature, and main url from ///""" - pattern = r"[\/]([\w\d\-_\.\@!]+)[\/]?([\w\d]*)[\/]?$" + pattern = r"[\/]([\w\d\-_\.\@!]+)[\/]?([\w\d\-_\.\@!]*)[\/]?$" match = re.findall(pattern, url) if match is None or len(match) == 0: diff --git a/backend/test/factory/schedule_factory.py b/backend/test/factory/schedule_factory.py index c53579133..88d448938 100644 --- a/backend/test/factory/schedule_factory.py +++ b/backend/test/factory/schedule_factory.py @@ -23,6 +23,7 @@ def _make_schedule(calendar_id=FAKER_RANDOM_VALUE, weekdays=[1,2,3,4,5], slot_duration=FAKER_RANDOM_VALUE, meeting_link_provider=models.MeetingLinkProviderType.none, + slug=FAKER_RANDOM_VALUE, ): with with_db() as db: return repo.schedule.create(db, schemas.ScheduleBase( @@ -41,6 +42,7 @@ def _make_schedule(calendar_id=FAKER_RANDOM_VALUE, weekdays=weekdays, slot_duration=slot_duration if factory_has_value(slot_duration) else fake.pyint(15, 60), meeting_link_provider=meeting_link_provider, + slug=slug if factory_has_value(slug) else fake.uuid4(), calendar_id=calendar_id if factory_has_value(calendar_id) else make_caldav_calendar(connected=True).id )) diff --git a/backend/test/unit/test_auth_dependency.py b/backend/test/unit/test_auth_dependency.py index 4bf26a22d..1405df508 100644 --- a/backend/test/unit/test_auth_dependency.py +++ b/backend/test/unit/test_auth_dependency.py @@ -4,9 +4,11 @@ import pytest from freezegun import freeze_time +from appointment.controller.auth import signed_url_by_subscriber from appointment.database import repo -from appointment.dependencies.auth import get_user_from_token -from appointment.exceptions.validation import InvalidTokenException +from appointment.dependencies.auth import get_user_from_token, get_subscriber, get_admin_subscriber, \ + get_subscriber_from_schedule_or_signed_url +from appointment.exceptions.validation import InvalidTokenException, InvalidPermissionLevelException from appointment.routes.auth import create_access_token @@ -61,3 +63,77 @@ def test_get_user_from_token(self, with_db, with_l10n, make_pro_subscriber): # Internally raises ExpiredSignatureError, but we catch it and send a HTTPException instead. with pytest.raises(InvalidTokenException): get_user_from_token(db, access_token) + + def test_get_subscriber(self, with_db, with_l10n, make_pro_subscriber): + subscriber = make_pro_subscriber() + access_token = create_access_token(data={"sub": f"uid-{subscriber.id}"}) + + with with_db() as db: + retrieved_subscriber = get_subscriber(access_token, db) + + assert retrieved_subscriber.id == subscriber.id + assert retrieved_subscriber.email == subscriber.email + + def test_get_subscriber_with_invalid_token(self, with_db, with_l10n, make_pro_subscriber): + subscriber = make_pro_subscriber() + + with with_db() as db: + with pytest.raises(InvalidTokenException): + # Use a nonsense value, like the subscriber id! + get_subscriber(subscriber.id, db) + + def test_get_admin_subscriber(self, with_db, with_l10n, make_pro_subscriber): + subscriber = make_pro_subscriber() + access_token = create_access_token(data={"sub": f"uid-{subscriber.id}"}) + + os.environ['APP_ADMIN_ALLOW_LIST'] = subscriber.email + + with with_db() as db: + retrieved_subscriber = get_admin_subscriber(get_subscriber(access_token, db)) + + assert retrieved_subscriber.id == subscriber.id + assert retrieved_subscriber.email == subscriber.email + + def test_get_admin_subscriber_fails_with_no_allow_list(self, with_db, with_l10n, make_pro_subscriber): + subscriber = make_pro_subscriber() + access_token = create_access_token(data={"sub": f"uid-{subscriber.id}"}) + + os.environ['APP_ADMIN_ALLOW_LIST'] = '' + + with with_db() as db: + with pytest.raises(InvalidPermissionLevelException): + get_admin_subscriber(get_subscriber(access_token, db)) + + def test_get_admin_subscriber_fails_not_in_allow_list(self, with_db, with_l10n, make_pro_subscriber): + subscriber = make_pro_subscriber(email='cool-beans@example.org') + access_token = create_access_token(data={"sub": f"uid-{subscriber.id}"}) + + os.environ['APP_ADMIN_ALLOW_LIST'] = '@notexample.org' + + with with_db() as db: + with pytest.raises(InvalidPermissionLevelException): + get_admin_subscriber(get_subscriber(access_token, db)) + + def test_get_subscriber_from_schedule_or_signed_url_with_signed_url(self, with_db, with_l10n, make_pro_subscriber): + subscriber = make_pro_subscriber() + + with with_db() as db: + signed_url = signed_url_by_subscriber(subscriber) + retrieved_subscriber = get_subscriber_from_schedule_or_signed_url(signed_url, db) + + assert retrieved_subscriber.id == subscriber.id + assert retrieved_subscriber.email == subscriber.email + + def test_get_subscriber_from_schedule_or_signed_url_with_schedule_slug(self, with_db, with_l10n, + make_pro_subscriber, make_schedule, + make_caldav_calendar): + subscriber = make_pro_subscriber() + calendar = make_caldav_calendar(subscriber_id=subscriber.id) + schedule = make_schedule(calendar_id=calendar.id) + + with with_db() as db: + url = f"https://apmt.day/{subscriber.username}/{schedule.slug}/" + retrieved_subscriber = get_subscriber_from_schedule_or_signed_url(url, db) + + assert retrieved_subscriber.id == subscriber.id + assert retrieved_subscriber.email == subscriber.email