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.

Files changed (38) hide show
  1. nautobot/core/api/parsers.py +56 -2
  2. nautobot/core/models/__init__.py +2 -0
  3. nautobot/core/tests/test_csv.py +92 -1
  4. nautobot/core/tests/test_jinja_filters.py +59 -0
  5. nautobot/core/tests/test_views.py +73 -0
  6. nautobot/core/urls.py +2 -2
  7. nautobot/core/views/__init__.py +21 -0
  8. nautobot/dcim/models/device_component_templates.py +4 -0
  9. nautobot/dcim/models/device_components.py +12 -0
  10. nautobot/dcim/models/devices.py +6 -0
  11. nautobot/extras/context_managers.py +2 -2
  12. nautobot/extras/models/customfields.py +2 -0
  13. nautobot/extras/models/datasources.py +8 -0
  14. nautobot/extras/models/groups.py +18 -0
  15. nautobot/extras/models/jobs.py +14 -0
  16. nautobot/extras/models/metadata.py +2 -0
  17. nautobot/extras/models/models.py +4 -0
  18. nautobot/extras/models/secrets.py +7 -0
  19. nautobot/extras/secrets/__init__.py +14 -0
  20. nautobot/extras/tests/test_context_managers.py +20 -0
  21. nautobot/extras/tests/test_models.py +26 -0
  22. nautobot/ipam/models.py +32 -0
  23. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +39 -38
  24. nautobot/project-static/docs/release-notes/version-1.6.html +297 -0
  25. nautobot/project-static/docs/release-notes/version-2.4.html +101 -0
  26. nautobot/project-static/docs/search/search_index.json +1 -1
  27. nautobot/project-static/docs/sitemap.xml +298 -298
  28. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  29. nautobot/project-static/docs/user-guide/administration/security/index.html +0 -1
  30. nautobot/project-static/docs/user-guide/administration/security/notices.html +113 -1
  31. nautobot/users/models.py +4 -0
  32. nautobot/virtualization/models.py +4 -0
  33. {nautobot-2.4.9.dist-info → nautobot-2.4.10.dist-info}/METADATA +2 -2
  34. {nautobot-2.4.9.dist-info → nautobot-2.4.10.dist-info}/RECORD +38 -38
  35. {nautobot-2.4.9.dist-info → nautobot-2.4.10.dist-info}/LICENSE.txt +0 -0
  36. {nautobot-2.4.9.dist-info → nautobot-2.4.10.dist-info}/NOTICE +0 -0
  37. {nautobot-2.4.9.dist-info → nautobot-2.4.10.dist-info}/WHEEL +0 -0
  38. {nautobot-2.4.9.dist-info → nautobot-2.4.10.dist-info}/entry_points.txt +0 -0
@@ -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.
@@ -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><span class="kn">from</span> <span class="nn">nautobot.apps.secrets</span> <span class="kn">import</span> <span class="n">SecretsProvider</span>
10096
- <a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a><span class="kn">from</span> <span class="nn">nautobot.utilities.forms</span> <span class="kn">import</span> <span class="n">BootstrapMixin</span>
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><span class="k">class</span> <span class="nc">ConstantValueSecretsProvider</span><span class="p">(</span><span class="n">SecretsProvider</span><span class="p">):</span>
10100
- <a id="__codelineno-0-8" name="__codelineno-0-8" href="#__codelineno-0-8"></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
10101
- <a id="__codelineno-0-9" name="__codelineno-0-9" href="#__codelineno-0-9"></a><span class="sd"> Example SecretsProvider - this one just returns a user-specified constant value.</span>
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><span class="sd"> Obviously this is insecure and not something you&#39;d want to actually use!</span>
10104
- <a id="__codelineno-0-12" name="__codelineno-0-12" href="#__codelineno-0-12"></a><span class="sd"> &quot;&quot;&quot;</span>
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> <span class="n">slug</span> <span class="o">=</span> <span class="s2">&quot;constant-value&quot;</span>
10107
- <a id="__codelineno-0-15" name="__codelineno-0-15" href="#__codelineno-0-15"></a> <span class="n">name</span> <span class="o">=</span> <span class="s2">&quot;Constant Value&quot;</span>
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> <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>
10110
- <a id="__codelineno-0-18" name="__codelineno-0-18" href="#__codelineno-0-18"></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
10111
- <a id="__codelineno-0-19" name="__codelineno-0-19" href="#__codelineno-0-19"></a><span class="sd"> User-friendly form for specifying the required parameters of this provider.</span>
10112
- <a id="__codelineno-0-20" name="__codelineno-0-20" href="#__codelineno-0-20"></a><span class="sd"> &quot;&quot;&quot;</span>
10113
- <a id="__codelineno-0-21" name="__codelineno-0-21" href="#__codelineno-0-21"></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>
10114
- <a id="__codelineno-0-22" name="__codelineno-0-22" href="#__codelineno-0-22"></a> <span class="n">required</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
10115
- <a id="__codelineno-0-23" name="__codelineno-0-23" href="#__codelineno-0-23"></a> <span class="n">help_text</span><span class="o">=</span><span class="s2">&quot;Constant secret value. &lt;strong&gt;DO NOT USE FOR REAL DATA&lt;/strong&gt;&quot;</span>
10116
- <a id="__codelineno-0-24" name="__codelineno-0-24" href="#__codelineno-0-24"></a> <span class="p">)</span>
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> <span class="nd">@classmethod</span>
10119
- <a id="__codelineno-0-27" name="__codelineno-0-27" href="#__codelineno-0-27"></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>
10120
- <a id="__codelineno-0-28" name="__codelineno-0-28" href="#__codelineno-0-28"></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
10121
- <a id="__codelineno-0-29" name="__codelineno-0-29" href="#__codelineno-0-29"></a><span class="sd"> Return the value defined in the Secret.parameters &quot;constant&quot; key.</span>
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><span class="sd"> A more realistic SecretsProvider would make calls to external APIs, etc.,</span>
10124
- <a id="__codelineno-0-32" name="__codelineno-0-32" href="#__codelineno-0-32"></a><span class="sd"> to retrieve a secret from another system as desired.</span>
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><span class="sd"> Args:</span>
10127
- <a id="__codelineno-0-35" name="__codelineno-0-35" href="#__codelineno-0-35"></a><span class="sd"> secret (nautobot.extras.models.Secret): The secret whose value should be retrieved.</span>
10128
- <a id="__codelineno-0-36" name="__codelineno-0-36" href="#__codelineno-0-36"></a><span class="sd"> obj (object): The object (Django model or similar) providing context for the secret&#39;s</span>
10129
- <a id="__codelineno-0-37" name="__codelineno-0-37" href="#__codelineno-0-37"></a><span class="sd"> parameters.</span>
10130
- <a id="__codelineno-0-38" name="__codelineno-0-38" href="#__codelineno-0-38"></a><span class="sd"> &quot;&quot;&quot;</span>
10131
- <a id="__codelineno-0-39" name="__codelineno-0-39" href="#__codelineno-0-39"></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">&quot;constant&quot;</span><span class="p">)</span>
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">&quot;&quot;&quot;</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&#39;d want to actually use!</span>
10105
+ <a id="__codelineno-0-13" name="__codelineno-0-13" href="#__codelineno-0-13"></a><span class="sd"> &quot;&quot;&quot;</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">&quot;constant-value&quot;</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">&quot;Constant Value&quot;</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">&quot;&quot;&quot;</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"> &quot;&quot;&quot;</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">&quot;Constant secret value. &lt;strong&gt;DO NOT USE FOR REAL DATA&lt;/strong&gt;&quot;</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">&quot;&quot;&quot;</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 &quot;constant&quot; 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&#39;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"> &quot;&quot;&quot;</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">&quot;constant&quot;</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><span class="n">secrets_providers</span> <span class="o">=</span> <span class="p">[</span><span class="n">ConstantValueSecretsProvider</span><span class="p">]</span>
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 &gt; 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