Move login & logout views to account app

This commit is contained in:
Jeremy Stretch 2023-07-31 09:00:59 -04:00
parent c0959dbce0
commit 14786648dd
3 changed files with 143 additions and 147 deletions

View File

@ -1,19 +1,156 @@
import logging
from django.conf import settings 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
from django.contrib.auth import update_session_auth_hash from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import get_object_or_404, redirect, render from django.contrib.auth.models import update_last_login
from django.contrib.auth.signals import user_logged_in
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.shortcuts import render, resolve_url
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.http import url_has_allowed_host_and_scheme, urlencode
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 account.models import UserToken from account.models import UserToken
from extras.models import Bookmark, ObjectChange from extras.models import Bookmark, ObjectChange
from extras.tables import BookmarkTable, ObjectChangeTable from extras.tables import BookmarkTable, ObjectChangeTable
from netbox.authentication import get_auth_backend_display, get_saml_idps
from netbox.config import get_config
from netbox.views import generic from netbox.views import generic
from users import filtersets, forms, tables from users import forms, tables
from users.models import Token from users.models import UserConfig
from utilities.views import register_model_view from utilities.views import register_model_view
#
# Login/logout
#
class LoginView(View):
"""
Perform user authentication via the web UI.
"""
template_name = 'login.html'
@method_decorator(sensitive_post_parameters('password'))
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def gen_auth_data(self, name, url, params):
display_name, icon_name = get_auth_backend_display(name)
return {
'display_name': display_name,
'icon_name': icon_name,
'url': f'{url}?{urlencode(params)}',
}
def get_auth_backends(self, request):
auth_backends = []
saml_idps = get_saml_idps()
for name in load_backends(settings.AUTHENTICATION_BACKENDS).keys():
url = reverse('social:begin', args=[name])
params = {}
if next := request.GET.get('next'):
params['next'] = next
if name.lower() == 'saml' and saml_idps:
for idp in saml_idps:
params['idp'] = idp
data = self.gen_auth_data(name, url, params)
data['display_name'] = f'{data["display_name"]} ({idp})'
auth_backends.append(data)
else:
auth_backends.append(self.gen_auth_data(name, url, params))
return auth_backends
def get(self, request):
form = forms.LoginForm(request)
if request.user.is_authenticated:
logger = logging.getLogger('netbox.auth.login')
return self.redirect_to_next(request, logger)
return render(request, self.template_name, {
'form': form,
'auth_backends': self.get_auth_backends(request),
})
def post(self, request):
logger = logging.getLogger('netbox.auth.login')
form = forms.LoginForm(request, data=request.POST)
if form.is_valid():
logger.debug("Login form validation was successful")
# If maintenance mode is enabled, assume the database is read-only, and disable updating the user's
# last_login time upon authentication.
if get_config().MAINTENANCE_MODE:
logger.warning("Maintenance mode enabled: disabling update of most recent login time")
user_logged_in.disconnect(update_last_login, dispatch_uid='update_last_login')
# Authenticate user
auth_login(request, form.get_user())
logger.info(f"User {request.user} successfully authenticated")
messages.success(request, f"Logged in as {request.user}.")
# Ensure the user has a UserConfig defined. (This should normally be handled by
# create_userconfig() on user creation.)
if not hasattr(request.user, 'config'):
config = get_config()
UserConfig(user=request.user, data=config.DEFAULT_USER_PREFERENCES).save()
return self.redirect_to_next(request, logger)
else:
logger.debug(f"Login form validation failed for username: {form['username'].value()}")
return render(request, self.template_name, {
'form': form,
'auth_backends': self.get_auth_backends(request),
})
def redirect_to_next(self, request, logger):
data = request.POST if request.method == "POST" else request.GET
redirect_url = data.get('next', settings.LOGIN_REDIRECT_URL)
if redirect_url and url_has_allowed_host_and_scheme(redirect_url, allowed_hosts=None):
logger.debug(f"Redirecting user to {redirect_url}")
else:
if redirect_url:
logger.warning(f"Ignoring unsafe 'next' URL passed to login form: {redirect_url}")
redirect_url = reverse('home')
return HttpResponseRedirect(redirect_url)
class LogoutView(View):
"""
Deauthenticate a web user.
"""
def get(self, request):
logger = logging.getLogger('netbox.auth.logout')
# Log out the user
username = request.user
auth_logout(request)
logger.info(f"User {username} has logged out")
messages.info(request, "You have logged out.")
# Delete session key cookie (if set) upon logout
response = HttpResponseRedirect(resolve_url(settings.LOGOUT_REDIRECT_URL))
response.delete_cookie('session_key')
return response
# #
# User profiles # User profiles
# #

View File

@ -1,19 +1,18 @@
from django.conf import settings from django.conf import settings
from django.conf.urls import include from django.conf.urls import include
from django.urls import path, re_path from django.urls import path
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.static import serve from django.views.static import serve
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
from account.views import LoginView, LogoutView
from extras.plugins.urls import plugin_admin_patterns, plugin_patterns, plugin_api_patterns from extras.plugins.urls import plugin_admin_patterns, plugin_patterns, plugin_api_patterns
from netbox.api.views import APIRootView, StatusView from netbox.api.views import APIRootView, StatusView
from netbox.graphql.schema import schema from netbox.graphql.schema import schema
from netbox.graphql.views import GraphQLView from netbox.graphql.views import GraphQLView
from netbox.views import HomeView, StaticMediaFailureView, SearchView, htmx from netbox.views import HomeView, StaticMediaFailureView, SearchView, htmx
from users.views import LoginView, LogoutView
from .admin import admin_site from .admin import admin_site
_patterns = [ _patterns = [
# Base views # Base views

View File

@ -1,151 +1,11 @@
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
from django.contrib.auth.models import update_last_login
from django.contrib.auth.signals import user_logged_in
from django.db.models import Count from django.db.models import Count
from django.http import HttpResponseRedirect
from django.shortcuts import render, resolve_url
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.http import url_has_allowed_host_and_scheme, urlencode
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import View
from social_core.backends.utils import load_backends
from extras.models import ObjectChange from extras.models import ObjectChange
from extras.tables import ObjectChangeTable from extras.tables import ObjectChangeTable
from netbox.authentication import get_auth_backend_display, get_saml_idps
from netbox.config import get_config
from netbox.views import generic from netbox.views import generic
from utilities.views import register_model_view from utilities.views import register_model_view
from . import filtersets, forms, tables from . import filtersets, forms, tables
from .models import NetBoxGroup, NetBoxUser, ObjectPermission, Token, UserConfig from .models import NetBoxGroup, NetBoxUser, ObjectPermission, Token
#
# Login/logout
#
class LoginView(View):
"""
Perform user authentication via the web UI.
"""
template_name = 'login.html'
@method_decorator(sensitive_post_parameters('password'))
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def gen_auth_data(self, name, url, params):
display_name, icon_name = get_auth_backend_display(name)
return {
'display_name': display_name,
'icon_name': icon_name,
'url': f'{url}?{urlencode(params)}',
}
def get_auth_backends(self, request):
auth_backends = []
saml_idps = get_saml_idps()
for name in load_backends(settings.AUTHENTICATION_BACKENDS).keys():
url = reverse('social:begin', args=[name])
params = {}
if next := request.GET.get('next'):
params['next'] = next
if name.lower() == 'saml' and saml_idps:
for idp in saml_idps:
params['idp'] = idp
data = self.gen_auth_data(name, url, params)
data['display_name'] = f'{data["display_name"]} ({idp})'
auth_backends.append(data)
else:
auth_backends.append(self.gen_auth_data(name, url, params))
return auth_backends
def get(self, request):
form = forms.LoginForm(request)
if request.user.is_authenticated:
logger = logging.getLogger('netbox.auth.login')
return self.redirect_to_next(request, logger)
return render(request, self.template_name, {
'form': form,
'auth_backends': self.get_auth_backends(request),
})
def post(self, request):
logger = logging.getLogger('netbox.auth.login')
form = forms.LoginForm(request, data=request.POST)
if form.is_valid():
logger.debug("Login form validation was successful")
# If maintenance mode is enabled, assume the database is read-only, and disable updating the user's
# last_login time upon authentication.
if get_config().MAINTENANCE_MODE:
logger.warning("Maintenance mode enabled: disabling update of most recent login time")
user_logged_in.disconnect(update_last_login, dispatch_uid='update_last_login')
# Authenticate user
auth_login(request, form.get_user())
logger.info(f"User {request.user} successfully authenticated")
messages.success(request, f"Logged in as {request.user}.")
# Ensure the user has a UserConfig defined. (This should normally be handled by
# create_userconfig() on user creation.)
if not hasattr(request.user, 'config'):
config = get_config()
UserConfig(user=request.user, data=config.DEFAULT_USER_PREFERENCES).save()
return self.redirect_to_next(request, logger)
else:
logger.debug(f"Login form validation failed for username: {form['username'].value()}")
return render(request, self.template_name, {
'form': form,
'auth_backends': self.get_auth_backends(request),
})
def redirect_to_next(self, request, logger):
data = request.POST if request.method == "POST" else request.GET
redirect_url = data.get('next', settings.LOGIN_REDIRECT_URL)
if redirect_url and url_has_allowed_host_and_scheme(redirect_url, allowed_hosts=None):
logger.debug(f"Redirecting user to {redirect_url}")
else:
if redirect_url:
logger.warning(f"Ignoring unsafe 'next' URL passed to login form: {redirect_url}")
redirect_url = reverse('home')
return HttpResponseRedirect(redirect_url)
class LogoutView(View):
"""
Deauthenticate a web user.
"""
def get(self, request):
logger = logging.getLogger('netbox.auth.logout')
# Log out the user
username = request.user
auth_logout(request)
logger.info(f"User {username} has logged out")
messages.info(request, "You have logged out.")
# Delete session key cookie (if set) upon logout
response = HttpResponseRedirect(resolve_url(settings.LOGOUT_REDIRECT_URL))
response.delete_cookie('session_key')
return response
# #