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

Implement OIDC #6

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ keyset
*.db

.vscode
.idea
21 changes: 14 additions & 7 deletions fvserver/context_processors.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
from django.conf import settings
import plistlib
import os


def crypt_version(request):
# return the value you want as a dictionary. you may add multiple values in there.
current_dir = os.path.dirname(os.path.realpath(__file__))
with open(
os.path.join(os.path.dirname(current_dir), "fvserver", "version.plist"), "rb"
) as f:
version = plistlib.load(f)
return {"CRYPT_VERSION": version["version"]}
# return the value you want as a dictionary. you may add multiple values in there.
current_dir = os.path.dirname(os.path.realpath(__file__))
with open(
os.path.join(os.path.dirname(current_dir), "fvserver", "version.plist"), "rb"
) as f:
version = plistlib.load(f)
return {"CRYPT_VERSION": version["version"]}


def oidc_enabled(request):
return {
"OIDC_ENABLED": settings.OIDC_ENABLED
}
20 changes: 20 additions & 0 deletions fvserver/oidc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from mozilla_django_oidc.auth import OIDCAuthenticationBackend


def update_user(user, claims):
user.username = claims.get("email").split("@")[0]
user.first_name = claims.get("given_name")
user.last_name = claims.get("family_name")
user.save()

return user


class CustomOIDC(OIDCAuthenticationBackend):
def create_user(self, claims):
user = super().create_user(claims)
return update_user(user, claims)

def update_user(self, user, claims):
user = super().update_user(user, claims)
return update_user(user, claims)
195 changes: 110 additions & 85 deletions fvserver/system_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Django settings for fvserver project.

PROJECT_DIR = os.path.abspath(
os.path.join(os.path.dirname(os.path.abspath(__file__)), os.path.pardir)
os.path.join(os.path.dirname(os.path.abspath(__file__)), os.path.pardir)
)
ENCRYPTED_FIELD_KEYS_DIR = os.path.join(PROJECT_DIR, "keyset")
DEBUG = False
Expand All @@ -13,11 +13,7 @@
DATE_FORMAT = "Y-m-d H:i:s"
DATETIME_FORMAT = "Y-m-d H:i:s"

ADMINS = [
(
# ('Your Name', '[email protected]'),
)
]
ADMINS = ()

FIELD_ENCRYPTION_KEY = os.environ.get("FIELD_ENCRYPTION_KEY", "")

Expand Down Expand Up @@ -74,10 +70,10 @@

# Additional locations of static files
STATICFILES_DIRS = (
# Put strings here, like "/home/html/static" or "C:/www/django/static".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
os.path.join(PROJECT_DIR, "site_static"),
# Put strings here, like "/home/html/static" or "C:/www/django/static".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
os.path.join(PROJECT_DIR, "site_static"),
)

LOGIN_URL = "/login/"
Expand All @@ -88,99 +84,128 @@
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)

# Make this unique, and don't share it with anybody.
SECRET_KEY = "6%y8=x5(#ufxd*+d+-ohwy0b$5z^cla@7tvl@n55_h_cex0qat"

TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [
# insert your TEMPLATE_DIRS here
os.path.join(PROJECT_DIR, "templates")
],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
# Insert your TEMPLATE_CONTEXT_PROCESSORS here or use this
# list if you haven't customized them:
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.contrib.messages.context_processors.messages",
"django.template.context_processors.media",
"django.template.context_processors.static",
"django.template.context_processors.tz",
"django.template.context_processors.request",
"fvserver.context_processors.crypt_version",
],
"debug": DEBUG,
},
}
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [
# insert your TEMPLATE_DIRS here
os.path.join(PROJECT_DIR, "templates")
],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
# Insert your TEMPLATE_CONTEXT_PROCESSORS here or use this
# list if you haven't customized them:
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.contrib.messages.context_processors.messages",
"django.template.context_processors.media",
"django.template.context_processors.static",
"django.template.context_processors.tz",
"django.template.context_processors.request",
"fvserver.context_processors.crypt_version",
"fvserver.context_processors.oidc_enabled"
],
"debug": DEBUG,
},
}
]

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]


ROOT_URLCONF = "fvserver.urls"

# Python dotted path to the WSGI application used by Django's runserver.
WSGI_APPLICATION = "fvserver.wsgi.application"


INSTALLED_APPS = (
"whitenoise.runserver_nostatic",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.sites",
"django.contrib.messages",
"django.contrib.staticfiles",
# Uncomment the next line to enable the admin:
"django.contrib.admin",
# Uncomment the next line to enable admin documentation:
"django.contrib.admindocs",
"server",
"bootstrap4",
"django_extensions",
)
INSTALLED_APPS = [
"whitenoise.runserver_nostatic",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.sites",
"django.contrib.messages",
"django.contrib.staticfiles",
# Uncomment the next line to enable the admin:
"django.contrib.admin",
# Uncomment the next line to enable admin documentation:
"django.contrib.admindocs",
"server",
"bootstrap4",
"django_extensions",
]

LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"format": "[DJANGO] %(levelname)s %(asctime)s %(module)s "
"%(name)s.%(funcName)s:%(lineno)s: %(message)s"
},
},
"handlers": {
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "default",
}
},
"loggers": {
"*": {
"handlers": ["console"],
"level": "DEBUG",
"propagate": True,
}
},
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"format": "[DJANGO] %(levelname)s %(asctime)s %(module)s "
"%(name)s.%(funcName)s:%(lineno)s: %(message)s"
},
},
"handlers": {
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "default",
}
},
"loggers": {
"*": {
"handlers": ["console"],
"level": "DEBUG",
"propagate": True,
},
},
}

DEFAULT_AUTO_FIELD = "django.db.models.AutoField"

# OIDC stuff
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend"
]

OIDC_ENABLED = int(os.environ.get("OIDC_ENABLED", default=0))

if OIDC_ENABLED:
OIDC_VERIFY_SSL = True
OIDC_CREATE_USER = True
OIDC_RENEW_ID_TOKEN_EXPIRY_SECONDS = int(os.environ.get('OIDC_RENEW_ID_TOKEN_EXPIRY_SECONDS', default=900))
# OIDC_RP_IDP_SIGN_KEY = "<OP signing key in PEM or DER format>"
OIDC_RP_CLIENT_ID = os.environ.get("OIDC_RP_CLIENT_ID", None)
OIDC_RP_CLIENT_SECRET = os.environ.get("OIDC_RP_CLIENT_SECRET", None)
OIDC_RP_SCOPES = "openid profile email groups"
OIDC_RP_SIGN_ALGO = os.environ.get("OIDC_RP_SIGN_ALGO", None)
OIDC_OP_AUTHORIZATION_ENDPOINT = os.environ.get("OIDC_OP_AUTHORIZATION_ENDPOINT", None)
OIDC_OP_JWKS_ENDPOINT = os.environ.get("OIDC_OP_JWKS_ENDPOINT", None)
OIDC_OP_TOKEN_ENDPOINT = os.environ.get("OIDC_OP_TOKEN_ENDPOINT", None)
OIDC_OP_USER_ENDPOINT = os.environ.get("OIDC_OP_USER_ENDPOINT", None)
OIDC_REDIRECT_URL = os.environ.get("OIDC_REDIRECT_URL", None)

INSTALLED_APPS.append("mozilla_django_oidc")
MIDDLEWARE.append("mozilla_django_oidc.middleware.SessionRefresh")

# overwrite the existing backends to disable local auth
AUTHENTICATION_BACKENDS = ["fvserver.oidc.CustomOIDC"]

INSTALLED_APPS = tuple(INSTALLED_APPS)
43 changes: 26 additions & 17 deletions fvserver/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,34 @@
import django.contrib.auth.views as auth_views
import django.contrib.admindocs.urls as admindocs_urls
from django.urls import path, include
from django.conf import settings

app_name = "fvserver"

urlpatterns = [
path("login/", auth_views.LoginView.as_view(), name="login"),
path("logout/", auth_views.logout_then_login, name="logout"),
path(
"changepassword/",
auth_views.PasswordChangeView.as_view(),
name="password_change",
),
path(
"changepassword/done/",
auth_views.PasswordChangeDoneView.as_view(),
name="password_change_done",
),
path("", include("server.urls")),
# Uncomment the admin/doc line below to enable admin documentation:
path("admin/doc/", include(admindocs_urls)),
# Uncomment the next line to enable the admin:
path("admin/", admin.site.urls),
path("login/", auth_views.LoginView.as_view(), name="login"),
path("logout/", auth_views.logout_then_login, name="logout"),
path("", include("server.urls")),
# Uncomment the admin/doc line below to enable admin documentation:
path("admin/doc/", include(admindocs_urls)),
# Uncomment the next line to enable the admin:
path("admin/", admin.site.urls),

# OIDC
path("oidc/", include("mozilla_django_oidc.urls")),
]

# inject password reset routes if oidc is disabled
if not settings.OIDC_ENABLED:
urlpatterns += [
path(
"changepassword/",
auth_views.PasswordChangeView.as_view(),
name="password_change",
),
path(
"changepassword/done/",
auth_views.PasswordChangeDoneView.as_view(),
name="password_change_done",
),
]
3 changes: 2 additions & 1 deletion setup/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ sqlparse==0.4.4
supervisor==4.2.4
tomli==2.0.1
whitenoise==6.2.0
wrapt==1.14.1
wrapt==1.14.1
mozilla_django_oidc==4.0.1
2 changes: 1 addition & 1 deletion templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@

{% endblock %}

{% if user.has_usable_password %}
{% if user.has_usable_password and not OIDC_ENABLED %}
<li class="nav-item"><a class="nav-link" href="{% url 'password_change' %}"><i class="fa fa-user fa-fw"></i> {% trans "Change password" %}</a>
</li>
{% endif %}
Expand Down
Loading