Skip to content

Commit 79f1236

Browse files
Merge pull request #82 from AustralianBioCommons/sbp-register-whitelist
AAI-386: Restrict user registration to a restricted list of domains [SBP]
2 parents 8e5033a + 20494d2 commit 79f1236

4 files changed

Lines changed: 82 additions & 1 deletion

File tree

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ ADMIN_ROLES='["Admin", "GalaxyAdmin"]'
1717
SEND_EMAIL=False
1818
# AAI Portal URL for admin links in emails
1919
AAI_PORTAL_URL=https://aaiportal.example.com
20+
# Allowed email domains for SBP registration (note the JSON list syntax)
21+
SBP_ALLOWED_EMAIL_DOMAINS='["gmail.com", "outlook.com", "example.com", "yahoo.com"]'
2022
# URL of Galaxy instance, for making calls to Galaxy API
2123
GALAXY_URL=https://galaxy.example.com
2224
GALAXY_API_KEY=api-key

config.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,23 @@ class Settings(BaseSettings):
2121
cors_allowed_origins: str
2222
# AAI Portal URL for admin links in emails
2323
aai_portal_url: str = "https://aaiportal.test.biocommons.org.au"
24+
# Allowed email domains for SBP registration
25+
sbp_allowed_email_domains: list[str] = [
26+
# UNSW
27+
"unsw.edu.au", "ad.unsw.edu.au", "student.unsw.edu.au",
28+
# BioCommons
29+
"biocommons.org.au",
30+
# USyd
31+
"sydney.edu.au", "uni.sydney.edu.au",
32+
# WEHI
33+
"wehi.edu.au",
34+
# Monash
35+
"monash.edu", "student.monash.edu",
36+
# Griffith
37+
"griffith.edu.au", "griffithuni.edu.au",
38+
# UoM
39+
"unimelb.edu.au", "student.unimelb.edu.au"
40+
]
2441

2542
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
2643

routers/sbp_register.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22

3+
from email_validator import EmailNotValidError, validate_email
34
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
45
from httpx import HTTPStatusError
56
from sqlmodel import Session
@@ -24,6 +25,15 @@
2425
route_class=RegistrationRoute
2526
)
2627

28+
def validate_sbp_email_domain(email: str, settings: Settings) -> bool:
29+
try:
30+
validated_email = validate_email(email)
31+
domain = validated_email.domain.lower()
32+
allowed_domains_lower = [domain.lower() for domain in settings.sbp_allowed_email_domains]
33+
return domain in allowed_domains_lower
34+
except EmailNotValidError:
35+
return False
36+
2737

2838
def send_approval_email(registration: SBPRegistrationRequest, settings: Settings):
2939
"""Send email notification about new SBP registration."""
@@ -62,6 +72,18 @@ async def register_sbp_user(
6272
):
6373
"""Register a new SBP user."""
6474

75+
# Validate email domain
76+
if not validate_sbp_email_domain(registration.email, settings):
77+
logger.warning(f"SBP registration rejected for email domain: {registration.email}")
78+
allowed_domains_str = ", ".join(settings.sbp_allowed_email_domains)
79+
response = RegistrationErrorResponse(
80+
message=(
81+
"Email domain not approved for SBP registration. "
82+
f"Please use an email from an approved domain: {allowed_domains_str}."
83+
)
84+
)
85+
return JSONResponse(status_code=400, content=response.model_dump(mode="json"))
86+
6587
# Create Auth0 user data
6688
user_data = BiocommonsRegisterData.from_sbp_registration(
6789
registration=registration

tests/test_sbp_register.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
PlatformMembership,
1010
PlatformMembershipHistory,
1111
)
12+
from routers.sbp_register import validate_sbp_email_domain
1213
from schemas.biocommons import BiocommonsRegisterData
1314
from tests.datagen import (
1415
Auth0UserDataFactory,
@@ -23,7 +24,7 @@ def valid_registration_data():
2324
username="testuser",
2425
first_name="Test",
2526
last_name="User",
26-
email="test@example.com",
27+
email="test@unsw.edu.au",
2728
reason="Need access to SBP resources",
2829
password="SecurePass123!",
2930
).model_dump()
@@ -37,6 +38,34 @@ def test_to_biocommons_register_data():
3738
assert register_data.app_metadata.registration_from == "sbp"
3839

3940

41+
def test_validate_sbp_email_domain_function():
42+
from unittest.mock import Mock
43+
mock_settings = Mock()
44+
mock_settings.sbp_allowed_email_domains = [
45+
"unsw.edu.au", "ad.unsw.edu.au", "student.unsw.edu.au",
46+
"biocommons.org.au",
47+
"sydney.edu.au", "uni.sydney.edu.au",
48+
"wehi.edu.au",
49+
"monash.edu", "student.monash.edu",
50+
"griffith.edu.au", "griffithuni.edu.au",
51+
"unimelb.edu.au", "student.unimelb.edu.au"
52+
]
53+
54+
# Test approved domains
55+
assert validate_sbp_email_domain("user@unsw.edu.au", mock_settings)
56+
assert validate_sbp_email_domain("user@biocommons.org.au", mock_settings)
57+
assert validate_sbp_email_domain("user@sydney.edu.au", mock_settings)
58+
assert validate_sbp_email_domain("USER@UNSW.EDU.AU", mock_settings)
59+
60+
# Test rejected domains
61+
assert not validate_sbp_email_domain("user@gmail.com", mock_settings)
62+
assert not validate_sbp_email_domain("user@unsw.com", mock_settings)
63+
assert not validate_sbp_email_domain("user@biocommons.org", mock_settings)
64+
assert not validate_sbp_email_domain("user@evilunsw.edu.au", mock_settings)
65+
assert not validate_sbp_email_domain("user@malicious.biocommons.org.au", mock_settings)
66+
assert not validate_sbp_email_domain("user@fakeunimelb.edu.au", mock_settings)
67+
68+
4069
def test_successful_registration(
4170
test_client, valid_registration_data, mock_auth0_client, test_db_session
4271
):
@@ -136,3 +165,14 @@ def test_registration_email_format(test_client, valid_registration_data):
136165
details = response.json()
137166
errors = details["field_errors"]
138167
assert "email" in [error["field"] for error in errors]
168+
169+
170+
def test_registration_rejected_email_domains(test_client, valid_registration_data, mock_auth0_client):
171+
data = valid_registration_data.copy()
172+
data["email"] = "user@gmail.com"
173+
174+
response = test_client.post("/sbp/register", json=data)
175+
176+
assert response.status_code == 400
177+
assert "Email domain not approved for SBP registration" in response.json()["message"]
178+
assert not mock_auth0_client.create_user.called

0 commit comments

Comments
 (0)