Просмотр исходного кода

Fixes: #20490 - Add filtering of Script objects based on object permissions with custom constraints (#21212)

bctiemann 20 часов назад
Родитель
Сommit
b22e490847

+ 2 - 1
docs/customization/custom-scripts.md

@@ -18,7 +18,8 @@ They can also be used as a mechanism for validating the integrity of data within
 Custom scripts are Python code which exists outside the NetBox code base, so they can be updated and changed without interfering with the core NetBox installation. And because they're completely custom, there is no inherent limitation on what a script can accomplish.
 
 !!! danger "Only install trusted scripts"
-    Custom scripts have unrestricted access to change anything in the databse and are inherently unsafe and should only be installed and run from trusted sources.  You should also review and set permissions for who can run scripts if the script can modify any data.
+    Custom scripts have unrestricted access to change anything in the database and are inherently unsafe and should only be installed and run from trusted sources.  You should also review and set permissions for who can run scripts if the script can modify any data.
+
 
 ## Writing Custom Scripts
 

+ 6 - 2
netbox/extras/views.py

@@ -1443,12 +1443,16 @@ class ScriptListView(ContentTypePermissionRequiredMixin, View):
         return 'extras.view_script'
 
     def get(self, request):
-        script_modules = ScriptModule.objects.restrict(request.user).prefetch_related(
-            'data_source', 'data_file', 'jobs'
+        available_scripts = Script.objects.restrict(request.user)
+        module_ids = {s.module_id for s in available_scripts}
+        script_modules = ScriptModule.objects.restrict(request.user).filter(pk__in=module_ids).prefetch_related(
+            'data_source', 'data_file',
         )
+
         context = {
             'model': ScriptModule,
             'script_modules': script_modules,
+            'available_scripts': available_scripts,
         }
 
         # Use partial template for dashboard widgets

+ 72 - 70
netbox/templates/extras/inc/script_list_content.html

@@ -38,81 +38,83 @@
           </thead>
           <tbody>
             {% for script in scripts %}
-              {% with last_job=script.get_latest_jobs|first %}
-                <tr>
-                  <td>
-                    {% if script.is_executable %}
-                      <a href="{% url 'extras:script' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
-                    {% else %}
-                      <a href="{% url 'extras:script_jobs' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
-                      <span class="text-danger">
-                        <i class="mdi mdi-alert" title="{% trans "Script is no longer present in the source file" %}"></i>
-                      </span>
-                    {% endif %}
-                  </td>
-                  <td>{{ script.python_class.description|markdown|placeholder }}</td>
-                  {% if last_job %}
+              {% if script in available_scripts %}
+                {% with last_job=script.get_latest_jobs|first %}
+                  <tr>
                     <td>
-                      <a href="{% url 'extras:script_result' job_pk=last_job.pk %}">{{ last_job.created|isodatetime }}</a>
+                      {% if script.is_executable %}
+                        <a href="{% url 'extras:script' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
+                      {% else %}
+                        <a href="{% url 'extras:script_jobs' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
+                        <span class="text-danger">
+                          <i class="mdi mdi-alert" title="{% trans "Script is no longer present in the source file" %}"></i>
+                        </span>
+                      {% endif %}
                     </td>
+                    <td>{{ script.python_class.description|markdown|placeholder }}</td>
+                    {% if last_job %}
+                      <td>
+                        <a href="{% url 'extras:script_result' job_pk=last_job.pk %}">{{ last_job.created|isodatetime }}</a>
+                      </td>
+                      <td>
+                        {% badge last_job.get_status_display last_job.get_status_color %}
+                      </td>
+                    {% else %}
+                      <td class="text-muted">{% trans "Never" %}</td>
+                      <td>{{ ''|placeholder }}</td>
+                    {% endif %}
                     <td>
-                      {% badge last_job.get_status_display last_job.get_status_color %}
+                      {% if request.user|can_run:script and script.is_executable %}
+                        <div class="float-end d-print-none">
+                          <form action="{% url 'extras:script' script.pk %}" method="post">
+                            {% if script.python_class.commit_default %}
+                              <input type="checkbox" name="_commit" hidden checked>
+                            {% endif %}
+                            {% csrf_token %}
+                            <button type="submit" name="_run" class="btn btn-primary{% if embedded %} btn-sm{% endif %}">
+                              {% if last_job %}
+                                <i class="mdi mdi-replay"></i> {% if not embedded %}{% trans "Run Again" %}{% endif %}
+                              {% else %}
+                                <i class="mdi mdi-play"></i> {% if not embedded %}{% trans "Run Script" %}{% endif %}
+                              {% endif %}
+                            </button>
+                          </form>
+                        </div>
+                      {% endif %}
                     </td>
-                  {% else %}
-                    <td class="text-muted">{% trans "Never" %}</td>
-                    <td>{{ ''|placeholder }}</td>
+                  </tr>
+                  {% if last_job and not embedded %}
+                    {% for test_name, data in last_job.data.tests.items %}
+                      <tr>
+                        <td colspan="4" class="method">
+                          <span class="ps-3">{{ test_name }}</span>
+                        </td>
+                        <td class="text-end text-nowrap script-stats">
+                          <span class="badge text-bg-success">{{ data.success }}</span>
+                          <span class="badge text-bg-info">{{ data.info }}</span>
+                          <span class="badge text-bg-warning">{{ data.warning }}</span>
+                          <span class="badge text-bg-danger">{{ data.failure }}</span>
+                        </td>
+                      </tr>
+                    {% endfor %}
+                  {% elif last_job and not last_job.data.log and not embedded %}
+                    {# legacy #}
+                    {% for method, stats in last_job.data.items %}
+                      <tr>
+                        <td colspan="4" class="method">
+                          <span class="ps-3">{{ method }}</span>
+                        </td>
+                        <td class="text-end text-nowrap report-stats">
+                          <span class="badge bg-success">{{ stats.success }}</span>
+                          <span class="badge bg-info">{{ stats.info }}</span>
+                          <span class="badge bg-warning">{{ stats.warning }}</span>
+                          <span class="badge bg-danger">{{ stats.failure }}</span>
+                        </td>
+                      </tr>
+                    {% endfor %}
                   {% endif %}
-                  <td>
-                    {% if request.user|can_run:script and script.is_executable %}
-                      <div class="float-end d-print-none">
-                        <form action="{% url 'extras:script' script.pk %}" method="post">
-                          {% if script.python_class.commit_default %}
-                            <input type="checkbox" name="_commit" hidden checked>
-                          {% endif %}
-                          {% csrf_token %}
-                          <button type="submit" name="_run" class="btn btn-primary{% if embedded %} btn-sm{% endif %}">
-                            {% if last_job %}
-                              <i class="mdi mdi-replay"></i> {% if not embedded %}{% trans "Run Again" %}{% endif %}
-                            {% else %}
-                              <i class="mdi mdi-play"></i> {% if not embedded %}{% trans "Run Script" %}{% endif %}
-                            {% endif %}
-                          </button>
-                        </form>
-                      </div>
-                    {% endif %}
-                  </td>
-                </tr>
-                {% if last_job and not embedded %}
-                  {% for test_name, data in last_job.data.tests.items %}
-                    <tr>
-                      <td colspan="4" class="method">
-                        <span class="ps-3">{{ test_name }}</span>
-                      </td>
-                      <td class="text-end text-nowrap script-stats">
-                        <span class="badge text-bg-success">{{ data.success }}</span>
-                        <span class="badge text-bg-info">{{ data.info }}</span>
-                        <span class="badge text-bg-warning">{{ data.warning }}</span>
-                        <span class="badge text-bg-danger">{{ data.failure }}</span>
-                      </td>
-                    </tr>
-                  {% endfor %}
-                {% elif last_job and not last_job.data.log and not embedded %}
-                  {# legacy #}
-                  {% for method, stats in last_job.data.items %}
-                    <tr>
-                      <td colspan="4" class="method">
-                        <span class="ps-3">{{ method }}</span>
-                      </td>
-                      <td class="text-end text-nowrap report-stats">
-                        <span class="badge bg-success">{{ stats.success }}</span>
-                        <span class="badge bg-info">{{ stats.info }}</span>
-                        <span class="badge bg-warning">{{ stats.warning }}</span>
-                        <span class="badge bg-danger">{{ stats.failure }}</span>
-                      </td>
-                    </tr>
-                  {% endfor %}
-                {% endif %}
-              {% endwith %}
+                {% endwith %}
+              {% endif %}
             {% endfor %}
           </tbody>
         </table>

+ 1 - 1
netbox/users/forms/model_forms.py

@@ -328,7 +328,7 @@ class ObjectPermissionForm(forms.ModelForm):
         widget=SplitMultiSelectWidget(
             choices=get_object_types_choices
         ),
-        help_text=_('Select the types of objects to which the permission will appy.')
+        help_text=_('Select the types of objects to which the permission will apply.')
     )
     can_view = forms.BooleanField(
         required=False