mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-09-09 19:31:47 +02:00
SharedObjectManager scopes querysets to what a user may see, which includes other people's public and shared-with-them objects. Several mutating rule endpoints treated that visibility as permission to write. Generalise get_owned_object_or_403 into get_shared_object_or_error, which takes an explicit access level instead of inferring one: - READ requires the object to be visible; denial is 404 so the response does not confirm that an id exists. - EDIT requires ownership; denial is 403, but only after the visibility check, so 403 never leaks the existence of an invisible object. This matters for TransactionRuleAction, whose manager is unscoped. The previous owner_path resolved to a User and discarded the object, so it could not express visibility at all. via= now points at the governing SharedObject and is resolved with a plain getattr, so an unresolvable path raises instead of silently granting access. is_visible_to/is_editable_by replace is_accessible_by, which was never called and tested visibility == "shared", a value that does not exist in Visibility. Also fixes two further holes in the same module: - transaction_rule_delete fell through to delete() whenever the caller was not in shared_with, so any user could delete a public rule. Now only the owner deletes; a shared user revokes their own access. - transaction_rule_view was read-only but is now explicitly READ, so rules shared with a user stay viewable. The activate/deactivate control is hidden for rules the user does not own, instead of rendering a button that always fails. Refs GHSA-83g9-vjqf-2j5q
101 lines
4.8 KiB
HTML
101 lines
4.8 KiB
HTML
{% load i18n %}
|
|
<c-ui.fab-single-action
|
|
url="{% url 'transaction_rule_add' %}"
|
|
hx_target="#generic-offcanvas">
|
|
</c-ui.fab-single-action>
|
|
<div class="container">
|
|
<div class="text-3xl font-bold font-mono w-full mb-3">
|
|
{% spaceless %}
|
|
<div>{% translate 'Rules' %}</div>
|
|
{% endspaceless %}
|
|
</div>
|
|
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body overflow-x-auto">
|
|
{% if transaction_rules %}
|
|
<c-config.search></c-config.search>
|
|
<div class="overflow-x-auto">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col" class="table-col-auto"></th>
|
|
<th scope="col" class="table-col-auto"></th>
|
|
<th scope="col" class="table-col-auto">{% translate 'Order' %}</th>
|
|
<th scope="col">{% translate 'Name' %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for rule in transaction_rules %}
|
|
<tr class="transaction_rule">
|
|
<td class="table-col-auto">
|
|
<div class="join" role="group" aria-label="{% translate 'Actions' %}">
|
|
<a class="btn btn-secondary btn-sm join-item"
|
|
role="button"
|
|
data-tippy-content="{% translate "View" %}"
|
|
hx-get="{% url 'transaction_rule_view' transaction_rule_id=rule.id %}"
|
|
hx-target="#persistent-generic-offcanvas-left">
|
|
<i class="fa-solid fa-eye fa-fw"></i></a>
|
|
{% if not rule.owner %}
|
|
<a class="btn btn-secondary btn-sm join-item"
|
|
role="button"
|
|
data-tippy-content="{% translate "Take ownership" %}"
|
|
hx-get="{% url 'transaction_rule_take_ownership' transaction_rule_id=rule.id %}">
|
|
<i class="fa-solid fa-crown fa-fw"></i></a>
|
|
{% endif %}
|
|
{% if user == rule.owner %}
|
|
<a class="btn btn-secondary btn-sm join-item"
|
|
role="button"
|
|
hx-target="#generic-offcanvas"
|
|
hx-swap="innerHTML"
|
|
data-tippy-content="{% translate "Share" %}"
|
|
hx-get="{% url 'transaction_rule_share_settings' pk=rule.id %}">
|
|
<i class="fa-solid fa-share fa-fw"></i></a>
|
|
{% endif %}
|
|
<a class="btn btn-error btn-sm join-item"
|
|
role="button"
|
|
data-tippy-content="{% translate "Delete" %}"
|
|
hx-delete="{% url 'transaction_rule_delete' transaction_rule_id=rule.id %}"
|
|
hx-trigger='confirmed'
|
|
data-bypass-on-ctrl="true"
|
|
data-title="{% translate "Are you sure?" %}"
|
|
data-text="{% translate "You won't be able to revert this!" %}"
|
|
data-confirm-text="{% translate "Yes, delete it!" %}"
|
|
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
|
|
</div>
|
|
</td>
|
|
<td class="table-col-auto">
|
|
{% if not rule.owner or user == rule.owner %}
|
|
<a class="no-underline cursor-pointer"
|
|
role="button"
|
|
data-tippy-content="
|
|
{% if rule.active %}{% translate "Deactivate" %}{% else %}{% translate "Activate" %}{% endif %}"
|
|
hx-get="{% url 'transaction_rule_toggle_activity' transaction_rule_id=rule.id %}">
|
|
{% if rule.active %}<i class="fa-solid fa-toggle-on text-success"></i>{% else %}
|
|
<i class="fa-solid fa-toggle-off text-error"></i>{% endif %}
|
|
</a>
|
|
{% else %}
|
|
<span data-tippy-content="{% translate "Only the owner can change this" %}">
|
|
{% if rule.active %}<i class="fa-solid fa-toggle-on text-success opacity-50"></i>{% else %}
|
|
<i class="fa-solid fa-toggle-off text-error opacity-50"></i>{% endif %}
|
|
</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="table-col-auto text-center">
|
|
<div>{{ rule.order }}</div>
|
|
</td>
|
|
<td>
|
|
<div>{{ rule.name }}</div>
|
|
<div class="text-gray-400">{{ rule.description }}</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<c-msg.empty title="{% translate "No rules" %}" remove-padding></c-msg.empty>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|