| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- 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
- 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, render
- from django.urls import reverse
- 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 utilities.forms import ConfirmationForm
- from .forms import LoginForm, PasswordChangeForm, TokenForm
- from .models import 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 get(self, request):
- form = 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,
- })
- def post(self, request):
- logger = logging.getLogger('netbox.auth.login')
- form = 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 settings.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.info(request, "Logged in as {}.".format(request.user))
- return self.redirect_to_next(request, logger)
- else:
- logger.debug("Login form validation failed")
- return render(request, self.template_name, {
- 'form': form,
- })
- def redirect_to_next(self, request, logger):
- if request.method == "POST":
- redirect_to = request.POST.get('next', reverse('home'))
- else:
- redirect_to = request.GET.get('next', reverse('home'))
- 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}")
- redirect_to = reverse('home')
- logger.debug(f"Redirecting user to {redirect_to}")
- return HttpResponseRedirect(redirect_to)
- 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(reverse('home'))
- response.delete_cookie('session_key')
- return response
- #
- # User profiles
- #
- class ProfileView(LoginRequiredMixin, View):
- template_name = 'users/profile.html'
- def get(self, request):
- return render(request, self.template_name, {
- 'active_tab': 'profile',
- })
- class UserConfigView(LoginRequiredMixin, View):
- template_name = 'users/preferences.html'
- def get(self, request):
- return render(request, self.template_name, {
- 'preferences': request.user.config.all(),
- 'active_tab': 'preferences',
- })
- def post(self, request):
- userconfig = request.user.config
- data = userconfig.all()
- # Delete selected preferences
- if "_delete" in request.POST:
- for key in request.POST.getlist('pk'):
- if key in data:
- userconfig.clear(key)
- # Update specific values
- elif "_update" in request.POST:
- for key in request.POST:
- if not key.startswith('_') and not key.startswith('csrf'):
- for value in request.POST.getlist(key):
- userconfig.set(key, value)
- userconfig.save()
- messages.success(request, "Your preferences have been updated.")
- return redirect('user:preferences')
- class ChangePasswordView(LoginRequiredMixin, View):
- template_name = 'users/password.html'
- def get(self, request):
- # LDAP users cannot change their password here
- if getattr(request.user, 'ldap_username', None):
- messages.warning(request, "LDAP-authenticated user credentials cannot be changed within NetBox.")
- return redirect('user:profile')
- form = PasswordChangeForm(user=request.user)
- return render(request, self.template_name, {
- 'form': form,
- 'active_tab': 'password',
- })
- def post(self, request):
- form = PasswordChangeForm(user=request.user, data=request.POST)
- if form.is_valid():
- form.save()
- update_session_auth_hash(request, form.user)
- messages.success(request, "Your password has been changed successfully.")
- return redirect('user:profile')
- return render(request, self.template_name, {
- 'form': form,
- 'active_tab': 'change_password',
- })
- #
- # API tokens
- #
- class TokenListView(LoginRequiredMixin, View):
- def get(self, request):
- tokens = Token.objects.filter(user=request.user)
- return render(request, 'users/api_tokens.html', {
- 'tokens': tokens,
- 'active_tab': 'api-tokens',
- })
- class TokenEditView(LoginRequiredMixin, View):
- def get(self, request, pk=None):
- if pk:
- token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
- else:
- token = Token(user=request.user)
- form = TokenForm(instance=token)
- return render(request, 'generic/object_edit.html', {
- 'obj': token,
- 'obj_type': token._meta.verbose_name,
- 'form': form,
- 'return_url': reverse('user:token_list'),
- })
- def post(self, request, pk=None):
- if pk:
- token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
- form = TokenForm(request.POST, instance=token)
- else:
- token = Token(user=request.user)
- form = TokenForm(request.POST)
- if form.is_valid():
- token = form.save(commit=False)
- token.user = request.user
- token.save()
- msg = f"Modified token {token}" if pk else f"Created token {token}"
- messages.success(request, msg)
- if '_addanother' in request.POST:
- return redirect(request.path)
- else:
- return redirect('user:token_list')
- return render(request, 'generic/object_edit.html', {
- 'obj': token,
- 'obj_type': token._meta.verbose_name,
- 'form': form,
- 'return_url': reverse('user:token_list'),
- })
- class TokenDeleteView(LoginRequiredMixin, View):
- def get(self, request, pk):
- token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
- initial_data = {
- 'return_url': reverse('user:token_list'),
- }
- form = ConfirmationForm(initial=initial_data)
- return render(request, 'generic/object_delete.html', {
- 'obj': token,
- 'obj_type': token._meta.verbose_name,
- 'form': form,
- 'return_url': reverse('user:token_list'),
- })
- def post(self, request, pk):
- token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
- form = ConfirmationForm(request.POST)
- if form.is_valid():
- token.delete()
- messages.success(request, "Token deleted")
- return redirect('user:token_list')
- return render(request, 'generic/object_delete.html', {
- 'obj': token,
- 'obj_type': token._meta.verbose_name,
- 'form': form,
- 'return_url': reverse('user:token_list'),
- })
|