Closes #1739: Enabled custom fields for secrets

This commit is contained in:
Jeremy Stretch 2018-07-17 09:43:57 -04:00
parent 9e2ac7b3f4
commit 0c0799f3bf
7 changed files with 32 additions and 10 deletions

View File

@ -6,6 +6,7 @@ CUSTOMFIELD_MODELS = (
'provider', 'circuit', # Circuits 'provider', 'circuit', # Circuits
'site', 'rack', 'devicetype', 'device', # DCIM 'site', 'rack', 'devicetype', 'device', # DCIM
'aggregate', 'prefix', 'ipaddress', 'vlan', 'vrf', 'service', # IPAM 'aggregate', 'prefix', 'ipaddress', 'vlan', 'vrf', 'service', # IPAM
'secret', # Secrets
'tenant', # Tenancy 'tenant', # Tenancy
'cluster', 'virtualmachine', # Virtualization 'cluster', 'virtualmachine', # Virtualization
) )

View File

@ -5,6 +5,7 @@ from rest_framework.validators import UniqueTogetherValidator
from taggit.models import Tag from taggit.models import Tag
from dcim.api.serializers import NestedDeviceSerializer from dcim.api.serializers import NestedDeviceSerializer
from extras.api.customfields import CustomFieldModelSerializer
from secrets.models import Secret, SecretRole from secrets.models import Secret, SecretRole
from utilities.api import TagField, ValidatedModelSerializer, WritableNestedSerializer from utilities.api import TagField, ValidatedModelSerializer, WritableNestedSerializer
@ -32,7 +33,7 @@ class NestedSecretRoleSerializer(WritableNestedSerializer):
# Secrets # Secrets
# #
class SecretSerializer(ValidatedModelSerializer): class SecretSerializer(CustomFieldModelSerializer):
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
role = NestedSecretRoleSerializer() role = NestedSecretRoleSerializer()
plaintext = serializers.CharField() plaintext = serializers.CharField()
@ -40,7 +41,9 @@ class SecretSerializer(ValidatedModelSerializer):
class Meta: class Meta:
model = Secret model = Secret
fields = ['id', 'device', 'role', 'name', 'plaintext', 'hash', 'tags', 'created', 'last_updated'] fields = [
'id', 'device', 'role', 'name', 'plaintext', 'hash', 'tags', 'custom_fields', 'created', 'last_updated',
]
validators = [] validators = []
def validate(self, data): def validate(self, data):

View File

@ -4,6 +4,7 @@ import django_filters
from django.db.models import Q from django.db.models import Q
from dcim.models import Device from dcim.models import Device
from extras.filters import CustomFieldFilterSet
from utilities.filters import NumericInFilter from utilities.filters import NumericInFilter
from .models import Secret, SecretRole from .models import Secret, SecretRole
@ -15,7 +16,7 @@ class SecretRoleFilter(django_filters.FilterSet):
fields = ['name', 'slug'] fields = ['name', 'slug']
class SecretFilter(django_filters.FilterSet): class SecretFilter(CustomFieldFilterSet, django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in') id__in = NumericInFilter(name='id', lookup_expr='in')
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',

View File

@ -7,8 +7,8 @@ from django.db.models import Count
from taggit.forms import TagField from taggit.forms import TagField
from dcim.models import Device from dcim.models import Device
from extras.forms import AddRemoveTagsForm from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldForm
from utilities.forms import BootstrapMixin, BulkEditForm, FilterChoiceField, FlexibleModelChoiceField, SlugField from utilities.forms import BootstrapMixin, FilterChoiceField, FlexibleModelChoiceField, SlugField
from .models import Secret, SecretRole, UserKey from .models import Secret, SecretRole, UserKey
@ -59,7 +59,7 @@ class SecretRoleCSVForm(forms.ModelForm):
# Secrets # Secrets
# #
class SecretForm(BootstrapMixin, forms.ModelForm): class SecretForm(BootstrapMixin, CustomFieldForm):
plaintext = forms.CharField( plaintext = forms.CharField(
max_length=65535, max_length=65535,
required=False, required=False,
@ -129,7 +129,7 @@ class SecretCSVForm(forms.ModelForm):
return s return s
class SecretBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm): class SecretBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
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)
name = forms.CharField(max_length=100, required=False) name = forms.CharField(max_length=100, required=False)
@ -138,7 +138,8 @@ class SecretBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
nullable_fields = ['name'] nullable_fields = ['name']
class SecretFilterForm(BootstrapMixin, forms.Form): class SecretFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Secret
q = forms.CharField(required=False, label='Search') q = forms.CharField(required=False, label='Search')
role = FilterChoiceField( role = FilterChoiceField(
queryset=SecretRole.objects.annotate(filter_count=Count('secrets')), queryset=SecretRole.objects.annotate(filter_count=Count('secrets')),

View File

@ -8,12 +8,14 @@ from Crypto.Util import strxor
from django.conf import settings from django.conf import settings
from django.contrib.auth.hashers import make_password, check_password from django.contrib.auth.hashers import make_password, check_password
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_bytes, python_2_unicode_compatible from django.utils.encoding import force_bytes, python_2_unicode_compatible
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from extras.models import CustomFieldModel
from utilities.models import ChangeLoggedModel from utilities.models import ChangeLoggedModel
from .exceptions import InvalidKey from .exceptions import InvalidKey
from .hashers import SecretValidationHasher from .hashers import SecretValidationHasher
@ -311,7 +313,7 @@ class SecretRole(ChangeLoggedModel):
@python_2_unicode_compatible @python_2_unicode_compatible
class Secret(ChangeLoggedModel): class Secret(ChangeLoggedModel, CustomFieldModel):
""" """
A Secret stores an AES256-encrypted copy of sensitive data, such as passwords or secret keys. An irreversible A Secret stores an AES256-encrypted copy of sensitive data, such as passwords or secret keys. An irreversible
SHA-256 hash is stored along with the ciphertext for validation upon decryption. Each Secret is assigned to a SHA-256 hash is stored along with the ciphertext for validation upon decryption. Each Secret is assigned to a
@ -343,6 +345,11 @@ class Secret(ChangeLoggedModel):
max_length=128, max_length=128,
editable=False editable=False
) )
custom_field_values = GenericRelation(
to='extras.CustomFieldValue',
content_type_field='obj_type',
object_id_field='obj_id'
)
tags = TaggableManager() tags = TaggableManager()

View File

@ -69,7 +69,7 @@
</tr> </tr>
</table> </table>
</div> </div>
{% include 'extras/inc/tags_panel.html' with tags=secret.tags.all url='secrets:secret_list' %} {% include 'inc/custom_fields_panel.html' with custom_fields=secret.get_custom_fields %}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
{% if secret|decryptable_by:request.user %} {% if secret|decryptable_by:request.user %}
@ -101,6 +101,7 @@
You do not have permission to decrypt this secret. You do not have permission to decrypt this secret.
</div> </div>
{% endif %} {% endif %}
{% include 'extras/inc/tags_panel.html' with tags=secret.tags.all url='secrets:secret_list' %}
</div> </div>
</div> </div>

View File

@ -54,6 +54,14 @@
{% render_field form.plaintext2 %} {% render_field form.plaintext2 %}
</div> </div>
</div> </div>
{% if form.custom_fields %}
<div class="panel panel-default">
<div class="panel-heading"><strong>Custom Fields</strong></div>
<div class="panel-body">
{% render_custom_fields form %}
</div>
</div>
{% endif %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div> <div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body"> <div class="panel-body">