Explorar o código

Fixes #4146: Fix SecretRole permissions enforcement

Jeremy Stretch %!s(int64=6) %!d(string=hai) anos
pai
achega
5bf85597ed

+ 1 - 0
docs/release-notes/version-2.7.md

@@ -20,6 +20,7 @@
 * [#4108](https://github.com/netbox-community/netbox/issues/4108) - Avoid extraneous database queries when rendering search forms
 * [#4108](https://github.com/netbox-community/netbox/issues/4108) - Avoid extraneous database queries when rendering search forms
 * [#4134](https://github.com/netbox-community/netbox/issues/4134) - Device power ports and outlets should inherit type from the parent device type
 * [#4134](https://github.com/netbox-community/netbox/issues/4134) - Device power ports and outlets should inherit type from the parent device type
 * [#4137](https://github.com/netbox-community/netbox/issues/4137) - Disable occupied terminations when connecting a cable to a circuit
 * [#4137](https://github.com/netbox-community/netbox/issues/4137) - Disable occupied terminations when connecting a cable to a circuit
+* [#4146](https://github.com/netbox-community/netbox/issues/4146) - Fix enforcement of secret role assignment for secret decryption
 
 
 ---
 ---
 
 

+ 5 - 3
netbox/secrets/api/views.py

@@ -93,8 +93,8 @@ class SecretViewSet(ModelViewSet):
 
 
         secret = self.get_object()
         secret = self.get_object()
 
 
-        # Attempt to decrypt the secret if the master key is known
-        if self.master_key is not None:
+        # Attempt to decrypt the secret if the user is permitted and the master key is known
+        if secret.decryptable_by(request.user) and self.master_key is not None:
             secret.decrypt(self.master_key)
             secret.decrypt(self.master_key)
 
 
         serializer = self.get_serializer(secret)
         serializer = self.get_serializer(secret)
@@ -111,7 +111,9 @@ class SecretViewSet(ModelViewSet):
             if self.master_key is not None:
             if self.master_key is not None:
                 secrets = []
                 secrets = []
                 for secret in page:
                 for secret in page:
-                    secret.decrypt(self.master_key)
+                    # Enforce role permissions
+                    if secret.decryptable_by(request.user):
+                        secret.decrypt(self.master_key)
                     secrets.append(secret)
                     secrets.append(secret)
                 serializer = self.get_serializer(secrets, many=True)
                 serializer = self.get_serializer(secrets, many=True)
             else:
             else:

+ 36 - 11
netbox/secrets/tests/test_api.py

@@ -5,7 +5,8 @@ from rest_framework import status
 
 
 from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
 from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
 from secrets.models import Secret, SecretRole, SessionKey, UserKey
 from secrets.models import Secret, SecretRole, SessionKey, UserKey
-from utilities.testing import APITestCase
+from users.models import Token
+from utilities.testing import APITestCase, create_test_user
 from .constants import PRIVATE_KEY, PUBLIC_KEY
 from .constants import PRIVATE_KEY, PUBLIC_KEY
 
 
 
 
@@ -131,7 +132,15 @@ class SecretTest(APITestCase):
 
 
     def setUp(self):
     def setUp(self):
 
 
-        super().setUp()
+        # Create a non-superuser test user
+        self.user = create_test_user('testuser', permissions=(
+            'secrets.add_secret',
+            'secrets.change_secret',
+            'secrets.delete_secret',
+            'secrets.view_secret',
+        ))
+        self.token = Token.objects.create(user=self.user)
+        self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(self.token.key)}
 
 
         userkey = UserKey(user=self.user, public_key=PUBLIC_KEY)
         userkey = UserKey(user=self.user, public_key=PUBLIC_KEY)
         userkey.save()
         userkey.save()
@@ -144,11 +153,11 @@ class SecretTest(APITestCase):
             'HTTP_X_SESSION_KEY': base64.b64encode(session_key.key),
             'HTTP_X_SESSION_KEY': base64.b64encode(session_key.key),
         }
         }
 
 
-        self.plaintext = {
-            'secret1': 'Secret #1 Plaintext',
-            'secret2': 'Secret #2 Plaintext',
-            'secret3': 'Secret #3 Plaintext',
-        }
+        self.plaintexts = (
+            'Secret #1 Plaintext',
+            'Secret #2 Plaintext',
+            'Secret #3 Plaintext',
+        )
 
 
         site = Site.objects.create(name='Test Site 1', slug='test-site-1')
         site = Site.objects.create(name='Test Site 1', slug='test-site-1')
         manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
         manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
@@ -160,17 +169,17 @@ class SecretTest(APITestCase):
         self.secretrole1 = SecretRole.objects.create(name='Test Secret Role 1', slug='test-secret-role-1')
         self.secretrole1 = SecretRole.objects.create(name='Test Secret Role 1', slug='test-secret-role-1')
         self.secretrole2 = SecretRole.objects.create(name='Test Secret Role 2', slug='test-secret-role-2')
         self.secretrole2 = SecretRole.objects.create(name='Test Secret Role 2', slug='test-secret-role-2')
         self.secret1 = Secret(
         self.secret1 = Secret(
-            device=self.device, role=self.secretrole1, name='Test Secret 1', plaintext=self.plaintext['secret1']
+            device=self.device, role=self.secretrole1, name='Test Secret 1', plaintext=self.plaintexts[0]
         )
         )
         self.secret1.encrypt(self.master_key)
         self.secret1.encrypt(self.master_key)
         self.secret1.save()
         self.secret1.save()
         self.secret2 = Secret(
         self.secret2 = Secret(
-            device=self.device, role=self.secretrole1, name='Test Secret 2', plaintext=self.plaintext['secret2']
+            device=self.device, role=self.secretrole1, name='Test Secret 2', plaintext=self.plaintexts[1]
         )
         )
         self.secret2.encrypt(self.master_key)
         self.secret2.encrypt(self.master_key)
         self.secret2.save()
         self.secret2.save()
         self.secret3 = Secret(
         self.secret3 = Secret(
-            device=self.device, role=self.secretrole1, name='Test Secret 3', plaintext=self.plaintext['secret3']
+            device=self.device, role=self.secretrole1, name='Test Secret 3', plaintext=self.plaintexts[2]
         )
         )
         self.secret3.encrypt(self.master_key)
         self.secret3.encrypt(self.master_key)
         self.secret3.save()
         self.secret3.save()
@@ -178,16 +187,32 @@ class SecretTest(APITestCase):
     def test_get_secret(self):
     def test_get_secret(self):
 
 
         url = reverse('secrets-api:secret-detail', kwargs={'pk': self.secret1.pk})
         url = reverse('secrets-api:secret-detail', kwargs={'pk': self.secret1.pk})
+
+        # Secret plaintext not be decrypted as the user has not been assigned to the role
         response = self.client.get(url, **self.header)
         response = self.client.get(url, **self.header)
+        self.assertIsNone(response.data['plaintext'])
 
 
-        self.assertEqual(response.data['plaintext'], self.plaintext['secret1'])
+        # The plaintext should be present once the user has been assigned to the role
+        self.secretrole1.users.add(self.user)
+        response = self.client.get(url, **self.header)
+        self.assertEqual(response.data['plaintext'], self.plaintexts[0])
 
 
     def test_list_secrets(self):
     def test_list_secrets(self):
 
 
         url = reverse('secrets-api:secret-list')
         url = reverse('secrets-api:secret-list')
+
+        # Secret plaintext not be decrypted as the user has not been assigned to the role
         response = self.client.get(url, **self.header)
         response = self.client.get(url, **self.header)
+        self.assertEqual(response.data['count'], 3)
+        for secret in response.data['results']:
+            self.assertIsNone(secret['plaintext'])
 
 
+        # The plaintext should be present once the user has been assigned to the role
+        self.secretrole1.users.add(self.user)
+        response = self.client.get(url, **self.header)
         self.assertEqual(response.data['count'], 3)
         self.assertEqual(response.data['count'], 3)
+        for i, secret in enumerate(response.data['results']):
+            self.assertEqual(secret['plaintext'], self.plaintexts[i])
 
 
     def test_create_secret(self):
     def test_create_secret(self):
 
 

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

@@ -1,6 +1,7 @@
 {% extends '_base.html' %}
 {% extends '_base.html' %}
 {% load static %}
 {% load static %}
 {% load form_helpers %}
 {% load form_helpers %}
+{% load secret_helpers %}
 
 
 {% block content %}
 {% block content %}
 <form action="." method="post" class="form form-horizontal">
 <form action="." method="post" class="form form-horizontal">
@@ -34,7 +35,7 @@
             <div class="panel panel-default">
             <div class="panel panel-default">
                 <div class="panel-heading"><strong>Secret Data</strong></div>
                 <div class="panel-heading"><strong>Secret Data</strong></div>
                 <div class="panel-body">
                 <div class="panel-body">
-                    {% if secret.pk %}
+                    {% if secret.pk and secret|decryptable_by:request.user %}
                         <div class="form-group">
                         <div class="form-group">
                             <label class="col-md-3 control-label required">Current Plaintext</label>
                             <label class="col-md-3 control-label required">Current Plaintext</label>
                             <div class="col-md-7">
                             <div class="col-md-7">

+ 3 - 1
netbox/utilities/testing/utils.py

@@ -21,11 +21,13 @@ def post_data(data):
     return ret
     return ret
 
 
 
 
-def create_test_user(username='testuser', permissions=list()):
+def create_test_user(username='testuser', permissions=None):
     """
     """
     Create a User with the given permissions.
     Create a User with the given permissions.
     """
     """
     user = User.objects.create_user(username=username)
     user = User.objects.create_user(username=username)
+    if permissions is None:
+        permissions = ()
     for perm_name in permissions:
     for perm_name in permissions:
         app, codename = perm_name.split('.')
         app, codename = perm_name.split('.')
         perm = Permission.objects.get(content_type__app_label=app, codename=codename)
         perm = Permission.objects.get(content_type__app_label=app, codename=codename)