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:
@ -102,6 +102,14 @@ PyYAML
|
|||||||
# https://github.com/andymccurdy/redis-py
|
# https://github.com/andymccurdy/redis-py
|
||||||
redis
|
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)
|
# SVG image rendering (used for rack elevations)
|
||||||
# https://github.com/mozman/svgwrite
|
# https://github.com/mozman/svgwrite
|
||||||
svgwrite
|
svgwrite
|
||||||
|
@ -8,7 +8,6 @@ from django.contrib import auth
|
|||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db import ProgrammingError
|
from django.db import ProgrammingError
|
||||||
from django.http import Http404, HttpResponseRedirect
|
from django.http import Http404, HttpResponseRedirect
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from extras.context_managers import change_logging
|
from extras.context_managers import change_logging
|
||||||
from netbox.config import clear_config
|
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.
|
If LOGIN_REQUIRED is True, redirect all non-authenticated users to the login page.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
# Redirect unauthenticated requests (except those exempted) to the login page if LOGIN_REQUIRED is true
|
# 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:
|
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
|
# 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())}'
|
login_url = f'{settings.LOGIN_URL}?next={parse.quote(request.get_full_path_info())}'
|
||||||
return HttpResponseRedirect(login_url)
|
return HttpResponseRedirect(login_url)
|
||||||
|
|
||||||
|
@ -305,6 +305,7 @@ INSTALLED_APPS = [
|
|||||||
'graphene_django',
|
'graphene_django',
|
||||||
'mptt',
|
'mptt',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
|
'social_django',
|
||||||
'taggit',
|
'taggit',
|
||||||
'timezone_field',
|
'timezone_field',
|
||||||
'circuits',
|
'circuits',
|
||||||
@ -400,7 +401,8 @@ MESSAGE_TAGS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Authentication URLs
|
# 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
|
CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
|
||||||
|
|
||||||
@ -414,6 +416,27 @@ EXEMPT_EXCLUDE_MODELS = (
|
|||||||
('users', 'objectpermission'),
|
('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
|
# Django Prometheus
|
||||||
|
@ -39,6 +39,7 @@ _patterns = [
|
|||||||
# Login/logout
|
# Login/logout
|
||||||
path('login/', LoginView.as_view(), name='login'),
|
path('login/', LoginView.as_view(), name='login'),
|
||||||
path('logout/', LogoutView.as_view(), name='logout'),
|
path('logout/', LogoutView.as_view(), name='logout'),
|
||||||
|
path('oauth/', include('social_django.urls', namespace='social')),
|
||||||
|
|
||||||
# Apps
|
# Apps
|
||||||
path('circuits/', include('circuits.urls')),
|
path('circuits/', include('circuits.urls')),
|
||||||
|
@ -39,6 +39,14 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</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 #}
|
{# Login form errors #}
|
||||||
{% if form.non_field_errors %}
|
{% if form.non_field_errors %}
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="alert alert-danger" role="alert">
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
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 import login as auth_login, logout as auth_logout, update_session_auth_hash
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
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.utils.http import is_safe_url
|
||||||
from django.views.decorators.debug import sensitive_post_parameters
|
from django.views.decorators.debug import sensitive_post_parameters
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
from social_core.backends.utils import load_backends
|
||||||
|
|
||||||
from netbox.config import get_config
|
from netbox.config import get_config
|
||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
@ -42,6 +44,7 @@ class LoginView(View):
|
|||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'form': form,
|
'form': form,
|
||||||
|
'auth_backends': load_backends(settings.AUTHENTICATION_BACKENDS),
|
||||||
})
|
})
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
@ -69,13 +72,14 @@ class LoginView(View):
|
|||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'form': form,
|
'form': form,
|
||||||
|
'auth_backends': load_backends(settings.AUTHENTICATION_BACKENDS),
|
||||||
})
|
})
|
||||||
|
|
||||||
def redirect_to_next(self, request, logger):
|
def redirect_to_next(self, request, logger):
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
redirect_to = request.POST.get('next', reverse('home'))
|
redirect_to = request.POST.get('next', settings.LOGIN_REDIRECT_URL)
|
||||||
else:
|
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()):
|
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}")
|
logger.warning(f"Ignoring unsafe 'next' URL passed to login form: {redirect_to}")
|
||||||
|
@ -23,6 +23,8 @@ netaddr==0.8.0
|
|||||||
Pillow==8.4.0
|
Pillow==8.4.0
|
||||||
psycopg2-binary==2.9.1
|
psycopg2-binary==2.9.1
|
||||||
PyYAML==6.0
|
PyYAML==6.0
|
||||||
|
social-auth-app-django==5.0.0
|
||||||
|
social-auth-core==4.1.0
|
||||||
svgwrite==1.4.1
|
svgwrite==1.4.1
|
||||||
tablib==3.0.0
|
tablib==3.0.0
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user