views.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import base64
  2. import logging
  3. from django.contrib import messages
  4. from django.shortcuts import redirect, render
  5. from django.utils.html import escape
  6. from django.utils.safestring import mark_safe
  7. from netbox.views import generic
  8. from utilities.tables import paginate_table
  9. from utilities.utils import count_related
  10. from . import filtersets, forms, tables
  11. from .models import SecretRole, Secret, SessionKey, UserKey
  12. def get_session_key(request):
  13. """
  14. Extract and decode the session key sent with a request. Returns None if no session key was provided.
  15. """
  16. session_key = request.COOKIES.get('session_key', None)
  17. if session_key is not None:
  18. return base64.b64decode(session_key)
  19. return session_key
  20. #
  21. # Secret roles
  22. #
  23. class SecretRoleListView(generic.ObjectListView):
  24. queryset = SecretRole.objects.annotate(
  25. secret_count=count_related(Secret, 'role')
  26. )
  27. table = tables.SecretRoleTable
  28. class SecretRoleView(generic.ObjectView):
  29. queryset = SecretRole.objects.all()
  30. def get_extra_context(self, request, instance):
  31. secrets = Secret.objects.restrict(request.user, 'view').filter(
  32. role=instance
  33. )
  34. secrets_table = tables.SecretTable(secrets)
  35. secrets_table.columns.hide('role')
  36. paginate_table(secrets_table, request)
  37. return {
  38. 'secrets_table': secrets_table,
  39. }
  40. class SecretRoleEditView(generic.ObjectEditView):
  41. queryset = SecretRole.objects.all()
  42. model_form = forms.SecretRoleForm
  43. class SecretRoleDeleteView(generic.ObjectDeleteView):
  44. queryset = SecretRole.objects.all()
  45. class SecretRoleBulkImportView(generic.BulkImportView):
  46. queryset = SecretRole.objects.all()
  47. model_form = forms.SecretRoleCSVForm
  48. table = tables.SecretRoleTable
  49. class SecretRoleBulkEditView(generic.BulkEditView):
  50. queryset = SecretRole.objects.annotate(
  51. secret_count=count_related(Secret, 'role')
  52. )
  53. filterset = filtersets.SecretRoleFilterSet
  54. table = tables.SecretRoleTable
  55. form = forms.SecretRoleBulkEditForm
  56. class SecretRoleBulkDeleteView(generic.BulkDeleteView):
  57. queryset = SecretRole.objects.annotate(
  58. secret_count=count_related(Secret, 'role')
  59. )
  60. table = tables.SecretRoleTable
  61. #
  62. # Secrets
  63. #
  64. def inject_deprecation_warning(request):
  65. """
  66. Inject a warning message notifying the user of the pending removal of secrets functionality.
  67. """
  68. messages.warning(
  69. request,
  70. mark_safe('<i class="mdi mdi-alert"></i> The secrets functionality will be moved to a plugin in NetBox v3.0. '
  71. 'Please see <a href="https://github.com/netbox-community/netbox/issues/5278">issue #5278</a> for '
  72. 'more information.')
  73. )
  74. class SecretListView(generic.ObjectListView):
  75. queryset = Secret.objects.all()
  76. filterset = filtersets.SecretFilterSet
  77. filterset_form = forms.SecretFilterForm
  78. table = tables.SecretTable
  79. action_buttons = ('import', 'export')
  80. def get(self, request):
  81. inject_deprecation_warning(request)
  82. return super().get(request)
  83. class SecretView(generic.ObjectView):
  84. queryset = Secret.objects.all()
  85. def get(self, request, *args, **kwargs):
  86. inject_deprecation_warning(request)
  87. return super().get(request, *args, **kwargs)
  88. class SecretEditView(generic.ObjectEditView):
  89. queryset = Secret.objects.all()
  90. model_form = forms.SecretForm
  91. template_name = 'secrets/secret_edit.html'
  92. def dispatch(self, request, *args, **kwargs):
  93. # Check that the user has a valid UserKey
  94. try:
  95. uk = UserKey.objects.get(user=request.user)
  96. except UserKey.DoesNotExist:
  97. messages.warning(request, "This operation requires an active user key, but you don't have one.")
  98. return redirect('user:userkey')
  99. if not uk.is_active():
  100. messages.warning(request, "This operation is not available. Your user key has not been activated.")
  101. return redirect('user:userkey')
  102. return super().dispatch(request, *args, **kwargs)
  103. def post(self, request, *args, **kwargs):
  104. logger = logging.getLogger('netbox.views.ObjectEditView')
  105. session_key = get_session_key(request)
  106. secret = self.get_object(kwargs)
  107. form = self.model_form(request.POST, instance=secret)
  108. if form.is_valid():
  109. logger.debug("Form validation was successful")
  110. secret = form.save(commit=False)
  111. # We must have a session key in order to set the plaintext of a Secret
  112. if form.cleaned_data['plaintext'] and session_key is None:
  113. logger.debug("Unable to proceed: No session key was provided with the request")
  114. form.add_error(None, "No session key was provided with the request. Unable to encrypt secret data.")
  115. elif form.cleaned_data['plaintext']:
  116. master_key = None
  117. try:
  118. sk = SessionKey.objects.get(userkey__user=request.user)
  119. master_key = sk.get_master_key(session_key)
  120. except SessionKey.DoesNotExist:
  121. logger.debug("Unable to proceed: User has no session key assigned")
  122. form.add_error(None, "No session key found for this user.")
  123. if master_key is not None:
  124. logger.debug("Successfully resolved master key for encryption")
  125. secret.plaintext = str(form.cleaned_data['plaintext'])
  126. secret.encrypt(master_key)
  127. secret.save()
  128. form.save_m2m()
  129. msg = '{} secret'.format('Created' if not form.instance.pk else 'Modified')
  130. logger.info(f"{msg} {secret} (PK: {secret.pk})")
  131. msg = f'{msg} <a href="{secret.get_absolute_url()}">{escape(secret)}</a>'
  132. messages.success(request, mark_safe(msg))
  133. return redirect(self.get_return_url(request, secret))
  134. else:
  135. logger.debug("Form validation failed")
  136. return render(request, self.template_name, {
  137. 'obj': secret,
  138. 'obj_type': self.queryset.model._meta.verbose_name,
  139. 'form': form,
  140. 'return_url': self.get_return_url(request, secret),
  141. })
  142. class SecretDeleteView(generic.ObjectDeleteView):
  143. queryset = Secret.objects.all()
  144. class SecretBulkImportView(generic.BulkImportView):
  145. queryset = Secret.objects.all()
  146. model_form = forms.SecretCSVForm
  147. table = tables.SecretTable
  148. template_name = 'secrets/secret_import.html'
  149. widget_attrs = {'class': 'requires-session-key'}
  150. master_key = None
  151. def _save_obj(self, obj_form, request):
  152. """
  153. Encrypt each object before saving it to the database.
  154. """
  155. obj = obj_form.save(commit=False)
  156. obj.encrypt(self.master_key)
  157. obj.save()
  158. return obj
  159. def post(self, request):
  160. # Grab the session key from cookies.
  161. session_key = request.COOKIES.get('session_key')
  162. if session_key:
  163. # Attempt to derive the master key using the provided session key.
  164. try:
  165. sk = SessionKey.objects.get(userkey__user=request.user)
  166. self.master_key = sk.get_master_key(base64.b64decode(session_key))
  167. except SessionKey.DoesNotExist:
  168. messages.error(request, "No session key found for this user.")
  169. if self.master_key is not None:
  170. return super().post(request)
  171. else:
  172. messages.error(request, "Invalid private key! Unable to encrypt secret data.")
  173. else:
  174. messages.error(request, "No session key was provided with the request. Unable to encrypt secret data.")
  175. return render(request, self.template_name, {
  176. 'form': self._import_form(request.POST),
  177. 'fields': self.model_form().fields,
  178. 'obj_type': self.model_form._meta.model._meta.verbose_name,
  179. 'return_url': self.get_return_url(request),
  180. })
  181. class SecretBulkEditView(generic.BulkEditView):
  182. queryset = Secret.objects.prefetch_related('role')
  183. filterset = filtersets.SecretFilterSet
  184. table = tables.SecretTable
  185. form = forms.SecretBulkEditForm
  186. class SecretBulkDeleteView(generic.BulkDeleteView):
  187. queryset = Secret.objects.prefetch_related('role')
  188. filterset = filtersets.SecretFilterSet
  189. table = tables.SecretTable