Przeglądaj źródła

Closes #1739: Enabled custom fields for secrets

Jeremy Stretch 7 lat temu
rodzic
commit
0c0799f3bf

+ 1 - 0
netbox/extras/constants.py

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

+ 5 - 2
netbox/secrets/api/serializers.py

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

+ 2 - 1
netbox/secrets/filters.py

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

+ 6 - 5
netbox/secrets/forms.py

@@ -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 utilities.forms import BootstrapMixin, BulkEditForm, FilterChoiceField, FlexibleModelChoiceField, SlugField
+from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldForm
+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')),

+ 8 - 1
netbox/secrets/models.py

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

+ 2 - 1
netbox/templates/secrets/secret.html

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

+ 8 - 0
netbox/templates/secrets/secret_edit.html

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