diff --git a/benefits/core/admin.py b/benefits/core/admin.py index c6744e380..68c045698 100644 --- a/benefits/core/admin.py +++ b/benefits/core/admin.py @@ -2,8 +2,9 @@ The core application: Admin interface configuration. """ -from django.conf import settings +import requests +from django.conf import settings if settings.ADMIN: import logging @@ -21,3 +22,23 @@ ]: logger.debug(f"Register {model.__name__}") admin.site.register(model) + + def pre_login_user(user, request): + logger.debug(f"Running pre-login callback for user: {user.username}") + token = request.session.get("google_sso_access_token") + if token: + headers = { + "Authorization": f"Bearer {token}", + } + + # Request Google user info to get name and email + url = "https://www.googleapis.com/oauth2/v3/userinfo" + response = requests.get(url, headers=headers, timeout=settings.REQUESTS_TIMEOUT) + user_data = response.json() + logger.debug(f"Updating admin user data from Google for user with email: {user_data['email']}") + + user.first_name = user_data["given_name"] + user.last_name = user_data["family_name"] + user.username = user_data["email"] + user.email = user_data["email"] + user.save() diff --git a/benefits/settings.py b/benefits/settings.py index 222312904..842620c42 100644 --- a/benefits/settings.py +++ b/benefits/settings.py @@ -22,7 +22,7 @@ def _filter_empty(ls): ADMIN = os.environ.get("DJANGO_ADMIN", "False").lower() == "true" -ALLOWED_HOSTS = _filter_empty(os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")) +ALLOWED_HOSTS = _filter_empty(os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost").split(",")) # Application definition @@ -37,11 +37,27 @@ def _filter_empty(ls): ] if ADMIN: + GOOGLE_SSO_CLIENT_ID = os.environ.get("GOOGLE_SSO_CLIENT_ID", "secret") + GOOGLE_SSO_PROJECT_ID = os.environ.get("GOOGLE_SSO_PROJECT_ID", "benefits-admin") + GOOGLE_SSO_CLIENT_SECRET = os.environ.get("GOOGLE_SSO_CLIENT_SECRET", "secret") + GOOGLE_SSO_ALLOWABLE_DOMAINS = _filter_empty(os.environ.get("GOOGLE_SSO_ALLOWABLE_DOMAINS", "compiler.la").split(",")) + GOOGLE_SSO_STAFF_LIST = _filter_empty(os.environ.get("GOOGLE_SSO_STAFF_LIST", "").split(",")) + GOOGLE_SSO_SUPERUSER_LIST = _filter_empty(os.environ.get("GOOGLE_SSO_SUPERUSER_LIST", "").split(",")) + GOOGLE_SSO_LOGO_URL = "/static/img/icon/google_sso_logo.svg" + GOOGLE_SSO_SAVE_ACCESS_TOKEN = True + GOOGLE_SSO_PRE_LOGIN_CALLBACK = "benefits.core.admin.pre_login_user" + GOOGLE_SSO_SCOPES = [ + "openid", + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/userinfo.profile", + ] + INSTALLED_APPS.extend( [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", + "django_google_sso", # Add django_google_sso ] ) @@ -282,7 +298,11 @@ def _filter_empty(ls): if len(env_frame_src) > 0: CSP_FRAME_SRC = env_frame_src -CSP_IMG_SRC = ["'self'", "data:"] +CSP_IMG_SRC = [ + "'self'", + "data:", + "*.googleusercontent.com", +] # Configuring strict Content Security Policy # https://django-csp.readthedocs.io/en/latest/nonce.html @@ -294,9 +314,11 @@ def _filter_empty(ls): CSP_REPORT_URI = [sentry.SENTRY_CSP_REPORT_URI] CSP_SCRIPT_SRC = [ + "'self'", "https://cdn.amplitude.com/libs/", "https://cdn.jsdelivr.net/", "*.littlepay.com", + "https://code.jquery.com/jquery-3.6.0.min.js", ] env_script_src = _filter_empty(os.environ.get("DJANGO_CSP_SCRIPT_SRC", "").split(",")) CSP_SCRIPT_SRC.extend(env_script_src) diff --git a/benefits/static/img/icon/google_sso_logo.svg b/benefits/static/img/icon/google_sso_logo.svg new file mode 100644 index 000000000..21ec49090 --- /dev/null +++ b/benefits/static/img/icon/google_sso_logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/benefits/urls.py b/benefits/urls.py index 57d7931ec..fdc5a28ac 100644 --- a/benefits/urls.py +++ b/benefits/urls.py @@ -39,5 +39,6 @@ def trigger_error(request): logger.debug("Register admin urls") urlpatterns.append(path("admin/", admin.site.urls)) + urlpatterns.append(path("google_sso/", include("django_google_sso.urls", namespace="django_google_sso"))) else: logger.debug("Skip url registrations for admin") diff --git a/pyproject.toml b/pyproject.toml index fdd977f2c..a2bba5985 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ dependencies = [ "Authlib==1.3.0", "Django==5.0.1", "django-csp==3.7", + "django-google-sso==5.0.0", "eligibility-api==2023.9.1", "requests==2.31.0", "sentry-sdk==1.39.2", diff --git a/terraform/app_service.tf b/terraform/app_service.tf index 37db1d5ac..bc8004731 100644 --- a/terraform/app_service.tf +++ b/terraform/app_service.tf @@ -84,6 +84,15 @@ resource "azurerm_linux_web_app" "main" { "HEALTHCHECK_USER_AGENTS" = local.is_dev ? null : "${local.secret_prefix}healthcheck-user-agents)", + # Google SSO for Admin + + "GOOGLE_SSO_CLIENT_ID" = "${local.secret_prefix}google-sso-client-id", + "GOOGLE_SSO_PROJECT_ID" = "${local.secret_prefix}google-sso-project-id", + "GOOGLE_SSO_CLIENT_SECRET" = "${local.secret_prefix}google-sso-client-secret", + "GOOGLE_SSO_ALLOWABLE_DOMAINS" = "${local.secret_prefix}google-sso-allowable-domains", + "GOOGLE_SSO_STAFF_LIST" = "${local.secret_prefix}google-sso-staff-list", + "GOOGLE_SSO_SUPERUSER_LIST" = "${local.secret_prefix}google-sso-superuser-list" + # Sentry "SENTRY_DSN" = "${local.secret_prefix}sentry-dsn)", "SENTRY_ENVIRONMENT" = local.env_name,