Parcourir la source

Converted secrets import view to new scheme

Jeremy Stretch il y a 8 ans
Parent
commit
af604aba31

+ 1 - 2
netbox/ipam/views.py

@@ -13,8 +13,7 @@ from django.views.generic import View
 from dcim.models import Device
 from utilities.paginator import EnhancedPaginator
 from utilities.views import (
-    BulkAddView, BulkDeleteView, BulkEditView, BulkImportView, BulkImportView2, ObjectDeleteView, ObjectEditView,
-    ObjectListView,
+    BulkAddView, BulkDeleteView, BulkEditView, BulkImportView2, ObjectDeleteView, ObjectEditView, ObjectListView,
 )
 from . import filters, forms, tables
 from .models import (

+ 22 - 12
netbox/secrets/forms.py

@@ -7,7 +7,7 @@ from django import forms
 from django.db.models import Count
 
 from dcim.models import Device
-from utilities.forms import BootstrapMixin, BulkEditForm, BulkImportForm, CSVDataField, FilterChoiceField, SlugField
+from utilities.forms import BootstrapMixin, BulkEditForm, CSVDataField2, FilterChoiceField, SlugField
 from .models import Secret, SecretRole, UserKey
 
 
@@ -65,27 +65,37 @@ class SecretForm(BootstrapMixin, forms.ModelForm):
             })
 
 
-class SecretFromCSVForm(forms.ModelForm):
-    device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, to_field_name='name',
-                                    error_messages={'invalid_choice': 'Device not found.'})
-    role = forms.ModelChoiceField(queryset=SecretRole.objects.all(), to_field_name='name',
-                                  error_messages={'invalid_choice': 'Invalid secret role.'})
-    plaintext = forms.CharField()
+class SecretCSVForm(forms.ModelForm):
+    device = forms.ModelChoiceField(
+        queryset=Device.objects.all(),
+        to_field_name='name',
+        help_text='Device name',
+        error_messages={
+            'invalid_choice': 'Device not found.',
+        }
+    )
+    role = forms.ModelChoiceField(
+        queryset=SecretRole.objects.all(),
+        to_field_name='name',
+        help_text='Name of assigned role',
+        error_messages={
+            'invalid_choice': 'Invalid secret role.',
+        }
+    )
+    plaintext = forms.CharField(
+        help_text='Plaintext secret data'
+    )
 
     class Meta:
         model = Secret
         fields = ['device', 'role', 'name', 'plaintext']
 
     def save(self, *args, **kwargs):
-        s = super(SecretFromCSVForm, self).save(*args, **kwargs)
+        s = super(SecretCSVForm, self).save(*args, **kwargs)
         s.plaintext = str(self.cleaned_data['plaintext'])
         return s
 
 
-class SecretImportForm(BootstrapMixin, BulkImportForm):
-    csv = CSVDataField(csv_form=SecretFromCSVForm, widget=forms.Textarea(attrs={'class': 'requires-session-key'}))
-
-
 class SecretBulkEditForm(BootstrapMixin, BulkEditForm):
     pk = forms.ModelMultipleChoiceField(queryset=Secret.objects.all(), widget=forms.MultipleHiddenInput)
     role = forms.ModelChoiceField(queryset=SecretRole.objects.all(), required=False)

+ 1 - 1
netbox/secrets/urls.py

@@ -16,7 +16,7 @@ urlpatterns = [
 
     # Secrets
     url(r'^secrets/$', views.SecretListView.as_view(), name='secret_list'),
-    url(r'^secrets/import/$', views.secret_import, name='secret_import'),
+    url(r'^secrets/import/$', views.SecretBulkImportView.as_view(), name='secret_import'),
     url(r'^secrets/edit/$', views.SecretBulkEditView.as_view(), name='secret_bulk_edit'),
     url(r'^secrets/delete/$', views.SecretBulkDeleteView.as_view(), name='secret_bulk_delete'),
     url(r'^secrets/(?P<pk>\d+)/$', views.SecretView.as_view(), name='secret'),

+ 35 - 41
netbox/secrets/views.py

@@ -12,7 +12,9 @@ from django.utils.decorators import method_decorator
 from django.views.generic import View
 
 from dcim.models import Device
-from utilities.views import BulkDeleteView, BulkEditView, ObjectDeleteView, ObjectEditView, ObjectListView
+from utilities.views import (
+    BulkDeleteView, BulkEditView, BulkImportView2, ObjectDeleteView, ObjectEditView, ObjectListView,
+)
 from . import filters, forms, tables
 from .decorators import userkey_required
 from .models import SecretRole, Secret, SessionKey
@@ -185,58 +187,50 @@ class SecretDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     default_return_url = 'secrets:secret_list'
 
 
-@permission_required('secrets.add_secret')
-@userkey_required()
-def secret_import(request):
-
-    session_key = request.COOKIES.get('session_key', None)
+class SecretBulkImportView(BulkImportView2):
+    permission_required = 'ipam.add_vlan'
+    model_form = forms.SecretCSVForm
+    table = tables.SecretTable
+    default_return_url = 'secrets:secret_list'
 
-    if request.method == 'POST':
-        form = forms.SecretImportForm(request.POST)
+    master_key = None
 
-        if session_key is None:
-            form.add_error(None, "No session key was provided with the request. Unable to encrypt secret data.")
+    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
 
-        if form.is_valid():
+    def post(self, request):
 
-            new_secrets = []
+        # Grab the session key from cookies.
+        session_key = request.COOKIES.get('session_key')
+        if session_key:
 
-            session_key = base64.b64decode(session_key)
-            master_key = None
+            # Attempt to derive the master key using the provided session key.
             try:
                 sk = SessionKey.objects.get(userkey__user=request.user)
-                master_key = sk.get_master_key(session_key)
+                self.master_key = sk.get_master_key(base64.b64decode(session_key))
             except SessionKey.DoesNotExist:
-                form.add_error(None, "No session key found for this user.")
+                messages.error(request, "No session key found for this user.")
 
-            if master_key is None:
-                form.add_error(None, "Invalid private key! Unable to encrypt secret data.")
+            if self.master_key is not None:
+                return super(SecretBulkImportView, self).post(request)
             else:
-                try:
-                    with transaction.atomic():
-                        for secret in form.cleaned_data['csv']:
-                            secret.encrypt(master_key)
-                            secret.save()
-                            new_secrets.append(secret)
-
-                    table = tables.SecretTable(new_secrets)
-                    messages.success(request, "Imported {} new secrets.".format(len(new_secrets)))
-
-                    return render(request, 'import_success.html', {
-                        'table': table,
-                        'return_url': 'secrets:secret_list',
-                    })
-
-                except IntegrityError as e:
-                    form.add_error('csv', "Record {}: {}".format(len(new_secrets) + 1, e.__cause__))
+                messages.error(request, "Invalid private key! Unable to encrypt secret data.")
 
-    else:
-        form = forms.SecretImportForm()
+        else:
+            messages.error(request, "No session key was provided with the request. Unable to encrypt secret data.")
 
-    return render(request, 'secrets/secret_import.html', {
-        'form': form,
-        'return_url': 'secrets:secret_list',
-    })
+        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):

+ 1 - 1
netbox/tenancy/views.py

@@ -10,7 +10,7 @@ from circuits.models import Circuit
 from dcim.models import Site, Rack, Device
 from ipam.models import IPAddress, Prefix, VLAN, VRF
 from utilities.views import (
-    BulkDeleteView, BulkEditView, BulkImportView, BulkImportView2, ObjectDeleteView, ObjectEditView, ObjectListView,
+    BulkDeleteView, BulkEditView, BulkImportView2, ObjectDeleteView, ObjectEditView, ObjectListView,
 )
 from .models import Tenant, TenantGroup
 from . import filters, forms, tables

+ 7 - 4
netbox/utilities/views.py

@@ -448,6 +448,12 @@ class BulkImportView2(View):
 
         return ImportForm(*args, **kwargs)
 
+    def _save_obj(self, obj_form):
+        """
+        Provide a hook to modify the object immediately before saving it (e.g. to encrypt secret data).
+        """
+        return obj_form.save()
+
     def get(self, request):
 
         return render(request, self.template_name, {
@@ -471,7 +477,7 @@ class BulkImportView2(View):
                     for row, data in enumerate(form.cleaned_data['csv'], start=1):
                         obj_form = self.model_form(data)
                         if obj_form.is_valid():
-                            obj = obj_form.save()
+                            obj = self._save_obj(obj_form)
                             new_objs.append(obj)
                         else:
                             for field, err in obj_form.errors.items():
@@ -501,9 +507,6 @@ class BulkImportView2(View):
             'return_url': self.default_return_url,
         })
 
-    def save_obj(self, obj):
-        obj.save()
-
 
 class BulkEditView(View):
     """