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 dcim.models import Device
 from utilities.paginator import EnhancedPaginator
 from utilities.paginator import EnhancedPaginator
 from utilities.views import (
 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 . import filters, forms, tables
 from .models import (
 from .models import (

+ 22 - 12
netbox/secrets/forms.py

@@ -7,7 +7,7 @@ from django import forms
 from django.db.models import Count
 from django.db.models import Count
 
 
 from dcim.models import Device
 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
 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:
     class Meta:
         model = Secret
         model = Secret
         fields = ['device', 'role', 'name', 'plaintext']
         fields = ['device', 'role', 'name', 'plaintext']
 
 
     def save(self, *args, **kwargs):
     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'])
         s.plaintext = str(self.cleaned_data['plaintext'])
         return s
         return s
 
 
 
 
-class SecretImportForm(BootstrapMixin, BulkImportForm):
-    csv = CSVDataField(csv_form=SecretFromCSVForm, widget=forms.Textarea(attrs={'class': 'requires-session-key'}))
-
-
 class SecretBulkEditForm(BootstrapMixin, BulkEditForm):
 class SecretBulkEditForm(BootstrapMixin, BulkEditForm):
     pk = forms.ModelMultipleChoiceField(queryset=Secret.objects.all(), widget=forms.MultipleHiddenInput)
     pk = forms.ModelMultipleChoiceField(queryset=Secret.objects.all(), widget=forms.MultipleHiddenInput)
     role = forms.ModelChoiceField(queryset=SecretRole.objects.all(), required=False)
     role = forms.ModelChoiceField(queryset=SecretRole.objects.all(), required=False)

+ 1 - 1
netbox/secrets/urls.py

@@ -16,7 +16,7 @@ urlpatterns = [
 
 
     # Secrets
     # Secrets
     url(r'^secrets/$', views.SecretListView.as_view(), name='secret_list'),
     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/edit/$', views.SecretBulkEditView.as_view(), name='secret_bulk_edit'),
     url(r'^secrets/delete/$', views.SecretBulkDeleteView.as_view(), name='secret_bulk_delete'),
     url(r'^secrets/delete/$', views.SecretBulkDeleteView.as_view(), name='secret_bulk_delete'),
     url(r'^secrets/(?P<pk>\d+)/$', views.SecretView.as_view(), name='secret'),
     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 django.views.generic import View
 
 
 from dcim.models import Device
 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 . import filters, forms, tables
 from .decorators import userkey_required
 from .decorators import userkey_required
 from .models import SecretRole, Secret, SessionKey
 from .models import SecretRole, Secret, SessionKey
@@ -185,58 +187,50 @@ class SecretDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     default_return_url = 'secrets:secret_list'
     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:
             try:
                 sk = SessionKey.objects.get(userkey__user=request.user)
                 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:
             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:
             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):
 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 dcim.models import Site, Rack, Device
 from ipam.models import IPAddress, Prefix, VLAN, VRF
 from ipam.models import IPAddress, Prefix, VLAN, VRF
 from utilities.views import (
 from utilities.views import (
-    BulkDeleteView, BulkEditView, BulkImportView, BulkImportView2, ObjectDeleteView, ObjectEditView, ObjectListView,
+    BulkDeleteView, BulkEditView, BulkImportView2, ObjectDeleteView, ObjectEditView, ObjectListView,
 )
 )
 from .models import Tenant, TenantGroup
 from .models import Tenant, TenantGroup
 from . import filters, forms, tables
 from . import filters, forms, tables

+ 7 - 4
netbox/utilities/views.py

@@ -448,6 +448,12 @@ class BulkImportView2(View):
 
 
         return ImportForm(*args, **kwargs)
         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):
     def get(self, request):
 
 
         return render(request, self.template_name, {
         return render(request, self.template_name, {
@@ -471,7 +477,7 @@ class BulkImportView2(View):
                     for row, data in enumerate(form.cleaned_data['csv'], start=1):
                     for row, data in enumerate(form.cleaned_data['csv'], start=1):
                         obj_form = self.model_form(data)
                         obj_form = self.model_form(data)
                         if obj_form.is_valid():
                         if obj_form.is_valid():
-                            obj = obj_form.save()
+                            obj = self._save_obj(obj_form)
                             new_objs.append(obj)
                             new_objs.append(obj)
                         else:
                         else:
                             for field, err in obj_form.errors.items():
                             for field, err in obj_form.errors.items():
@@ -501,9 +507,6 @@ class BulkImportView2(View):
             'return_url': self.default_return_url,
             'return_url': self.default_return_url,
         })
         })
 
 
-    def save_obj(self, obj):
-        obj.save()
-
 
 
 class BulkEditView(View):
 class BulkEditView(View):
     """
     """