diff --git a/backend/Dockerfile b/backend/Dockerfile index f40285c..d7f089e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -35,6 +35,7 @@ COPY . /app/ ENV DJANGO_SETTINGS_MODULE Platform.settings.production ENV SECRET_KEY 'temporary key just to build the docker image' ENV IDENTITY_RSA_PRIVATE_KEY 'temporary private key just to build the docker image' +ENV OIDC_RSA_PRIVATE_KEY 'temporary private key just to build the docker image' # Collect static files RUN python3 /app/manage.py collectstatic --noinput diff --git a/backend/Dockerfile.dev b/backend/Dockerfile.dev index 806d880..4cfd091 100644 --- a/backend/Dockerfile.dev +++ b/backend/Dockerfile.dev @@ -14,6 +14,7 @@ COPY . /app/ ENV DJANGO_SETTINGS_MODULE Platform.settings.production ENV SECRET_KEY 'temporary key just to build the docker image' ENV IDENTITY_RSA_PRIVATE_KEY 'temporary private key just to build the docker image' +ENV OIDC_RSA_PRIVATE_KEY 'temporary private key just to build the docker image' # Collect static files RUN python3 /app/manage.py collectstatic --noinput diff --git a/backend/Platform/settings/base.py b/backend/Platform/settings/base.py index b6cac72..0a24e47 100644 --- a/backend/Platform/settings/base.py +++ b/backend/Platform/settings/base.py @@ -47,6 +47,26 @@ -----END RSA PRIVATE KEY-----""", ) +OIDC_RSA_PRIVATE_KEY = os.environ.get( + "OIDC_RSA_PRIVATE_KEY", + """-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMF37VZGAUWIsrcE +Iw2uskamXXkQtFb7dMD96iaPNvp7Buc4Z/p+6Mc1uLJRKUeJavU5JIF3K2uRWuLh +OozP5O3WKDnD5johit1GsaBejc45unxMIAoP4fdLO+iVSHQSl9VS4SXBX1RtitgP +/eRvEBxk4/LCpJJiZo4cQVZjavc5AgMBAAECgYBx6qiwHQZQqB37D4+IVe4ZFYqC +Z6iYcvWbUadWzwsjT9+PtDHdWG6+Jc68CHgS7EIzZFMvfDjv3KW0Y8Qy95Kmw++z +HlmvfGatR8NRge1tdEUCFKkLfSj0hUh/RjculDWM5hlnWnaVa9kw+drUGVhNqUxa +pMbqYSDiMljtFVDeVQJBAP7KSS9Pm+XZx0qVo4u82Fd9AuoyGumt0EqqHcwpHTpu +N5mIUttIBvGeQ9Cc3LiUqzMBP/vIEIMoqLt104io1esCQQDCYxnFS+1DfFIvgXWu +K55PZKVLl4S8/6IKrfulpbZrBNYfQbbsjY+3GHHBzGU0cId3yAsTHZsZ1OdoFb4w +AyprAkAzfTWk9fWPUZ9Ql0ThrFwb8gtwwIdnydRaAl7bL0PU1wktYbs8zSV6Fn2l +3s1MD985A3umqhuMJd9TYtBIwbXZAkEAseVR616+J5m5+SHoWdovSodYQuLKptDo +Mg/hkkoitLQ7ZWWVi81N7gmf6fUt1Zz6TSO1Bux8Slqu4HGtmXD8OwJAW5FEDoOQ +D0LF8EFEhFrtPvkb0wTr6pyDWtIAJuxqvIRwaP4FACgOL/Cv6BGn5DyM6H/W5/Kp +Zk+r72xEuoNzUQ== +-----END PRIVATE KEY-----""", +) + # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -178,13 +198,17 @@ # OAuth2 Settings OAUTH2_PROVIDER = { + "OAUTH2_VALIDATOR_CLASS": "accounts.oauth2_validator.LabsOAuth2Validator", "SCOPES": { + "openid": "OpenID Connect scope", "read": "Read scope", "write": "Write scope", "introspection": "Introspect token scope", }, "ALLOWED_REDIRECT_URI_SCHEMES": ["http", "https"], "PKCE_REQUIRED": False, + "OIDC_ENABLED": True, + "OIDC_RSA_PRIVATE_KEY": OIDC_RSA_PRIVATE_KEY, } # Custom User Model diff --git a/backend/Platform/settings/production.py b/backend/Platform/settings/production.py index 679d63b..1529636 100644 --- a/backend/Platform/settings/production.py +++ b/backend/Platform/settings/production.py @@ -26,13 +26,20 @@ "Please provide environment variable IDENTITY_RSA_PRIVATE_KEY in production" ) + +OIDC_RSA_PRIVATE_KEY = os.environ.get("OIDC_RSA_PRIVATE_KEY", None) +if OIDC_RSA_PRIVATE_KEY is None: + raise ImproperlyConfigured( + "Please provide environment variable OIDC_RSA_PRIVATE_KEY in production" + ) + # Sentry settings SENTRY_URL = os.environ.get("SENTRY_URL", "") sentry_sdk.init(dsn=SENTRY_URL, integrations=[DjangoIntegration()]) # CORS settings CORS_ALLOW_ALL_ORIGINS = True -CORS_ALLOW_METHODS = ["GET"] +CORS_ALLOW_METHODS = ["GET", "POST"] CORS_URLS_REGEX = r"^/options/$" # Email client settings diff --git a/backend/Platform/urls.py b/backend/Platform/urls.py index ef299eb..6dbbff8 100644 --- a/backend/Platform/urls.py +++ b/backend/Platform/urls.py @@ -10,7 +10,7 @@ urlpatterns = [ path("admin/", admin.site.urls), path("announcements/", include("announcements.urls", namespace="announcements")), - path("accounts/", include("accounts.urls")), + path("accounts/", include("accounts.urls", namespace="oauth2_provider")), path("options/", include("options.urls", namespace="options")), path("identity/", include("identity.urls", namespace="identity")), path("s/", include("shortener.urls", namespace="shortener")), diff --git a/backend/accounts/backends.py b/backend/accounts/backends.py index 2b8708e..fd72c91 100644 --- a/backend/accounts/backends.py +++ b/backend/accounts/backends.py @@ -15,6 +15,8 @@ class ShibbolethRemoteUserBackend(RemoteUserBackend): """ def get_email(self, pennid): + if settings.DEBUG: + return None """ Use Penn Directory API with OAuth2 to get the email of a user given their Penn ID. This is necessary to ensure that we have the correct domain (@seas vs. @wharton, etc.) diff --git a/backend/accounts/models.py b/backend/accounts/models.py index 9dfbec9..955444b 100644 --- a/backend/accounts/models.py +++ b/backend/accounts/models.py @@ -69,6 +69,10 @@ class User(AbstractUser): VERIFICATION_EXPIRATION_MINUTES = 10 + @property + def id(self): + return self.username + def get_preferred_name(self): if self.preferred_name != "": return self.preferred_name diff --git a/backend/accounts/oauth2_validator.py b/backend/accounts/oauth2_validator.py new file mode 100644 index 0000000..6e12585 --- /dev/null +++ b/backend/accounts/oauth2_validator.py @@ -0,0 +1,23 @@ +from oauth2_provider.oauth2_validators import OAuth2Validator + + +class LabsOAuth2Validator(OAuth2Validator): + oidc_claim_scope = OAuth2Validator.oidc_claim_scope + oidc_claim_scope.update( + { + "name": "read", + "email": "read", + "pennid": "read", + "is_staff": "read", + "is_active": "read", + } + ) + + def get_additional_claims(self, request): + return { + "name": request.user.preferred_name or request.user.get_full_name(), + "email": request.user.email, + "pennid": request.user.pennid, + "is_staff": request.user.is_staff, + "is_active": request.user.is_active, + } diff --git a/backend/accounts/urls.py b/backend/accounts/urls.py index 5f0cd19..e04c2af 100644 --- a/backend/accounts/urls.py +++ b/backend/accounts/urls.py @@ -1,7 +1,12 @@ from django.conf import settings from django.conf.urls.static import static from django.urls import path -from oauth2_provider.views import AuthorizationView, TokenView +from oauth2_provider.views import ( + AuthorizationView, + ConnectDiscoveryInfoView, + JwksInfoView, + TokenView, +) from rest_framework import routers from accounts.views import ( @@ -47,6 +52,12 @@ path("privacy/", PrivacySettingView.as_view(), name="privacy"), path("privacy//", PrivacySettingView.as_view(), name="privacy"), path("user/", FindUserView.as_view(), name="user"), + path( + ".well-known/openid-configuration", + ConnectDiscoveryInfoView.as_view(), + name="oidc-connect-discovery-info", + ), + path(".well-known/jwks.json", JwksInfoView.as_view(), name="oidc-jwks-info"), ] urlpatterns += router.urls