diff --git a/base_requirements.txt b/base_requirements.txt index 11ddac634..7295607f3 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -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 diff --git a/netbox/netbox/middleware.py b/netbox/netbox/middleware.py index 8d03c6aee..959b6b525 100644 --- a/netbox/netbox/middleware.py +++ b/netbox/netbox/middleware.py @@ -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) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 45475ef9a..6efd4d375 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -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 diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index 4e0a2e2c6..e76efe0fe 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -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')), diff --git a/netbox/templates/login.html b/netbox/templates/login.html index a01d75422..b7f466c16 100644 --- a/netbox/templates/login.html +++ b/netbox/templates/login.html @@ -39,6 +39,14 @@ + {# TODO: Improve the design & layout #} + {% if auth_backends %} +
Or use an SSO provider:
+ {% for name, backend in auth_backends.items %} +

{{ name }}

+ {% endfor %} + {% endif %} + {# Login form errors #} {% if form.non_field_errors %}