views.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. import logging
  2. from django.conf import settings
  3. from django.contrib import messages
  4. from django.contrib.auth import login as auth_login, logout as auth_logout, update_session_auth_hash
  5. from django.contrib.auth.mixins import LoginRequiredMixin
  6. from django.contrib.auth.models import update_last_login
  7. from django.contrib.auth.signals import user_logged_in
  8. from django.http import HttpResponseForbidden, HttpResponseRedirect
  9. from django.shortcuts import get_object_or_404, redirect, render
  10. from django.urls import reverse
  11. from django.utils.decorators import method_decorator
  12. from django.utils.http import is_safe_url
  13. from django.views.decorators.debug import sensitive_post_parameters
  14. from django.views.generic import View
  15. from secrets.forms import UserKeyForm
  16. from secrets.models import SessionKey, UserKey
  17. from utilities.forms import ConfirmationForm
  18. from .forms import LoginForm, PasswordChangeForm, TokenForm
  19. from .models import Token
  20. #
  21. # Login/logout
  22. #
  23. class LoginView(View):
  24. """
  25. Perform user authentication via the web UI.
  26. """
  27. template_name = 'login.html'
  28. @method_decorator(sensitive_post_parameters('password'))
  29. def dispatch(self, *args, **kwargs):
  30. return super().dispatch(*args, **kwargs)
  31. def get(self, request):
  32. form = LoginForm(request)
  33. if request.user.is_authenticated:
  34. logger = logging.getLogger('netbox.auth.login')
  35. return self.redirect_to_next(request, logger)
  36. return render(request, self.template_name, {
  37. 'form': form,
  38. })
  39. def post(self, request):
  40. logger = logging.getLogger('netbox.auth.login')
  41. form = LoginForm(request, data=request.POST)
  42. if form.is_valid():
  43. logger.debug("Login form validation was successful")
  44. # If maintenance mode is enabled, assume the database is read-only, and disable updating the user's
  45. # last_login time upon authentication.
  46. if settings.MAINTENANCE_MODE:
  47. logger.warning("Maintenance mode enabled: disabling update of most recent login time")
  48. user_logged_in.disconnect(update_last_login, dispatch_uid='update_last_login')
  49. # Authenticate user
  50. auth_login(request, form.get_user())
  51. logger.info(f"User {request.user} successfully authenticated")
  52. messages.info(request, "Logged in as {}.".format(request.user))
  53. return self.redirect_to_next(request, logger)
  54. else:
  55. logger.debug("Login form validation failed")
  56. return render(request, self.template_name, {
  57. 'form': form,
  58. })
  59. def redirect_to_next(self, request, logger):
  60. if request.method == "POST":
  61. redirect_to = request.POST.get('next', reverse('home'))
  62. else:
  63. redirect_to = request.GET.get('next', reverse('home'))
  64. if redirect_to and not is_safe_url(url=redirect_to, allowed_hosts=request.get_host()):
  65. logger.warning(f"Ignoring unsafe 'next' URL passed to login form: {redirect_to}")
  66. redirect_to = reverse('home')
  67. logger.debug(f"Redirecting user to {redirect_to}")
  68. return HttpResponseRedirect(redirect_to)
  69. class LogoutView(View):
  70. """
  71. Deauthenticate a web user.
  72. """
  73. def get(self, request):
  74. logger = logging.getLogger('netbox.auth.logout')
  75. # Log out the user
  76. username = request.user
  77. auth_logout(request)
  78. logger.info(f"User {username} has logged out")
  79. messages.info(request, "You have logged out.")
  80. # Delete session key cookie (if set) upon logout
  81. response = HttpResponseRedirect(reverse('home'))
  82. response.delete_cookie('session_key')
  83. return response
  84. #
  85. # User profiles
  86. #
  87. class ProfileView(LoginRequiredMixin, View):
  88. template_name = 'users/profile.html'
  89. def get(self, request):
  90. return render(request, self.template_name, {
  91. 'active_tab': 'profile',
  92. })
  93. class UserConfigView(LoginRequiredMixin, View):
  94. template_name = 'users/preferences.html'
  95. def get(self, request):
  96. return render(request, self.template_name, {
  97. 'preferences': request.user.config.all(),
  98. 'active_tab': 'preferences',
  99. })
  100. def post(self, request):
  101. userconfig = request.user.config
  102. data = userconfig.all()
  103. # Delete selected preferences
  104. for key in request.POST.getlist('pk'):
  105. if key in data:
  106. userconfig.clear(key)
  107. userconfig.save()
  108. messages.success(request, "Your preferences have been updated.")
  109. return redirect('user:preferences')
  110. class ChangePasswordView(LoginRequiredMixin, View):
  111. template_name = 'users/change_password.html'
  112. def get(self, request):
  113. # LDAP users cannot change their password here
  114. if getattr(request.user, 'ldap_username', None):
  115. messages.warning(request, "LDAP-authenticated user credentials cannot be changed within NetBox.")
  116. return redirect('user:profile')
  117. form = PasswordChangeForm(user=request.user)
  118. return render(request, self.template_name, {
  119. 'form': form,
  120. 'active_tab': 'change_password',
  121. })
  122. def post(self, request):
  123. form = PasswordChangeForm(user=request.user, data=request.POST)
  124. if form.is_valid():
  125. form.save()
  126. update_session_auth_hash(request, form.user)
  127. messages.success(request, "Your password has been changed successfully.")
  128. return redirect('user:profile')
  129. return render(request, self.template_name, {
  130. 'form': form,
  131. 'active_tab': 'change_password',
  132. })
  133. class UserKeyView(LoginRequiredMixin, View):
  134. template_name = 'users/userkey.html'
  135. def get(self, request):
  136. try:
  137. userkey = UserKey.objects.get(user=request.user)
  138. except UserKey.DoesNotExist:
  139. userkey = None
  140. return render(request, self.template_name, {
  141. 'userkey': userkey,
  142. 'active_tab': 'userkey',
  143. })
  144. class UserKeyEditView(LoginRequiredMixin, View):
  145. template_name = 'users/userkey_edit.html'
  146. def dispatch(self, request, *args, **kwargs):
  147. try:
  148. self.userkey = UserKey.objects.get(user=request.user)
  149. except UserKey.DoesNotExist:
  150. self.userkey = UserKey(user=request.user)
  151. return super().dispatch(request, *args, **kwargs)
  152. def get(self, request):
  153. form = UserKeyForm(instance=self.userkey)
  154. return render(request, self.template_name, {
  155. 'userkey': self.userkey,
  156. 'form': form,
  157. 'active_tab': 'userkey',
  158. })
  159. def post(self, request):
  160. form = UserKeyForm(data=request.POST, instance=self.userkey)
  161. if form.is_valid():
  162. uk = form.save(commit=False)
  163. uk.user = request.user
  164. uk.save()
  165. messages.success(request, "Your user key has been saved.")
  166. return redirect('user:userkey')
  167. return render(request, self.template_name, {
  168. 'userkey': self.userkey,
  169. 'form': form,
  170. 'active_tab': 'userkey',
  171. })
  172. class SessionKeyDeleteView(LoginRequiredMixin, View):
  173. def get(self, request):
  174. sessionkey = get_object_or_404(SessionKey, userkey__user=request.user)
  175. form = ConfirmationForm()
  176. return render(request, 'users/sessionkey_delete.html', {
  177. 'obj_type': sessionkey._meta.verbose_name,
  178. 'form': form,
  179. 'return_url': reverse('user:userkey'),
  180. })
  181. def post(self, request):
  182. sessionkey = get_object_or_404(SessionKey, userkey__user=request.user)
  183. form = ConfirmationForm(request.POST)
  184. if form.is_valid():
  185. # Delete session key
  186. sessionkey.delete()
  187. messages.success(request, "Session key deleted")
  188. # Delete cookie
  189. response = redirect('user:userkey')
  190. response.delete_cookie('session_key')
  191. return response
  192. return render(request, 'users/sessionkey_delete.html', {
  193. 'obj_type': sessionkey._meta.verbose_name,
  194. 'form': form,
  195. 'return_url': reverse('user:userkey'),
  196. })
  197. #
  198. # API tokens
  199. #
  200. class TokenListView(LoginRequiredMixin, View):
  201. def get(self, request):
  202. tokens = Token.objects.filter(user=request.user)
  203. return render(request, 'users/api_tokens.html', {
  204. 'tokens': tokens,
  205. 'active_tab': 'api_tokens',
  206. })
  207. class TokenEditView(LoginRequiredMixin, View):
  208. def get(self, request, pk=None):
  209. if pk is not None:
  210. if not request.user.has_perm('users.change_token'):
  211. return HttpResponseForbidden()
  212. token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
  213. else:
  214. if not request.user.has_perm('users.add_token'):
  215. return HttpResponseForbidden()
  216. token = Token(user=request.user)
  217. form = TokenForm(instance=token)
  218. return render(request, 'generic/object_edit.html', {
  219. 'obj': token,
  220. 'obj_type': token._meta.verbose_name,
  221. 'form': form,
  222. 'return_url': reverse('user:token_list'),
  223. })
  224. def post(self, request, pk=None):
  225. if pk is not None:
  226. token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
  227. form = TokenForm(request.POST, instance=token)
  228. else:
  229. token = Token()
  230. form = TokenForm(request.POST)
  231. if form.is_valid():
  232. token = form.save(commit=False)
  233. token.user = request.user
  234. token.save()
  235. msg = "Modified token {}".format(token) if pk else "Created token {}".format(token)
  236. messages.success(request, msg)
  237. if '_addanother' in request.POST:
  238. return redirect(request.path)
  239. else:
  240. return redirect('user:token_list')
  241. return render(request, 'generic/object_edit.html', {
  242. 'obj': token,
  243. 'obj_type': token._meta.verbose_name,
  244. 'form': form,
  245. 'return_url': reverse('user:token_list'),
  246. })
  247. class TokenDeleteView(LoginRequiredMixin, View):
  248. def get(self, request, pk):
  249. token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
  250. initial_data = {
  251. 'return_url': reverse('user:token_list'),
  252. }
  253. form = ConfirmationForm(initial=initial_data)
  254. return render(request, 'generic/object_delete.html', {
  255. 'obj': token,
  256. 'obj_type': token._meta.verbose_name,
  257. 'form': form,
  258. 'return_url': reverse('user:token_list'),
  259. })
  260. def post(self, request, pk):
  261. token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
  262. form = ConfirmationForm(request.POST)
  263. if form.is_valid():
  264. token.delete()
  265. messages.success(request, "Token deleted")
  266. return redirect('user:token_list')
  267. return render(request, 'generic/object_delete.html', {
  268. 'obj': token,
  269. 'obj_type': token._meta.verbose_name,
  270. 'form': form,
  271. 'return_url': reverse('user:token_list'),
  272. })