1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Initial work on SSO support (WIP)

This commit is contained in:
jeremystretch
2021-10-29 17:06:14 -04:00
parent 4099dd3a05
commit 339776c139
7 changed files with 50 additions and 13 deletions

View File

@ -102,6 +102,14 @@ PyYAML
# https://github.com/andymccurdy/redis-py
redis
# Social authentication framework
# https://github.com/python-social-auth/social-core
social-auth-core[all]
# Django app for social-auth-core
# https://github.com/python-social-auth/social-app-django
social-auth-app-django
# SVG image rendering (used for rack elevations)
# https://github.com/mozman/svgwrite
svgwrite

View File

@ -8,7 +8,6 @@ from django.contrib import auth
from django.core.exceptions import ImproperlyConfigured
from django.db import ProgrammingError
from django.http import Http404, HttpResponseRedirect
from django.urls import reverse
from extras.context_managers import change_logging
from netbox.config import clear_config
@ -20,23 +19,15 @@ class LoginRequiredMiddleware:
"""
If LOGIN_REQUIRED is True, redirect all non-authenticated users to the login page.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Redirect unauthenticated requests (except those exempted) to the login page if LOGIN_REQUIRED is true
if settings.LOGIN_REQUIRED and not request.user.is_authenticated:
# Determine exempt paths
exempt_paths = [
reverse('api-root'),
reverse('graphql'),
]
if settings.METRICS_ENABLED:
exempt_paths.append(reverse('prometheus-django-metrics'))
# Redirect unauthenticated requests
if not request.path_info.startswith(tuple(exempt_paths)) and request.path_info != settings.LOGIN_URL:
if not request.path_info.startswith(settings.EXEMPT_PATHS):
login_url = f'{settings.LOGIN_URL}?next={parse.quote(request.get_full_path_info())}'
return HttpResponseRedirect(login_url)

View File

@ -305,6 +305,7 @@ INSTALLED_APPS = [
'graphene_django',
'mptt',
'rest_framework',
'social_django',
'taggit',
'timezone_field',
'circuits',
@ -400,7 +401,8 @@ MESSAGE_TAGS = {
}
# Authentication URLs
LOGIN_URL = '/{}login/'.format(BASE_PATH)
LOGIN_URL = f'/{BASE_PATH}login/'
LOGIN_REDIRECT_URL = f'/{BASE_PATH}'
CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
@ -414,6 +416,27 @@ EXEMPT_EXCLUDE_MODELS = (
('users', 'objectpermission'),
)
# All URLs starting with a string listed here are exempt from login enforcement
EXEMPT_PATHS = (
f'/{BASE_PATH}api/',
f'/{BASE_PATH}graphql/',
f'/{BASE_PATH}login/',
f'/{BASE_PATH}oauth/',
f'/{BASE_PATH}metrics/',
)
#
# Django social auth
#
# Load all SOCIAL_AUTH_* settings from the user configuration
for param in dir(configuration):
if param.startswith('SOCIAL_AUTH_'):
globals()[param] = getattr(configuration, param)
SOCIAL_AUTH_JSONFIELD_ENABLED = True
#
# Django Prometheus

View File

@ -39,6 +39,7 @@ _patterns = [
# Login/logout
path('login/', LoginView.as_view(), name='login'),
path('logout/', LogoutView.as_view(), name='logout'),
path('oauth/', include('social_django.urls', namespace='social')),
# Apps
path('circuits/', include('circuits.urls')),

View File

@ -39,6 +39,14 @@
</form>
</div>
{# TODO: Improve the design & layout #}
{% if auth_backends %}
<h6 class="mt-4">Or use an SSO provider:</h6>
{% for name, backend in auth_backends.items %}
<h4><a href="{% url 'social:begin' backend=name %}" class="my-2">{{ name }}</a></h4>
{% endfor %}
{% endif %}
{# Login form errors #}
{% if form.non_field_errors %}
<div class="alert alert-danger" role="alert">

View File

@ -1,5 +1,6 @@
import logging
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import login as auth_login, logout as auth_logout, update_session_auth_hash
from django.contrib.auth.mixins import LoginRequiredMixin
@ -12,6 +13,7 @@ from django.utils.decorators import method_decorator
from django.utils.http import is_safe_url
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import View
from social_core.backends.utils import load_backends
from netbox.config import get_config
from utilities.forms import ConfirmationForm
@ -42,6 +44,7 @@ class LoginView(View):
return render(request, self.template_name, {
'form': form,
'auth_backends': load_backends(settings.AUTHENTICATION_BACKENDS),
})
def post(self, request):
@ -69,13 +72,14 @@ class LoginView(View):
return render(request, self.template_name, {
'form': form,
'auth_backends': load_backends(settings.AUTHENTICATION_BACKENDS),
})
def redirect_to_next(self, request, logger):
if request.method == "POST":
redirect_to = request.POST.get('next', reverse('home'))
redirect_to = request.POST.get('next', settings.LOGIN_REDIRECT_URL)
else:
redirect_to = request.GET.get('next', reverse('home'))
redirect_to = request.GET.get('next', settings.LOGIN_REDIRECT_URL)
if redirect_to and not is_safe_url(url=redirect_to, allowed_hosts=request.get_host()):
logger.warning(f"Ignoring unsafe 'next' URL passed to login form: {redirect_to}")

View File

@ -23,6 +23,8 @@ netaddr==0.8.0
Pillow==8.4.0
psycopg2-binary==2.9.1
PyYAML==6.0
social-auth-app-django==5.0.0
social-auth-core==4.1.0
svgwrite==1.4.1
tablib==3.0.0