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

View File

@ -2,6 +2,7 @@
{% load helpers %}
{% load form_helpers %}
{% load log_levels %}
{% load perms %}
{% load i18n %}
{% block content %}
@ -32,9 +33,17 @@
{% endif %}
{% endfor %}
</div>
<div class="float-end">
<div class="text-end">
<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>
</form>
</div>

View File

@ -12,7 +12,7 @@
{% 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' %}#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 %}
{% block subtitle %}

View File

@ -59,7 +59,9 @@
<a href="{% url 'extras:script' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.name }}</a>
{% else %}
<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 %}
</td>
<td>{{ script.description|markdown|placeholder }}</td>
@ -75,7 +77,7 @@
<td>{{ ''|placeholder }}</td>
{% endif %}
<td>
{% if perms.extras.run_script %}
{% 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">
{% csrf_token %}

View File

@ -376,11 +376,6 @@ class ObjectPermissionForm(forms.ModelForm):
for ct in object_types:
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:
tokens = {
CONSTRAINT_TOKEN_USER: 0, # Replace token with a null user ID

View File

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