views.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. from __future__ import unicode_literals
  2. import base64
  3. from django.contrib import messages
  4. from django.contrib.auth.decorators import permission_required, login_required
  5. from django.contrib.auth.mixins import PermissionRequiredMixin
  6. from django.db import transaction, IntegrityError
  7. from django.db.models import Count
  8. from django.shortcuts import get_object_or_404, redirect, render
  9. from django.urls import reverse
  10. from django.utils.decorators import method_decorator
  11. from django.views.generic import View
  12. from dcim.models import Device
  13. from utilities.views import BulkDeleteView, BulkEditView, ObjectDeleteView, ObjectEditView, ObjectListView
  14. from . import filters, forms, tables
  15. from .decorators import userkey_required
  16. from .models import SecretRole, Secret, SessionKey
  17. def get_session_key(request):
  18. """
  19. Extract and decode the session key sent with a request. Returns None if no session key was provided.
  20. """
  21. session_key = request.COOKIES.get('session_key', None)
  22. if session_key is not None:
  23. return base64.b64decode(session_key)
  24. return session_key
  25. #
  26. # Secret roles
  27. #
  28. class SecretRoleListView(ObjectListView):
  29. queryset = SecretRole.objects.annotate(secret_count=Count('secrets'))
  30. table = tables.SecretRoleTable
  31. template_name = 'secrets/secretrole_list.html'
  32. class SecretRoleEditView(PermissionRequiredMixin, ObjectEditView):
  33. permission_required = 'secrets.change_secretrole'
  34. model = SecretRole
  35. form_class = forms.SecretRoleForm
  36. def get_return_url(self, request, obj):
  37. return reverse('secrets:secretrole_list')
  38. class SecretRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  39. permission_required = 'secrets.delete_secretrole'
  40. cls = SecretRole
  41. default_return_url = 'secrets:secretrole_list'
  42. #
  43. # Secrets
  44. #
  45. @method_decorator(login_required, name='dispatch')
  46. class SecretListView(ObjectListView):
  47. queryset = Secret.objects.select_related('role', 'device')
  48. filter = filters.SecretFilter
  49. filter_form = forms.SecretFilterForm
  50. table = tables.SecretTable
  51. template_name = 'secrets/secret_list.html'
  52. @method_decorator(login_required, name='dispatch')
  53. class SecretView(View):
  54. def get(self, request, pk):
  55. secret = get_object_or_404(Secret, pk=pk)
  56. return render(request, 'secrets/secret.html', {
  57. 'secret': secret,
  58. })
  59. @permission_required('secrets.add_secret')
  60. @userkey_required()
  61. def secret_add(request, pk):
  62. # Retrieve device
  63. device = get_object_or_404(Device, pk=pk)
  64. secret = Secret(device=device)
  65. session_key = get_session_key(request)
  66. if request.method == 'POST':
  67. form = forms.SecretForm(request.POST, instance=secret)
  68. if form.is_valid():
  69. # We need a valid session key in order to create a Secret
  70. if session_key is None:
  71. form.add_error(None, "No session key was provided with the request. Unable to encrypt secret data.")
  72. # Create and encrypt the new Secret
  73. else:
  74. master_key = None
  75. try:
  76. sk = SessionKey.objects.get(userkey__user=request.user)
  77. master_key = sk.get_master_key(session_key)
  78. except SessionKey.DoesNotExist:
  79. form.add_error(None, "No session key found for this user.")
  80. if master_key is not None:
  81. secret = form.save(commit=False)
  82. secret.plaintext = str(form.cleaned_data['plaintext'])
  83. secret.encrypt(master_key)
  84. secret.save()
  85. messages.success(request, "Added new secret: {}.".format(secret))
  86. if '_addanother' in request.POST:
  87. return redirect('dcim:device_addsecret', pk=device.pk)
  88. else:
  89. return redirect('secrets:secret', pk=secret.pk)
  90. else:
  91. form = forms.SecretForm(instance=secret)
  92. return render(request, 'secrets/secret_edit.html', {
  93. 'secret': secret,
  94. 'form': form,
  95. 'return_url': device.get_absolute_url(),
  96. })
  97. @permission_required('secrets.change_secret')
  98. @userkey_required()
  99. def secret_edit(request, pk):
  100. secret = get_object_or_404(Secret, pk=pk)
  101. session_key = get_session_key(request)
  102. if request.method == 'POST':
  103. form = forms.SecretForm(request.POST, instance=secret)
  104. if form.is_valid():
  105. # Re-encrypt the Secret if a plaintext and session key have been provided.
  106. if form.cleaned_data['plaintext'] and session_key is not None:
  107. # Retrieve the master key using the provided session key
  108. master_key = None
  109. try:
  110. sk = SessionKey.objects.get(userkey__user=request.user)
  111. master_key = sk.get_master_key(session_key)
  112. except SessionKey.DoesNotExist:
  113. form.add_error(None, "No session key found for this user.")
  114. # Create and encrypt the new Secret
  115. if master_key is not None:
  116. secret = form.save(commit=False)
  117. secret.plaintext = str(form.cleaned_data['plaintext'])
  118. secret.encrypt(master_key)
  119. secret.save()
  120. messages.success(request, "Modified secret {}.".format(secret))
  121. return redirect('secrets:secret', pk=secret.pk)
  122. else:
  123. form.add_error(None, "Invalid session key. Unable to encrypt secret data.")
  124. # We can't save the plaintext without a session key.
  125. elif form.cleaned_data['plaintext']:
  126. form.add_error(None, "No session key was provided with the request. Unable to encrypt secret data.")
  127. # If no new plaintext was specified, a session key is not needed.
  128. else:
  129. secret = form.save()
  130. messages.success(request, "Modified secret {}.".format(secret))
  131. return redirect('secrets:secret', pk=secret.pk)
  132. else:
  133. form = forms.SecretForm(instance=secret)
  134. return render(request, 'secrets/secret_edit.html', {
  135. 'secret': secret,
  136. 'form': form,
  137. 'return_url': reverse('secrets:secret', kwargs={'pk': secret.pk}),
  138. })
  139. class SecretDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  140. permission_required = 'secrets.delete_secret'
  141. model = Secret
  142. default_return_url = 'secrets:secret_list'
  143. @permission_required('secrets.add_secret')
  144. @userkey_required()
  145. def secret_import(request):
  146. session_key = request.COOKIES.get('session_key', None)
  147. if request.method == 'POST':
  148. form = forms.SecretImportForm(request.POST)
  149. if session_key is None:
  150. form.add_error(None, "No session key was provided with the request. Unable to encrypt secret data.")
  151. if form.is_valid():
  152. new_secrets = []
  153. session_key = base64.b64decode(session_key)
  154. master_key = None
  155. try:
  156. sk = SessionKey.objects.get(userkey__user=request.user)
  157. master_key = sk.get_master_key(session_key)
  158. except SessionKey.DoesNotExist:
  159. form.add_error(None, "No session key found for this user.")
  160. if master_key is None:
  161. form.add_error(None, "Invalid private key! Unable to encrypt secret data.")
  162. else:
  163. try:
  164. with transaction.atomic():
  165. for secret in form.cleaned_data['csv']:
  166. secret.encrypt(master_key)
  167. secret.save()
  168. new_secrets.append(secret)
  169. table = tables.SecretTable(new_secrets)
  170. messages.success(request, "Imported {} new secrets.".format(len(new_secrets)))
  171. return render(request, 'import_success.html', {
  172. 'table': table,
  173. 'return_url': 'secrets:secret_list',
  174. })
  175. except IntegrityError as e:
  176. form.add_error('csv', "Record {}: {}".format(len(new_secrets) + 1, e.__cause__))
  177. else:
  178. form = forms.SecretImportForm()
  179. return render(request, 'secrets/secret_import.html', {
  180. 'form': form,
  181. 'return_url': 'secrets:secret_list',
  182. })
  183. class SecretBulkEditView(PermissionRequiredMixin, BulkEditView):
  184. permission_required = 'secrets.change_secret'
  185. cls = Secret
  186. filter = filters.SecretFilter
  187. form = forms.SecretBulkEditForm
  188. template_name = 'secrets/secret_bulk_edit.html'
  189. default_return_url = 'secrets:secret_list'
  190. class SecretBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  191. permission_required = 'secrets.delete_secret'
  192. cls = Secret
  193. filter = filters.SecretFilter
  194. default_return_url = 'secrets:secret_list'