1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Converted secrets import view to new scheme

This commit is contained in:
Jeremy Stretch
2017-06-02 17:23:41 -04:00
parent c82658440f
commit af604aba31
6 changed files with 67 additions and 61 deletions

View File

@ -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, BulkAddView, BulkDeleteView, BulkEditView, BulkImportView2, ObjectDeleteView, ObjectEditView, ObjectListView,
ObjectListView,
) )
from . import filters, forms, tables from . import filters, forms, tables
from .models import ( from .models import (

View File

@ -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): class SecretCSVForm(forms.ModelForm):
device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, to_field_name='name', device = forms.ModelChoiceField(
error_messages={'invalid_choice': 'Device not found.'}) queryset=Device.objects.all(),
role = forms.ModelChoiceField(queryset=SecretRole.objects.all(), to_field_name='name', to_field_name='name',
error_messages={'invalid_choice': 'Invalid secret role.'}) help_text='Device name',
plaintext = forms.CharField() 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)

View File

@ -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'),

View File

@ -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') class SecretBulkImportView(BulkImportView2):
@userkey_required() permission_required = 'ipam.add_vlan'
def secret_import(request): model_form = forms.SecretCSVForm
table = tables.SecretTable
default_return_url = 'secrets:secret_list'
session_key = request.COOKIES.get('session_key', None) master_key = None
if request.method == 'POST': def _save_obj(self, obj_form):
form = forms.SecretImportForm(request.POST) """
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 session_key is None: def post(self, request):
form.add_error(None, "No session key was provided with the request. Unable to encrypt secret data.")
if form.is_valid(): # Grab the session key from cookies.
session_key = request.COOKIES.get('session_key')
if session_key:
new_secrets = [] # Attempt to derive the master key using the provided session key.
session_key = base64.b64decode(session_key)
master_key = None
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: if self.master_key is not None:
form.add_error(None, "Invalid private key! Unable to encrypt secret data.") return super(SecretBulkImportView, self).post(request)
else: else:
try: messages.error(request, "Invalid private key! Unable to encrypt secret data.")
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) else:
messages.success(request, "Imported {} new secrets.".format(len(new_secrets))) messages.error(request, "No session key was provided with the request. Unable to encrypt secret data.")
return render(request, 'import_success.html', { return render(request, self.template_name, {
'table': table, 'form': self._import_form(request.POST),
'return_url': 'secrets:secret_list', 'fields': self.model_form().fields,
}) 'obj_type': self.model_form._meta.model._meta.verbose_name,
'return_url': self.default_return_url,
except IntegrityError as e: })
form.add_error('csv', "Record {}: {}".format(len(new_secrets) + 1, e.__cause__))
else:
form = forms.SecretImportForm()
return render(request, 'secrets/secret_import.html', {
'form': form,
'return_url': 'secrets:secret_list',
})
class SecretBulkEditView(PermissionRequiredMixin, BulkEditView): class SecretBulkEditView(PermissionRequiredMixin, BulkEditView):

View File

@ -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

View File

@ -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):
""" """