Fix support for object-based permissions

This commit is contained in:
Jeremy Stretch 2024-02-22 13:54:14 -05:00
parent 2d206fbe8b
commit 274bbfa22f
6 changed files with 59 additions and 102 deletions

View File

@ -1038,63 +1038,29 @@ class ScriptListView(ContentTypePermissionRequiredMixin, View):
}) })
def get_script_module(module, request): class ScriptView(generic.ObjectView):
return get_object_or_404(ScriptModule.objects.restrict(request.user), file_path__regex=f"^{module}\\.")
class BaseScriptView(generic.ObjectView):
queryset = Script.objects.all() queryset = Script.objects.all()
script = None
script_class = None
jobs = None
def _init_vars(self, request): def get(self, request, **kwargs):
if self.script.python_class: script = self.get_object(**kwargs)
self.script_class = self.script.python_class() script_class = script.python_class()
else: form = script_class.as_form(initial=normalize_querydict(request.GET))
self.script.delete(soft_delete=True)
messages.error(request, _("Script class has been deleted from module: ") + str(self.script.module))
if not self.script.id:
return redirect('extras:script_list')
else:
return redirect('extras:script_jobs', pk=self.script.id)
self.jobs = self.script.jobs.all()
return None
def init_vars_or_redirect(self, request, pk):
self.script = get_object_or_404(Script.objects.all(), pk=pk)
return self._init_vars(request)
class ScriptView(BaseScriptView):
def get(self, request, pk):
if ret := self.init_vars_or_redirect(request, pk):
return ret
form = None
if self.script_class:
form = self.script_class.as_form(initial=normalize_querydict(request.GET))
return render(request, 'extras/script.html', { return render(request, 'extras/script.html', {
'job_count': self.jobs.count(), 'script': script,
'module': self.script.module, 'script_class': script_class,
'script': self.script,
'script_class': self.script_class,
'form': form, 'form': form,
'job_count': script.jobs.count(),
}) })
def post(self, request, pk): def post(self, request, **kwargs):
if not request.user.has_perm('extras.run_script'): script = self.get_object(**kwargs)
script_class = script.python_class()
if not request.user.has_perm('extras.run_script', obj=script):
return HttpResponseForbidden() return HttpResponseForbidden()
if ret := self.init_vars_or_redirect(request, pk): form = script_class.as_form(request.POST, request.FILES)
return ret
form = None
if self.script_class:
form = self.script_class.as_form(request.POST, request.FILES)
# Allow execution only if RQ worker process is running # Allow execution only if RQ worker process is running
if not get_workers_for_queue('default'): if not get_workers_for_queue('default'):
@ -1102,77 +1068,58 @@ class ScriptView(BaseScriptView):
elif form.is_valid(): elif form.is_valid():
job = Job.enqueue( job = Job.enqueue(
run_script, run_script,
instance=self.script, instance=script,
name=self.script_class.class_name, name=script_class.class_name,
user=request.user, user=request.user,
schedule_at=form.cleaned_data.pop('_schedule_at'), schedule_at=form.cleaned_data.pop('_schedule_at'),
interval=form.cleaned_data.pop('_interval'), interval=form.cleaned_data.pop('_interval'),
data=form.cleaned_data, data=form.cleaned_data,
request=copy_safe_request(request), request=copy_safe_request(request),
job_timeout=self.script.python_class.job_timeout, job_timeout=script.python_class.job_timeout,
commit=form.cleaned_data.pop('_commit') commit=form.cleaned_data.pop('_commit')
) )
return redirect('extras:script_result', job_pk=job.pk) return redirect('extras:script_result', job_pk=job.pk)
return render(request, 'extras/script.html', { return render(request, 'extras/script.html', {
'job_count': self.jobs.count(), 'script': script,
'module': self.script.module, 'script_class': script.python_class(),
'script': self.script,
'script_class': self.script_class,
'form': form, 'form': form,
'job_count': script.jobs.count(),
}) })
class ScriptSourceView(BaseScriptView): class ScriptSourceView(generic.ObjectView):
queryset = Script.objects.all()
def get(self, request, pk): def get(self, request, **kwargs):
if ret := self.init_vars_or_redirect(request, pk): script = self.get_object(**kwargs)
return ret
return render(request, 'extras/script/source.html', { return render(request, 'extras/script/source.html', {
'job_count': self.jobs.count(), 'script': script,
'module': self.script.module, 'script_class': script.python_class(),
'script': self.script, 'job_count': script.jobs.count(),
'script_class': self.script_class,
'tab': 'source', 'tab': 'source',
}) })
class ScriptJobsView(generic.ObjectView): class ScriptJobsView(generic.ObjectView):
queryset = Script.objects.all() queryset = Script.objects.all()
script = None
script_class = None
jobs = None
def get_required_permission(self): def get(self, request, **kwargs):
return 'extras.view_script' script = self.get_object(**kwargs)
def get(self, request, pk):
self.script = get_object_or_404(Script.objects.all(), pk=pk)
if self.script.python_class:
self.script_class = self.script.python_class()
else:
self.script.delete(soft_delete=True)
if not self.script.id:
messages.error(request, _("Script class has been deleted from module: ") + str(self.script.module))
return redirect('extras:script_list')
self.jobs = self.script.jobs.all()
jobs_table = JobTable( jobs_table = JobTable(
data=self.jobs, data=script.jobs.all(),
orderable=False, orderable=False,
user=request.user user=request.user
) )
jobs_table.configure(request) jobs_table.configure(request)
return render(request, 'extras/script/jobs.html', { return render(request, 'extras/script/jobs.html', {
'job_count': self.jobs.count(), 'script': script,
'module': self.script.module,
'script': self.script,
'table': jobs_table, 'table': jobs_table,
'job_count': script.jobs.count(),
'tab': 'jobs', 'tab': 'jobs',
}) })
@ -1196,19 +1143,17 @@ class LegacyScriptRedirectView(ContentTypePermissionRequiredMixin, View):
return redirect(f'{url}{path}') return redirect(f'{url}{path}')
class ScriptResultView(BaseScriptView): class ScriptResultView(generic.ObjectView):
queryset = Job.objects.all()
def get_required_permission(self): def get_required_permission(self):
return 'extras.view_script' return 'extras.view_script'
def get(self, request, job_pk): def get(self, request, **kwargs):
object_type = ContentType.objects.get_by_natural_key(app_label='extras', model='script') job = get_object_or_404(Job.objects.all(), pk=kwargs.get('job_pk'))
job = get_object_or_404(Job.objects.all(), pk=job_pk, object_type=object_type)
script = job.object
context = { context = {
'script': script, 'script': job.object,
'job': job, 'job': job,
} }
if job.data and 'log' in job.data: if job.data and 'log' in job.data:

View File

@ -2,6 +2,7 @@
{% load helpers %} {% load helpers %}
{% load form_helpers %} {% load form_helpers %}
{% load log_levels %} {% load log_levels %}
{% load perms %}
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
@ -32,9 +33,17 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
<div class="float-end"> <div class="text-end">
<a href="{% url 'extras:script_list' %}" class="btn btn-outline-secondary">{% trans "Cancel" %}</a> <a href="{% url 'extras:script_list' %}" class="btn btn-outline-secondary">{% trans "Cancel" %}</a>
<button type="submit" name="_run" class="btn btn-primary"{% if not perms.extras.run_script %} disabled="disabled"{% endif %}><i class="mdi mdi-play"></i> {% trans "Run Script" %}</button> {% if not request.user|can_run:script or not script.is_executable %}
<button class="btn btn-primary" disabled>
<i class="mdi mdi-play"></i> {% trans "Run Script" %}
</button>
{% else %}
<button type="submit" name="_run" class="btn btn-primary">
<i class="mdi mdi-play"></i> {% trans "Run Script" %}
</button>
{% endif %}
</div> </div>
</form> </form>
</div> </div>

View File

@ -12,7 +12,7 @@
{% block breadcrumbs %} {% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}">{% trans "Scripts" %}</a></li> <li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}">{% trans "Scripts" %}</a></li>
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}#module{{ module.pk }}">{{ module|bettertitle }}</a></li> <li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}#module{{ script.module.pk }}">{{ script.module|bettertitle }}</a></li>
{% endblock breadcrumbs %} {% endblock breadcrumbs %}
{% block subtitle %} {% block subtitle %}

View File

@ -59,7 +59,9 @@
<a href="{% url 'extras:script' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.name }}</a> <a href="{% url 'extras:script' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.name }}</a>
{% else %} {% else %}
<a href="{% url 'extras:script_jobs' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.name }}</a> <a href="{% url 'extras:script_jobs' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.name }}</a>
<i class="mdi mdi-alert"></i> <span class="text-danger">
<i class="mdi mdi-alert" title="{% trans "Script is no longer present in the source file" %}"></i>
</span>
{% endif %} {% endif %}
</td> </td>
<td>{{ script.description|markdown|placeholder }}</td> <td>{{ script.description|markdown|placeholder }}</td>
@ -75,7 +77,7 @@
<td>{{ ''|placeholder }}</td> <td>{{ ''|placeholder }}</td>
{% endif %} {% endif %}
<td> <td>
{% if perms.extras.run_script %} {% if request.user|can_run:script and script.is_executable %}
<div class="float-end d-print-none"> <div class="float-end d-print-none">
<form action="{% url 'extras:script' script.pk %}" method="post"> <form action="{% url 'extras:script' script.pk %}" method="post">
{% csrf_token %} {% csrf_token %}

View File

@ -376,11 +376,6 @@ class ObjectPermissionForm(forms.ModelForm):
for ct in object_types: for ct in object_types:
model = ct.model_class() model = ct.model_class()
if model._meta.model_name in ['script', 'report']:
raise forms.ValidationError({
'constraints': _('Constraints are not supported for this object type.')
})
try: try:
tokens = { tokens = {
CONSTRAINT_TOKEN_USER: 0, # Replace token with a null user ID CONSTRAINT_TOKEN_USER: 0, # Replace token with a null user ID

View File

@ -6,6 +6,7 @@ __all__ = (
'can_add', 'can_add',
'can_change', 'can_change',
'can_delete', 'can_delete',
'can_run',
'can_sync', 'can_sync',
'can_view', 'can_view',
) )
@ -42,3 +43,8 @@ def can_delete(user, instance):
@register.filter() @register.filter()
def can_sync(user, instance): def can_sync(user, instance):
return _check_permission(user, instance, 'sync') return _check_permission(user, instance, 'sync')
@register.filter()
def can_run(user, instance):
return _check_permission(user, instance, 'run')