Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add Support for Okta #2021

Draft
wants to merge 14 commits into
base: develop
Choose a base branch
from
Draft
9 changes: 5 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -694,12 +694,13 @@ workflows:
only: develop

- deploy-stage:
requires:
- owasp-scan-dev
- build_and_test
# requires:
# - owasp-scan-dev
# - build_and_test
filters:
branches:
only: /^release.*/
# only: /^release.*/
only: "rw/add-okta-support"

- deploy-prod:
requires:
Expand Down
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pypdf = "*"
weasyprint = "*"
beautifulsoup4 = "*"
pytz = "*"
mozilla-django-oidc = "*"

[requires]
python_version = "3.13"
Expand Down
27 changes: 21 additions & 6 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions crt_portal/crt_portal/custom_oidc_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from mozilla_django_oidc.auth import OIDCAuthenticationBackend


class CrtAuthenticationBackend(OIDCAuthenticationBackend):
def create_user(self, claims):
print("CrtAuthenticationBackend: CreateUser: Claims = ", claims)
user = super(CrtAuthenticationBackend, self).create_user(claims)

user.first_name = claims.get('given_name', '')
user.last_name = claims.get('family_name', '')
user.save()

return user

def update_user(self, user, claims):
print("CrtAuthenticationBackend: UpdateUser: Claims = ", claims)
user.first_name = claims.get('given_name', '')
user.last_name = claims.get('family_name', '')
user.save()

return user
51 changes: 30 additions & 21 deletions crt_portal/crt_portal/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,8 @@
PRIV_S3_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY', 'AWSSAK')
PRIV_S3_ENDPOINT_URL = 'http://localhost:4566'

# for AUTH, in prod and stage
AUTHENTICATION_BACKENDS_LIST = []
# for ADFS AUTH, in prod and stage
if environment in ['PRODUCTION', 'STAGE']:
for service in vcap['user-provided']:
if service['instance_name'] == "VCAP_SERVICES":
Expand All @@ -297,8 +298,8 @@
AUTH_GROUP_CLAIM = creds['AUTH_GROUP_CLAIM']

INSTALLED_APPS.append('django_auth_adfs')
AUTHENTICATION_BACKENDS = (
'django_auth_adfs.backend.AdfsAuthCodeBackend',
AUTHENTICATION_BACKENDS_LIST.append(
'django_auth_adfs.backend.AdfsAuthCodeBackend'
)
MIDDLEWARE.append('django_auth_adfs.middleware.LoginRequiredMiddleware')

Expand All @@ -325,31 +326,33 @@
"USERNAME_CLAIM": AUTH_USERNAME_CLAIM,
# Explicitly DON'T set a group claim, as it will undo our native groups.
"GROUP_CLAIM": None,
'LOGIN_EXEMPT_URLS': [
'^$',
'^report',
'^link',
'^robots.txt',
'^privacy-policy',
'^hate-crime-human-trafficking',
'^i18n',
'^email',
'^housing-resources',
'^voting-resources',
'^oauth2_provider/token/',
'^oauth2_provider/userinfo/',
'^static/'
],
"LOGIN_EXEMPT_URLS": ['.*']
}

# OKTA Configuration
INSTALLED_APPS.append('mozilla_django_oidc')
# AUTHENTICATION_BACKENDS_LIST.append('mozilla_django_oidc.auth.OIDCAuthenticationBackend')
AUTHENTICATION_BACKENDS_LIST.append('crt_portal.custom_oidc_backend.CrtAuthenticationBackend')

OKTA_DOMAIN = os.environ['OKTA_DOMAIN']
OIDC_RP_CLIENT_ID = os.environ['OIDC_RP_CLIENT_ID']
OIDC_RP_CLIENT_SECRET = os.environ['OIDC_RP_CLIENT_SECRET']

OIDC_RP_SIGN_ALGO = "RS256"
OIDC_OP_AUTHORIZATION_ENDPOINT = f"https://{OKTA_DOMAIN}/oauth2/default/v1/authorize" # The OIDC authorization endpoint
OIDC_RP_TOKEN_ENDPOINT = f"https://{OKTA_DOMAIN}/oauth2/default/v1/token" # The OIDC token endpoint
OIDC_OP_USER_ENDPOINT = f"https://{OKTA_DOMAIN}/oauth2/default/v1/userinfo" # The OIDC userinfo endpoint
OIDC_OP_TOKEN_ENDPOINT = f"https://{OKTA_DOMAIN}/oauth2/default/v1/token" # The OIDC token endpoint
OIDC_OP_JWKS_ENDPOINT = f"https://{OKTA_DOMAIN}/oauth2/default/v1/keys" # The OIDC JWKS endpoint

OIDC_RP_SCOPES = "openid email profile"

if environment == 'STAGE':
login_base_url = 'https://crt-portal-django-stage.app.cloud.gov'
else:
login_base_url = 'https://crt-portal-django-prod.app.cloud.gov'
# Configure django to redirect users to the right URL for login
LOGIN_URL = f"{login_base_url}/oauth2/login"
# The url where the ADFS server calls back to our app
LOGIN_REDIRECT_URL = f"{login_base_url}/oauth2/callback"
LOGIN_URL = f"{login_base_url}/accounts/login"

ALLOWED_HOSTS = [
'civilrights.justice.gov',
Expand All @@ -360,6 +363,9 @@
'crt-portal-django-stage.apps.internal',
]

# Set AUTHENTICATION_BACKENDS
AUTHENTICATION_BACKENDS = tuple(AUTHENTICATION_BACKENDS_LIST)

STATIC_URL = '/static/'

if environment not in ['LOCAL', 'UNDEFINED']:
Expand Down Expand Up @@ -562,6 +568,9 @@
# django also has database level logging
'level': 'INFO'
},
'mozilla_django_oidc': {
'level': 'DEBUG'
},
},
}

Expand Down
13 changes: 9 additions & 4 deletions crt_portal/crt_portal/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,19 @@
from django.contrib import admin
from django.shortcuts import redirect
from django.urls import include, path, re_path
from django.views.generic import RedirectView, TemplateView
from django.views.generic import TemplateView

from mozilla_django_oidc import views as oidc_views

environment = os.environ.get('ENV', 'UNDEFINED')
if environment in ['PRODUCTION', 'STAGE']:
auth = [
re_path('admin/login/$', RedirectView.as_view(pattern_name='login')),
re_path('accounts/login/$', RedirectView.as_view(pattern_name='login')),
path('oauth2/', include('django_auth_adfs.urls'), name='login'),
# ADFS
path('oauth2/', include('django_auth_adfs.urls')),

# OKTA
path("authorization-code/authenticate/", oidc_views.OIDCAuthenticationRequestView.as_view(), name="oidc_authentication_init"),
path("authorization-code/callback/", oidc_views.OIDCAuthenticationCallbackView.as_view(), name="oidc_authentication_callback")
]
else:
auth = []
Expand Down
7 changes: 7 additions & 0 deletions crt_portal/templates/admin/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% extends "admin/login.html" %}
{% load get_env %}

{% block content %}
{% environment as env %}
{% include "authentication_page.html" %}
{% endblock %}
32 changes: 32 additions & 0 deletions crt_portal/templates/authentication_page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<div class="grid-container">
<div class="grid-row">
<div class="tablet:grid-col-7 tablet:grid-offset-3">
<h2>Login</h2>
{% if env != "PRODUCTION" and env != "STAGE" %}
<p>Please note this process will be replaced with DOJ single sign on.</p>
<form label="log in form" method="post">
{% csrf_token %}
<input type="hidden" name="next" value="{{ next }}" />
{{ form.as_p }}
<button label="login" type="submit" id="login" class="usa-button">Login</button>
</form>
{% else %}
<div>
<a href="{% url 'django_auth_adfs:login' %}">
<button class="usa-button" type="button">Log In with ADFS</button>
</a>
</div>
<div class="display-flex flex-align-center">
<hr class="flex-fill margin-0" />
<p class="margin-left-1 margin-right-1">or</p>
<hr class="flex-fill margin-0" />
</div>
<div>
<a href="{% url 'oidc_authentication_init' %}">
<button class="usa-button" type="button">Log In with DOJ Login</button>
</a>
</div>
{% endif %}
</div>
</div>
</div>
16 changes: 1 addition & 15 deletions crt_portal/templates/registration/login.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,4 @@
{% extends "base.html" %}

{% block content %}
<div class="grid-container">
<div class="grid-row">
<div class="tablet:grid-col-7 tablet:grid-offset-3">
<h2>Login</h2>
<p>Please note this process will be replaced with DOJ single sign on.</p>
<form label="log in form" method="post">
{% csrf_token %}
<input type="hidden" name="next" value="{{ next }}" />
{{ form.as_p }}
<button label="login" type="submit" id="login" class="usa-button">Login</button>
</form>
</div>
</div>
</div>
{% include "authentication_page.html" %}
{% endblock %}