mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 17:08:41 -06:00
Closes #1739: Enabled custom fields for secrets
This commit is contained in:
parent
9e2ac7b3f4
commit
0c0799f3bf
@ -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,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):
|
||||||
|
@ -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',
|
||||||
|
@ -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')),
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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">
|
||||||
|
Loading…
Reference in New Issue
Block a user