from __future__ import unicode_literals import base64 from django.contrib import messages from django.contrib.auth.decorators import permission_required, login_required from django.contrib.auth.mixins import PermissionRequiredMixin from django.db.models import Count from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.decorators import method_decorator from django.views.generic import View from dcim.models import Device from utilities.views import ( BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView, ) from . import filters, forms, tables from .decorators import userkey_required from .models import SecretRole, Secret, SessionKey def get_session_key(request): """ Extract and decode the session key sent with a request. Returns None if no session key was provided. """ session_key = request.COOKIES.get('session_key', None) if session_key is not None: return base64.b64decode(session_key) return session_key # # Secret roles # class SecretRoleListView(ObjectListView): queryset = SecretRole.objects.annotate(secret_count=Count('secrets')) table = tables.SecretRoleTable template_name = 'secrets/secretrole_list.html' class SecretRoleCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'secrets.add_secretrole' model = SecretRole model_form = forms.SecretRoleForm def get_return_url(self, request, obj): return reverse('secrets:secretrole_list') class SecretRoleEditView(SecretRoleCreateView): permission_required = 'secrets.change_secretrole' class SecretRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'secrets.delete_secretrole' cls = SecretRole queryset = SecretRole.objects.annotate(secret_count=Count('secrets')) table = tables.SecretRoleTable default_return_url = 'secrets:secretrole_list' # # Secrets # @method_decorator(login_required, name='dispatch') class SecretListView(ObjectListView): queryset = Secret.objects.select_related('role', 'device') filter = filters.SecretFilter filter_form = forms.SecretFilterForm table = tables.SecretTable template_name = 'secrets/secret_list.html' @method_decorator(login_required, name='dispatch') class SecretView(View): def get(self, request, pk): secret = get_object_or_404(Secret, pk=pk) return render(request, 'secrets/secret.html', { 'secret': secret, }) @permission_required('secrets.add_secret') @userkey_required() def secret_add(request, pk): # Retrieve device device = get_object_or_404(Device, pk=pk) secret = Secret(device=device) session_key = get_session_key(request) if request.method == 'POST': form = forms.SecretForm(request.POST, instance=secret) if form.is_valid(): # We need a valid session key in order to create a Secret if session_key is None: form.add_error(None, "No session key was provided with the request. Unable to encrypt secret data.") # Create and encrypt the new Secret else: master_key = None try: sk = SessionKey.objects.get(userkey__user=request.user) master_key = sk.get_master_key(session_key) except SessionKey.DoesNotExist: form.add_error(None, "No session key found for this user.") if master_key is not None: secret = form.save(commit=False) secret.plaintext = str(form.cleaned_data['plaintext']) secret.encrypt(master_key) secret.save() messages.success(request, "Added new secret: {}.".format(secret)) if '_addanother' in request.POST: return redirect('dcim:device_addsecret', pk=device.pk) else: return redirect('secrets:secret', pk=secret.pk) else: form = forms.SecretForm(instance=secret) return render(request, 'secrets/secret_edit.html', { 'secret': secret, 'form': form, 'return_url': device.get_absolute_url(), }) @permission_required('secrets.change_secret') @userkey_required() def secret_edit(request, pk): secret = get_object_or_404(Secret, pk=pk) session_key = get_session_key(request) if request.method == 'POST': form = forms.SecretForm(request.POST, instance=secret) if form.is_valid(): # Re-encrypt the Secret if a plaintext and session key have been provided. if form.cleaned_data['plaintext'] and session_key is not None: # Retrieve the master key using the provided session key master_key = None try: sk = SessionKey.objects.get(userkey__user=request.user) master_key = sk.get_master_key(session_key) except SessionKey.DoesNotExist: form.add_error(None, "No session key found for this user.") # Create and encrypt the new Secret if master_key is not None: secret = form.save(commit=False) secret.plaintext = str(form.cleaned_data['plaintext']) secret.encrypt(master_key) secret.save() messages.success(request, "Modified secret {}.".format(secret)) return redirect('secrets:secret', pk=secret.pk) else: form.add_error(None, "Invalid session key. Unable to encrypt secret data.") # We can't save the plaintext without a session key. elif form.cleaned_data['plaintext']: form.add_error(None, "No session key was provided with the request. Unable to encrypt secret data.") # If no new plaintext was specified, a session key is not needed. else: secret = form.save() messages.success(request, "Modified secret {}.".format(secret)) return redirect('secrets:secret', pk=secret.pk) else: form = forms.SecretForm(instance=secret) return render(request, 'secrets/secret_edit.html', { 'secret': secret, 'form': form, 'return_url': reverse('secrets:secret', kwargs={'pk': secret.pk}), }) class SecretDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'secrets.delete_secret' model = Secret default_return_url = 'secrets:secret_list' class SecretBulkImportView(BulkImportView): permission_required = 'ipam.add_vlan' model_form = forms.SecretCSVForm table = tables.SecretTable default_return_url = 'secrets:secret_list' master_key = None def _save_obj(self, obj_form): """ Encrypt each object before saving it to the database. """ obj = obj_form.save(commit=False) obj.encrypt(self.master_key) obj.save() return obj def post(self, request): # Grab the session key from cookies. session_key = request.COOKIES.get('session_key') if session_key: # Attempt to derive the master key using the provided session key. try: sk = SessionKey.objects.get(userkey__user=request.user) self.master_key = sk.get_master_key(base64.b64decode(session_key)) except SessionKey.DoesNotExist: messages.error(request, "No session key found for this user.") if self.master_key is not None: return super(SecretBulkImportView, self).post(request) else: messages.error(request, "Invalid private key! Unable to encrypt secret data.") else: messages.error(request, "No session key was provided with the request. Unable to encrypt secret data.") return render(request, self.template_name, { 'form': self._import_form(request.POST), 'fields': self.model_form().fields, 'obj_type': self.model_form._meta.model._meta.verbose_name, 'return_url': self.default_return_url, }) class SecretBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'secrets.change_secret' cls = Secret queryset = Secret.objects.select_related('role', 'device') filter = filters.SecretFilter table = tables.SecretTable form = forms.SecretBulkEditForm default_return_url = 'secrets:secret_list' class SecretBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'secrets.delete_secret' cls = Secret queryset = Secret.objects.select_related('role', 'device') filter = filters.SecretFilter table = tables.SecretTable default_return_url = 'secrets:secret_list'