nautobot 2.4.9__py3-none-any.whl → 2.4.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/core/api/parsers.py +56 -2
- nautobot/core/models/__init__.py +2 -0
- nautobot/core/tests/test_csv.py +92 -1
- nautobot/core/tests/test_jinja_filters.py +59 -0
- nautobot/core/tests/test_views.py +73 -0
- nautobot/core/urls.py +2 -2
- nautobot/core/views/__init__.py +21 -0
- nautobot/dcim/models/device_component_templates.py +4 -0
- nautobot/dcim/models/device_components.py +12 -0
- nautobot/dcim/models/devices.py +6 -0
- nautobot/extras/context_managers.py +2 -2
- nautobot/extras/models/customfields.py +2 -0
- nautobot/extras/models/datasources.py +8 -0
- nautobot/extras/models/groups.py +18 -0
- nautobot/extras/models/jobs.py +14 -0
- nautobot/extras/models/metadata.py +2 -0
- nautobot/extras/models/models.py +4 -0
- nautobot/extras/models/secrets.py +7 -0
- nautobot/extras/secrets/__init__.py +14 -0
- nautobot/extras/tests/test_context_managers.py +20 -0
- nautobot/extras/tests/test_models.py +26 -0
- nautobot/ipam/models.py +32 -0
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +39 -38
- nautobot/project-static/docs/release-notes/version-1.6.html +297 -0
- nautobot/project-static/docs/release-notes/version-2.4.html +101 -0
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +298 -298
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/security/index.html +0 -1
- nautobot/project-static/docs/user-guide/administration/security/notices.html +113 -1
- nautobot/users/models.py +4 -0
- nautobot/virtualization/models.py +4 -0
- {nautobot-2.4.9.dist-info → nautobot-2.4.10.dist-info}/METADATA +2 -2
- {nautobot-2.4.9.dist-info → nautobot-2.4.10.dist-info}/RECORD +38 -38
- {nautobot-2.4.9.dist-info → nautobot-2.4.10.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.9.dist-info → nautobot-2.4.10.dist-info}/NOTICE +0 -0
- {nautobot-2.4.9.dist-info → nautobot-2.4.10.dist-info}/WHEEL +0 -0
- {nautobot-2.4.9.dist-info → nautobot-2.4.10.dist-info}/entry_points.txt +0 -0
nautobot/extras/models/jobs.py
CHANGED
|
@@ -753,6 +753,8 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
753
753
|
duration.total_seconds()
|
|
754
754
|
)
|
|
755
755
|
|
|
756
|
+
set_status.alters_data = True
|
|
757
|
+
|
|
756
758
|
@classmethod
|
|
757
759
|
def execute_job(cls, *args, **kwargs):
|
|
758
760
|
"""
|
|
@@ -768,6 +770,8 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
768
770
|
"""
|
|
769
771
|
return cls.enqueue_job(*args, **kwargs, synchronous=True)
|
|
770
772
|
|
|
773
|
+
execute_job.__func__.alters_data = True
|
|
774
|
+
|
|
771
775
|
@classmethod
|
|
772
776
|
def enqueue_job(
|
|
773
777
|
cls,
|
|
@@ -938,6 +942,8 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
938
942
|
|
|
939
943
|
return job_result
|
|
940
944
|
|
|
945
|
+
enqueue_job.__func__.alters_data = True
|
|
946
|
+
|
|
941
947
|
def log(
|
|
942
948
|
self,
|
|
943
949
|
message,
|
|
@@ -989,6 +995,8 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
989
995
|
else:
|
|
990
996
|
log.save(using=JOB_LOGS)
|
|
991
997
|
|
|
998
|
+
log.alters_data = True
|
|
999
|
+
|
|
992
1000
|
|
|
993
1001
|
#
|
|
994
1002
|
# Job Button
|
|
@@ -1076,6 +1084,8 @@ class ScheduledJobs(models.Model):
|
|
|
1076
1084
|
if not instance.no_changes:
|
|
1077
1085
|
cls.update_changed()
|
|
1078
1086
|
|
|
1087
|
+
changed.__func__.alters_data = True
|
|
1088
|
+
|
|
1079
1089
|
@classmethod
|
|
1080
1090
|
def update_changed(cls, raw=False, **kwargs):
|
|
1081
1091
|
"""This function acts as a signal handler to track changes to the scheduled job that is triggered after a change"""
|
|
@@ -1083,6 +1093,8 @@ class ScheduledJobs(models.Model):
|
|
|
1083
1093
|
return
|
|
1084
1094
|
cls.objects.update_or_create(ident=1, defaults={"last_update": timezone.now()})
|
|
1085
1095
|
|
|
1096
|
+
update_changed.__func__.alters_data = True
|
|
1097
|
+
|
|
1086
1098
|
@classmethod
|
|
1087
1099
|
def last_change(cls):
|
|
1088
1100
|
"""This function acts as a getter for the last update on scheduled jobs"""
|
|
@@ -1397,6 +1409,8 @@ class ScheduledJob(BaseModel):
|
|
|
1397
1409
|
scheduled_job.validated_save()
|
|
1398
1410
|
return scheduled_job
|
|
1399
1411
|
|
|
1412
|
+
create_schedule.__func__.alters_data = True
|
|
1413
|
+
|
|
1400
1414
|
def to_cron(self):
|
|
1401
1415
|
tz = self.time_zone
|
|
1402
1416
|
t = self.start_time.astimezone(tz) # pylint: disable=no-member
|
|
@@ -251,6 +251,8 @@ class ObjectMetadata(ChangeLoggedModel, BaseModel):
|
|
|
251
251
|
self.clean()
|
|
252
252
|
return super().validated_save(*args, **kwargs)
|
|
253
253
|
|
|
254
|
+
validated_save.alters_data = True
|
|
255
|
+
|
|
254
256
|
def clean(self):
|
|
255
257
|
"""
|
|
256
258
|
Validate a value according to the field's type validation rules.
|
nautobot/extras/models/models.py
CHANGED
|
@@ -457,6 +457,8 @@ class ExportTemplate(
|
|
|
457
457
|
if self.file_extension.startswith("."):
|
|
458
458
|
self.file_extension = self.file_extension[1:]
|
|
459
459
|
|
|
460
|
+
clean.alters_data = True
|
|
461
|
+
|
|
460
462
|
|
|
461
463
|
#
|
|
462
464
|
# External integrations
|
|
@@ -919,6 +921,8 @@ class UserSavedViewAssociation(BaseModel):
|
|
|
919
921
|
super().clean()
|
|
920
922
|
self.view_name = self.saved_view.view
|
|
921
923
|
|
|
924
|
+
clean.alters_data = True
|
|
925
|
+
|
|
922
926
|
|
|
923
927
|
#
|
|
924
928
|
# Webhooks
|
|
@@ -4,6 +4,7 @@ from django.core.exceptions import ValidationError
|
|
|
4
4
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
5
5
|
from django.db import models
|
|
6
6
|
from jinja2.exceptions import TemplateSyntaxError, UndefinedError
|
|
7
|
+
from jinja2.sandbox import unsafe
|
|
7
8
|
|
|
8
9
|
from nautobot.core.constants import CHARFIELD_MAX_LENGTH
|
|
9
10
|
from nautobot.core.models import BaseModel
|
|
@@ -58,6 +59,7 @@ class Secret(PrimaryModel):
|
|
|
58
59
|
except (TemplateSyntaxError, UndefinedError) as exc:
|
|
59
60
|
raise SecretParametersError(self, registry["secrets_providers"].get(self.provider), str(exc)) from exc
|
|
60
61
|
|
|
62
|
+
@unsafe
|
|
61
63
|
def get_value(self, obj=None):
|
|
62
64
|
"""Retrieve the secret value that this Secret is a representation of.
|
|
63
65
|
|
|
@@ -77,6 +79,8 @@ class Secret(PrimaryModel):
|
|
|
77
79
|
except Exception as exc:
|
|
78
80
|
raise SecretError(self, provider, str(exc)) from exc
|
|
79
81
|
|
|
82
|
+
get_value.do_not_call_in_templates = True
|
|
83
|
+
|
|
80
84
|
def clean(self):
|
|
81
85
|
provider = registry["secrets_providers"].get(self.provider)
|
|
82
86
|
if not provider:
|
|
@@ -108,6 +112,7 @@ class SecretsGroup(OrganizationalModel):
|
|
|
108
112
|
def __str__(self):
|
|
109
113
|
return self.name
|
|
110
114
|
|
|
115
|
+
@unsafe
|
|
111
116
|
def get_secret_value(self, access_type, secret_type, obj=None, **kwargs):
|
|
112
117
|
"""Helper method to retrieve a specific secret from this group.
|
|
113
118
|
|
|
@@ -118,6 +123,8 @@ class SecretsGroup(OrganizationalModel):
|
|
|
118
123
|
).secret
|
|
119
124
|
return secret.get_value(obj=obj, **kwargs)
|
|
120
125
|
|
|
126
|
+
get_secret_value.do_not_call_in_templates = True
|
|
127
|
+
|
|
121
128
|
|
|
122
129
|
@extras_features(
|
|
123
130
|
"graphql",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
|
|
3
|
+
from jinja2.sandbox import unsafe
|
|
4
|
+
|
|
3
5
|
from nautobot.extras.registry import registry
|
|
4
6
|
|
|
5
7
|
from .exceptions import SecretError, SecretParametersError, SecretProviderError, SecretValueNotFoundError
|
|
@@ -31,6 +33,7 @@ class SecretsProvider(ABC):
|
|
|
31
33
|
|
|
32
34
|
@classmethod
|
|
33
35
|
@abstractmethod
|
|
36
|
+
@unsafe
|
|
34
37
|
def get_value_for_secret(cls, secret, obj=None, **kwargs):
|
|
35
38
|
"""Retrieve the stored value described by the given Secret record.
|
|
36
39
|
|
|
@@ -41,6 +44,17 @@ class SecretsProvider(ABC):
|
|
|
41
44
|
obj (object): Django model instance or similar providing additional context for retrieving the secret.
|
|
42
45
|
"""
|
|
43
46
|
|
|
47
|
+
get_value_for_secret.__func__.do_not_call_in_templates = True
|
|
48
|
+
|
|
49
|
+
def __init_subclass__(cls, **kwargs):
|
|
50
|
+
# Automatically apply protection against Django and Jinja2 template execution to child classes.
|
|
51
|
+
if not getattr(cls.get_value_for_secret, "do_not_call_in_templates", False): # Django
|
|
52
|
+
cls.get_value_for_secret.__func__.do_not_call_in_templates = True
|
|
53
|
+
if not getattr(cls.get_value_for_secret, "unsafe_callable", False): # Jinja @unsafe decorator
|
|
54
|
+
cls.get_value_for_secret.__func__.unsafe_callable = True
|
|
55
|
+
|
|
56
|
+
super().__init_subclass__(**kwargs)
|
|
57
|
+
|
|
44
58
|
|
|
45
59
|
def register_secrets_provider(provider):
|
|
46
60
|
"""
|
|
@@ -2,6 +2,7 @@ from unittest import mock
|
|
|
2
2
|
|
|
3
3
|
from django.contrib.auth import get_user_model
|
|
4
4
|
from django.contrib.contenttypes.models import ContentType
|
|
5
|
+
from django.core.exceptions import ValidationError
|
|
5
6
|
from django.test import TestCase
|
|
6
7
|
|
|
7
8
|
from nautobot.core.celery import app
|
|
@@ -220,6 +221,25 @@ class WebRequestContextTestCase(TestCase):
|
|
|
220
221
|
self.assertEqual(call_args[6], oc_list[0].request_id)
|
|
221
222
|
self.assertEqual(call_args[7], oc_list[0].get_snapshots())
|
|
222
223
|
|
|
224
|
+
def test_web_request_context_raises_exception_correctly(self):
|
|
225
|
+
"""
|
|
226
|
+
Test implemented to ensure the fix for https://github.com/nautobot/nautobot/issues/7358 is working as intended.
|
|
227
|
+
The operation should raise and allow an exception to be passed through instead of raising an
|
|
228
|
+
AttributeError: 'NoneType' object has no attribute 'get'"
|
|
229
|
+
"""
|
|
230
|
+
valid_location_type = LocationType.objects.get(name="Campus")
|
|
231
|
+
location_status = Status.objects.get_for_model(Location).first()
|
|
232
|
+
invalid_location_type = LocationType(name="rackgroup")
|
|
233
|
+
with self.assertRaises(ValidationError):
|
|
234
|
+
with web_request_context(self.user, context_detail="test_web_request_context_raises_exception_correctly"):
|
|
235
|
+
# These operations should generate some ObjectChange records to test the code path that was causing the reported issue.
|
|
236
|
+
location = Location(name="Test Location 1", location_type=valid_location_type, status=location_status)
|
|
237
|
+
location.save()
|
|
238
|
+
location.description = "changed"
|
|
239
|
+
location.save()
|
|
240
|
+
# Location type name is not allowed to be "rackgroup" (reserved name), so this should raise an exception.
|
|
241
|
+
invalid_location_type.validated_save()
|
|
242
|
+
|
|
223
243
|
|
|
224
244
|
class WebRequestContextTransactionTestCase(TransactionTestCase):
|
|
225
245
|
def test_change_log_thread_safe(self):
|
|
@@ -125,6 +125,13 @@ class ComputedFieldTest(ModelTestCases.BaseModelTestCase):
|
|
|
125
125
|
fallback_value="An error occurred while rendering this template.",
|
|
126
126
|
weight=50,
|
|
127
127
|
)
|
|
128
|
+
self.evil_computed_field = ComputedField.objects.create(
|
|
129
|
+
content_type=ContentType.objects.get_for_model(Secret),
|
|
130
|
+
key="evil_computed_field",
|
|
131
|
+
label="Evil Computed Field",
|
|
132
|
+
template="{{ obj.get_value() }}",
|
|
133
|
+
weight=666,
|
|
134
|
+
)
|
|
128
135
|
self.blank_fallback_value = ComputedField.objects.create(
|
|
129
136
|
content_type=ContentType.objects.get_for_model(Location),
|
|
130
137
|
key="blank_fallback_value",
|
|
@@ -133,6 +140,18 @@ class ComputedFieldTest(ModelTestCases.BaseModelTestCase):
|
|
|
133
140
|
weight=50,
|
|
134
141
|
)
|
|
135
142
|
self.location1 = Location.objects.filter(location_type=LocationType.objects.get(name="Campus")).first()
|
|
143
|
+
self.secret = Secret.objects.create(
|
|
144
|
+
name="Environment Variable Secret",
|
|
145
|
+
provider="environment-variable",
|
|
146
|
+
parameters={"variable": "NAUTOBOT_ROOT"},
|
|
147
|
+
)
|
|
148
|
+
self.secrets_group = SecretsGroup.objects.create(name="Group of Secrets")
|
|
149
|
+
SecretsGroupAssociation.objects.create(
|
|
150
|
+
secrets_group=self.secrets_group,
|
|
151
|
+
secret=self.secret,
|
|
152
|
+
access_type=SecretsGroupAccessTypeChoices.TYPE_GENERIC,
|
|
153
|
+
secret_type=SecretsGroupSecretTypeChoices.TYPE_SECRET,
|
|
154
|
+
)
|
|
136
155
|
|
|
137
156
|
def test_render_method(self):
|
|
138
157
|
rendered_value = self.good_computed_field.render(context={"obj": self.location1})
|
|
@@ -146,6 +165,13 @@ class ComputedFieldTest(ModelTestCases.BaseModelTestCase):
|
|
|
146
165
|
rendered_value = self.bad_computed_field.render(context={"obj": self.location1})
|
|
147
166
|
self.assertEqual(rendered_value, self.bad_computed_field.fallback_value)
|
|
148
167
|
|
|
168
|
+
def test_render_method_evil_template(self):
|
|
169
|
+
rendered_value = self.evil_computed_field.render(context={"obj": self.secret})
|
|
170
|
+
self.assertEqual(rendered_value, "")
|
|
171
|
+
self.evil_computed_field.template = "{{ obj.secrets_groups.first().get_secret_value('Generic', 'secret') }}"
|
|
172
|
+
rendered_value = self.evil_computed_field.render(context={"obj": self.secret})
|
|
173
|
+
self.assertEqual(rendered_value, "")
|
|
174
|
+
|
|
149
175
|
def test_check_if_key_is_graphql_safe(self):
|
|
150
176
|
"""
|
|
151
177
|
Check the GraphQL validation method on CustomField Key Attribute.
|
nautobot/ipam/models.py
CHANGED
|
@@ -200,6 +200,8 @@ class VRF(PrimaryModel):
|
|
|
200
200
|
instance.validated_save()
|
|
201
201
|
return instance
|
|
202
202
|
|
|
203
|
+
add_device.alters_data = True
|
|
204
|
+
|
|
203
205
|
def remove_device(self, device):
|
|
204
206
|
"""
|
|
205
207
|
Remove a `device` from this VRF.
|
|
@@ -213,6 +215,8 @@ class VRF(PrimaryModel):
|
|
|
213
215
|
instance = self.devices.through.objects.get(vrf=self, device=device)
|
|
214
216
|
return instance.delete()
|
|
215
217
|
|
|
218
|
+
remove_device.alters_data = True
|
|
219
|
+
|
|
216
220
|
def add_virtual_machine(self, virtual_machine, rd="", name=""):
|
|
217
221
|
"""
|
|
218
222
|
Add a `virtual_machine` to this VRF, optionally overloading `rd` and `name`.
|
|
@@ -231,6 +235,8 @@ class VRF(PrimaryModel):
|
|
|
231
235
|
instance.validated_save()
|
|
232
236
|
return instance
|
|
233
237
|
|
|
238
|
+
add_virtual_machine.alters_data = True
|
|
239
|
+
|
|
234
240
|
def remove_virtual_machine(self, virtual_machine):
|
|
235
241
|
"""
|
|
236
242
|
Remove a `virtual_machine` from this VRF.
|
|
@@ -244,6 +250,8 @@ class VRF(PrimaryModel):
|
|
|
244
250
|
instance = self.virtual_machines.through.objects.get(vrf=self, virtual_machine=virtual_machine)
|
|
245
251
|
return instance.delete()
|
|
246
252
|
|
|
253
|
+
remove_virtual_machine.alters_data = True
|
|
254
|
+
|
|
247
255
|
def add_virtual_device_context(self, virtual_device_context, rd="", name=""):
|
|
248
256
|
"""
|
|
249
257
|
Add a `virtual_device_context` to this VRF, optionally overloading `rd` and `name`.
|
|
@@ -264,6 +272,8 @@ class VRF(PrimaryModel):
|
|
|
264
272
|
instance.validated_save()
|
|
265
273
|
return instance
|
|
266
274
|
|
|
275
|
+
add_virtual_device_context.alters_data = True
|
|
276
|
+
|
|
267
277
|
def remove_virtual_device_context(self, virtual_device_context):
|
|
268
278
|
"""
|
|
269
279
|
Remove a `virtual_device_context` from this VRF.
|
|
@@ -279,6 +289,8 @@ class VRF(PrimaryModel):
|
|
|
279
289
|
)
|
|
280
290
|
return instance.delete()
|
|
281
291
|
|
|
292
|
+
remove_virtual_device_context.alters_data = True
|
|
293
|
+
|
|
282
294
|
def add_prefix(self, prefix):
|
|
283
295
|
"""
|
|
284
296
|
Add a `prefix` to this VRF. Each object must be in the same Namespace.
|
|
@@ -293,6 +305,8 @@ class VRF(PrimaryModel):
|
|
|
293
305
|
instance.validated_save()
|
|
294
306
|
return instance
|
|
295
307
|
|
|
308
|
+
add_prefix.alters_data = True
|
|
309
|
+
|
|
296
310
|
def remove_prefix(self, prefix):
|
|
297
311
|
"""
|
|
298
312
|
Remove a `prefix` from this VRF.
|
|
@@ -306,6 +320,8 @@ class VRF(PrimaryModel):
|
|
|
306
320
|
instance = self.prefixes.through.objects.get(vrf=self, prefix=prefix)
|
|
307
321
|
return instance.delete()
|
|
308
322
|
|
|
323
|
+
remove_prefix.alters_data = True
|
|
324
|
+
|
|
309
325
|
|
|
310
326
|
@extras_features("graphql")
|
|
311
327
|
class VRFDeviceAssignment(BaseModel):
|
|
@@ -374,6 +390,8 @@ class VRFDeviceAssignment(BaseModel):
|
|
|
374
390
|
"A VRFDeviceAssignment entry must be associated with a device, a virtual machine, or a virtual device context."
|
|
375
391
|
)
|
|
376
392
|
|
|
393
|
+
clean.alters_data = True
|
|
394
|
+
|
|
377
395
|
|
|
378
396
|
@extras_features("graphql")
|
|
379
397
|
class VRFPrefixAssignment(BaseModel):
|
|
@@ -640,6 +658,8 @@ class Prefix(PrimaryModel):
|
|
|
640
658
|
self.prefix_length = prefix.prefixlen
|
|
641
659
|
self.ip_version = prefix.version
|
|
642
660
|
|
|
661
|
+
_deconstruct_prefix.alters_data = True
|
|
662
|
+
|
|
643
663
|
def delete(self, *args, **kwargs):
|
|
644
664
|
"""
|
|
645
665
|
A Prefix with children will be impossible to delete and raise a `ProtectedError`.
|
|
@@ -705,6 +725,8 @@ class Prefix(PrimaryModel):
|
|
|
705
725
|
return parent
|
|
706
726
|
return None
|
|
707
727
|
|
|
728
|
+
get_parent.alters_data = True
|
|
729
|
+
|
|
708
730
|
def clean(self):
|
|
709
731
|
if self.prefix is not None: # missing network/prefix_length will be caught by super().clean()
|
|
710
732
|
# Clear host bits from prefix
|
|
@@ -716,6 +738,8 @@ class Prefix(PrimaryModel):
|
|
|
716
738
|
|
|
717
739
|
super().clean()
|
|
718
740
|
|
|
741
|
+
clean.alters_data = True
|
|
742
|
+
|
|
719
743
|
def save(self, *args, **kwargs):
|
|
720
744
|
self.clean()
|
|
721
745
|
|
|
@@ -821,6 +845,8 @@ class Prefix(PrimaryModel):
|
|
|
821
845
|
|
|
822
846
|
return query.update(parent=self)
|
|
823
847
|
|
|
848
|
+
reparent_subnets.alters_data = True
|
|
849
|
+
|
|
824
850
|
def reparent_ips(self):
|
|
825
851
|
"""Determine the list of child IPAddresses and set the parent to self."""
|
|
826
852
|
query = IPAddress.objects.select_for_update().filter(
|
|
@@ -832,6 +858,8 @@ class Prefix(PrimaryModel):
|
|
|
832
858
|
|
|
833
859
|
return query.update(parent=self)
|
|
834
860
|
|
|
861
|
+
reparent_ips.alters_data = True
|
|
862
|
+
|
|
835
863
|
def supernets(self, direct=False, include_self=False, for_update=False):
|
|
836
864
|
"""
|
|
837
865
|
Return supernets of this Prefix.
|
|
@@ -1228,6 +1256,8 @@ class IPAddress(PrimaryModel):
|
|
|
1228
1256
|
self.mask_length = address.prefixlen
|
|
1229
1257
|
self.ip_version = address.version
|
|
1230
1258
|
|
|
1259
|
+
_deconstruct_address.alters_data = True
|
|
1260
|
+
|
|
1231
1261
|
natural_key_field_names = ["parent__namespace", "host"]
|
|
1232
1262
|
|
|
1233
1263
|
def _get_closest_parent(self):
|
|
@@ -1289,6 +1319,8 @@ class IPAddress(PrimaryModel):
|
|
|
1289
1319
|
|
|
1290
1320
|
super().clean()
|
|
1291
1321
|
|
|
1322
|
+
clean.alters_data = True
|
|
1323
|
+
|
|
1292
1324
|
def save(self, *args, **kwargs):
|
|
1293
1325
|
self.clean() # MUST do data fixup as above
|
|
1294
1326
|
|
|
@@ -10092,46 +10092,47 @@
|
|
|
10092
10092
|
</div>
|
|
10093
10093
|
<div class="highlight"><pre><span></span><code><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="c1"># secrets.py</span>
|
|
10094
10094
|
<a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a><span class="kn">from</span> <span class="nn">django</span> <span class="kn">import</span> <span class="n">forms</span>
|
|
10095
|
-
<a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a
|
|
10096
|
-
<a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a><span class="kn">from</span> <span class="nn">nautobot.
|
|
10097
|
-
<a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a>
|
|
10095
|
+
<a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a>
|
|
10096
|
+
<a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a><span class="kn">from</span> <span class="nn">nautobot.apps.secrets</span> <span class="kn">import</span> <span class="n">SecretsProvider</span>
|
|
10097
|
+
<a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a><span class="kn">from</span> <span class="nn">nautobot.utilities.forms</span> <span class="kn">import</span> <span class="n">BootstrapMixin</span>
|
|
10098
10098
|
<a id="__codelineno-0-6" name="__codelineno-0-6" href="#__codelineno-0-6"></a>
|
|
10099
|
-
<a id="__codelineno-0-7" name="__codelineno-0-7" href="#__codelineno-0-7"></a
|
|
10100
|
-
<a id="__codelineno-0-8" name="__codelineno-0-8" href="#__codelineno-0-8"></a><span class="
|
|
10101
|
-
<a id="__codelineno-0-9" name="__codelineno-0-9" href="#__codelineno-0-9"></a><span class="
|
|
10102
|
-
<a id="__codelineno-0-10" name="__codelineno-0-10" href="#__codelineno-0-10"></a>
|
|
10103
|
-
<a id="__codelineno-0-11" name="__codelineno-0-11" href="#__codelineno-0-11"></a
|
|
10104
|
-
<a id="__codelineno-0-12" name="__codelineno-0-12" href="#__codelineno-0-12"></a><span class="sd">
|
|
10105
|
-
<a id="__codelineno-0-13" name="__codelineno-0-13" href="#__codelineno-0-13"></a>
|
|
10106
|
-
<a id="__codelineno-0-14" name="__codelineno-0-14" href="#__codelineno-0-14"></a>
|
|
10107
|
-
<a id="__codelineno-0-15" name="__codelineno-0-15" href="#__codelineno-0-15"></a> <span class="n">
|
|
10108
|
-
<a id="__codelineno-0-16" name="__codelineno-0-16" href="#__codelineno-0-16"></a>
|
|
10109
|
-
<a id="__codelineno-0-17" name="__codelineno-0-17" href="#__codelineno-0-17"></a>
|
|
10110
|
-
<a id="__codelineno-0-18" name="__codelineno-0-18" href="#__codelineno-0-18"></a><span class="
|
|
10111
|
-
<a id="__codelineno-0-19" name="__codelineno-0-19" href="#__codelineno-0-19"></a><span class="
|
|
10112
|
-
<a id="__codelineno-0-20" name="__codelineno-0-20" href="#__codelineno-0-20"></a><span class="sd">
|
|
10113
|
-
<a id="__codelineno-0-21" name="__codelineno-0-21" href="#__codelineno-0-21"></a
|
|
10114
|
-
<a id="__codelineno-0-22" name="__codelineno-0-22" href="#__codelineno-0-22"></a>
|
|
10115
|
-
<a id="__codelineno-0-23" name="__codelineno-0-23" href="#__codelineno-0-23"></a> <span class="n">
|
|
10116
|
-
<a id="__codelineno-0-24" name="__codelineno-0-24" href="#__codelineno-0-24"></a>
|
|
10117
|
-
<a id="__codelineno-0-25" name="__codelineno-0-25" href="#__codelineno-0-25"></a>
|
|
10118
|
-
<a id="__codelineno-0-26" name="__codelineno-0-26" href="#__codelineno-0-26"></a>
|
|
10119
|
-
<a id="__codelineno-0-27" name="__codelineno-0-27" href="#__codelineno-0-27"></a> <span class="
|
|
10120
|
-
<a id="__codelineno-0-28" name="__codelineno-0-28" href="#__codelineno-0-28"></a><span class="
|
|
10121
|
-
<a id="__codelineno-0-29" name="__codelineno-0-29" href="#__codelineno-0-29"></a><span class="
|
|
10122
|
-
<a id="__codelineno-0-30" name="__codelineno-0-30" href="#__codelineno-0-30"></a>
|
|
10123
|
-
<a id="__codelineno-0-31" name="__codelineno-0-31" href="#__codelineno-0-31"></a
|
|
10124
|
-
<a id="__codelineno-0-32" name="__codelineno-0-32" href="#__codelineno-0-32"></a><span class="sd">
|
|
10125
|
-
<a id="__codelineno-0-33" name="__codelineno-0-33" href="#__codelineno-0-33"></a>
|
|
10126
|
-
<a id="__codelineno-0-34" name="__codelineno-0-34" href="#__codelineno-0-34"></a
|
|
10127
|
-
<a id="__codelineno-0-35" name="__codelineno-0-35" href="#__codelineno-0-35"></a><span class="sd">
|
|
10128
|
-
<a id="__codelineno-0-36" name="__codelineno-0-36" href="#__codelineno-0-36"></a><span class="sd">
|
|
10129
|
-
<a id="__codelineno-0-37" name="__codelineno-0-37" href="#__codelineno-0-37"></a><span class="sd">
|
|
10130
|
-
<a id="__codelineno-0-38" name="__codelineno-0-38" href="#__codelineno-0-38"></a><span class="sd">
|
|
10131
|
-
<a id="__codelineno-0-39" name="__codelineno-0-39" href="#__codelineno-0-39"></a
|
|
10132
|
-
<a id="__codelineno-0-40" name="__codelineno-0-40" href="#__codelineno-0-40"></a>
|
|
10099
|
+
<a id="__codelineno-0-7" name="__codelineno-0-7" href="#__codelineno-0-7"></a>
|
|
10100
|
+
<a id="__codelineno-0-8" name="__codelineno-0-8" href="#__codelineno-0-8"></a><span class="k">class</span> <span class="nc">ConstantValueSecretsProvider</span><span class="p">(</span><span class="n">SecretsProvider</span><span class="p">):</span>
|
|
10101
|
+
<a id="__codelineno-0-9" name="__codelineno-0-9" href="#__codelineno-0-9"></a><span class="w"> </span><span class="sd">"""</span>
|
|
10102
|
+
<a id="__codelineno-0-10" name="__codelineno-0-10" href="#__codelineno-0-10"></a><span class="sd"> Example SecretsProvider - this one just returns a user-specified constant value.</span>
|
|
10103
|
+
<a id="__codelineno-0-11" name="__codelineno-0-11" href="#__codelineno-0-11"></a>
|
|
10104
|
+
<a id="__codelineno-0-12" name="__codelineno-0-12" href="#__codelineno-0-12"></a><span class="sd"> Obviously this is insecure and not something you'd want to actually use!</span>
|
|
10105
|
+
<a id="__codelineno-0-13" name="__codelineno-0-13" href="#__codelineno-0-13"></a><span class="sd"> """</span>
|
|
10106
|
+
<a id="__codelineno-0-14" name="__codelineno-0-14" href="#__codelineno-0-14"></a>
|
|
10107
|
+
<a id="__codelineno-0-15" name="__codelineno-0-15" href="#__codelineno-0-15"></a> <span class="n">slug</span> <span class="o">=</span> <span class="s2">"constant-value"</span>
|
|
10108
|
+
<a id="__codelineno-0-16" name="__codelineno-0-16" href="#__codelineno-0-16"></a> <span class="n">name</span> <span class="o">=</span> <span class="s2">"Constant Value"</span>
|
|
10109
|
+
<a id="__codelineno-0-17" name="__codelineno-0-17" href="#__codelineno-0-17"></a>
|
|
10110
|
+
<a id="__codelineno-0-18" name="__codelineno-0-18" href="#__codelineno-0-18"></a> <span class="k">class</span> <span class="nc">ParametersForm</span><span class="p">(</span><span class="n">BootstrapMixin</span><span class="p">,</span> <span class="n">forms</span><span class="o">.</span><span class="n">Form</span><span class="p">):</span>
|
|
10111
|
+
<a id="__codelineno-0-19" name="__codelineno-0-19" href="#__codelineno-0-19"></a><span class="w"> </span><span class="sd">"""</span>
|
|
10112
|
+
<a id="__codelineno-0-20" name="__codelineno-0-20" href="#__codelineno-0-20"></a><span class="sd"> User-friendly form for specifying the required parameters of this provider.</span>
|
|
10113
|
+
<a id="__codelineno-0-21" name="__codelineno-0-21" href="#__codelineno-0-21"></a><span class="sd"> """</span>
|
|
10114
|
+
<a id="__codelineno-0-22" name="__codelineno-0-22" href="#__codelineno-0-22"></a> <span class="n">constant</span> <span class="o">=</span> <span class="n">forms</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span>
|
|
10115
|
+
<a id="__codelineno-0-23" name="__codelineno-0-23" href="#__codelineno-0-23"></a> <span class="n">required</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
|
10116
|
+
<a id="__codelineno-0-24" name="__codelineno-0-24" href="#__codelineno-0-24"></a> <span class="n">help_text</span><span class="o">=</span><span class="s2">"Constant secret value. <strong>DO NOT USE FOR REAL DATA</strong>"</span>
|
|
10117
|
+
<a id="__codelineno-0-25" name="__codelineno-0-25" href="#__codelineno-0-25"></a> <span class="p">)</span>
|
|
10118
|
+
<a id="__codelineno-0-26" name="__codelineno-0-26" href="#__codelineno-0-26"></a>
|
|
10119
|
+
<a id="__codelineno-0-27" name="__codelineno-0-27" href="#__codelineno-0-27"></a> <span class="nd">@classmethod</span>
|
|
10120
|
+
<a id="__codelineno-0-28" name="__codelineno-0-28" href="#__codelineno-0-28"></a> <span class="k">def</span> <span class="nf">get_value_for_secret</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">secret</span><span class="p">,</span> <span class="n">obj</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
|
10121
|
+
<a id="__codelineno-0-29" name="__codelineno-0-29" href="#__codelineno-0-29"></a><span class="w"> </span><span class="sd">"""</span>
|
|
10122
|
+
<a id="__codelineno-0-30" name="__codelineno-0-30" href="#__codelineno-0-30"></a><span class="sd"> Return the value defined in the Secret.parameters "constant" key.</span>
|
|
10123
|
+
<a id="__codelineno-0-31" name="__codelineno-0-31" href="#__codelineno-0-31"></a>
|
|
10124
|
+
<a id="__codelineno-0-32" name="__codelineno-0-32" href="#__codelineno-0-32"></a><span class="sd"> A more realistic SecretsProvider would make calls to external APIs, etc.,</span>
|
|
10125
|
+
<a id="__codelineno-0-33" name="__codelineno-0-33" href="#__codelineno-0-33"></a><span class="sd"> to retrieve a secret from another system as desired.</span>
|
|
10126
|
+
<a id="__codelineno-0-34" name="__codelineno-0-34" href="#__codelineno-0-34"></a>
|
|
10127
|
+
<a id="__codelineno-0-35" name="__codelineno-0-35" href="#__codelineno-0-35"></a><span class="sd"> Args:</span>
|
|
10128
|
+
<a id="__codelineno-0-36" name="__codelineno-0-36" href="#__codelineno-0-36"></a><span class="sd"> secret (nautobot.extras.models.Secret): The secret whose value should be retrieved.</span>
|
|
10129
|
+
<a id="__codelineno-0-37" name="__codelineno-0-37" href="#__codelineno-0-37"></a><span class="sd"> obj (object): The object (Django model or similar) providing context for the secret's</span>
|
|
10130
|
+
<a id="__codelineno-0-38" name="__codelineno-0-38" href="#__codelineno-0-38"></a><span class="sd"> parameters.</span>
|
|
10131
|
+
<a id="__codelineno-0-39" name="__codelineno-0-39" href="#__codelineno-0-39"></a><span class="sd"> """</span>
|
|
10132
|
+
<a id="__codelineno-0-40" name="__codelineno-0-40" href="#__codelineno-0-40"></a> <span class="k">return</span> <span class="n">secret</span><span class="o">.</span><span class="n">rendered_parameters</span><span class="p">(</span><span class="n">obj</span><span class="o">=</span><span class="n">obj</span><span class="p">)</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"constant"</span><span class="p">)</span>
|
|
10133
10133
|
<a id="__codelineno-0-41" name="__codelineno-0-41" href="#__codelineno-0-41"></a>
|
|
10134
|
-
<a id="__codelineno-0-42" name="__codelineno-0-42" href="#__codelineno-0-42"></a
|
|
10134
|
+
<a id="__codelineno-0-42" name="__codelineno-0-42" href="#__codelineno-0-42"></a>
|
|
10135
|
+
<a id="__codelineno-0-43" name="__codelineno-0-43" href="#__codelineno-0-43"></a><span class="n">secrets_providers</span> <span class="o">=</span> <span class="p">[</span><span class="n">ConstantValueSecretsProvider</span><span class="p">]</span>
|
|
10135
10136
|
</code></pre></div>
|
|
10136
10137
|
<p>After installing and enabling your app, you should now be able to navigate to <code>Secrets > Secrets</code> and create a new Secret, at which point <code>"constant-value"</code> should now be available as a new secrets provider to use.</p>
|
|
10137
10138
|
|