Plugins: names are assumed unique across kinds, but nothing enforces it and the reference plugin breaks it #593

Closed
opened 2026-10-01 10:48:08 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d, by checking what the policy, the logo cache and the plugin forms key on, and rendering a source and a notifier with the same name in a test.

Checked again on 2026-10-01 against main at 0a5cad587: still present. The code is now at plugins/policy.py:158-164 (decide) and plugins/policy.py:277-305 (overview rows), accounts/settings_views.py:251-256 (set_choice) and core/server_views.py:1045-1101 (_policy_rows); plugins/registry.py:314-325, plugins/logos.py:120-142, the template and postulo-helloworld are where they were.

Policy rows, memoised decisions, the logo cache and URL, and both plugin forms key on plugin.name alone, and registry.find_any() says "A name is unique across the instance". Nothing checks it: the registry and the published postulo.plugins.testing check accept two plugins of different kinds with the same name. postulo-helloworld, the reference plugin third parties are told to copy, registers a source and a notifier both called helloworld.

  • Where: plugins/registry.py:314-325 (find_any and its docstring); plugins/policy.py:167-173 (decide, memoised on (plugin_name, person)); plugins/policy.py:286-314 (overview rows); accounts/settings_views.py:249-254 (set_choice(request.user, row["name"], on=row["name"] in wanted)); templates/settings/plugins.html:64 and :87 (id="plugin-{{ row.name }}", for="plugin-{{ row.name }}"); core/server_views.py:1044-1100 (_policy_rows); plugins/logos.py:120-142 (_cache keyed by name). In postulo-helloworld: src/postulo_helloworld/source.py:92 and src/postulo_helloworld/notifier.py:105, both name = "helloworld".
  • What happens: with postulo-helloworld installed, Settings → Plugins draws two rows named helloworld, with two checkboxes sharing id="plugin-helloworld" (the second label switches the first box). Unticking one of them does nothing: the other still posts on=helloworld, and both rows are saved as on. A test with a source and a notifier both named dup got two rows, and after ticking only one, decide("dup", user).on was True for both. One policy row, one instance-level switch and one logo govern both plugins, and the logo URL always resolves to whichever kind find_any meets first.
  • Why it matters: a person or an administrator cannot switch one of the two off without the other, the page has duplicate ids (WCAG 4.1.1/1.3.1 for the label association), and a third party copying the reference will ship the same collision.
  • Fix: enforce the rule: at load time log and skip a plugin whose name another kind already holds, and make postulo.plugins.testing fail on it; or key policy rows, form fields and logo URLs on kind and name together. Either way, rename one of postulo-helloworld's two plugins so the reference shows the rule.

Done when

  • A test in tests/test_plugin_policy.py that registers a source and a notifier with the same name and asserts the second is refused (or that the person's page renders two distinct ids and switching one leaves the other on).
Found on 2026-09-29 in a read-only review of postulo at 95348908d, by checking what the policy, the logo cache and the plugin forms key on, and rendering a source and a notifier with the same name in a test. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The code is now at `plugins/policy.py:158-164` (`decide`) and `plugins/policy.py:277-305` (`overview` rows), `accounts/settings_views.py:251-256` (`set_choice`) and `core/server_views.py:1045-1101` (`_policy_rows`); `plugins/registry.py:314-325`, `plugins/logos.py:120-142`, the template and postulo-helloworld are where they were. Policy rows, memoised decisions, the logo cache and URL, and both plugin forms key on `plugin.name` alone, and `registry.find_any()` says "A name is unique across the instance". Nothing checks it: the registry and the published `postulo.plugins.testing` check accept two plugins of different kinds with the same name. `postulo-helloworld`, the reference plugin third parties are told to copy, registers a source and a notifier both called `helloworld`. - **Where:** `plugins/registry.py:314-325` (`find_any` and its docstring); `plugins/policy.py:167-173` (`decide`, memoised on `(plugin_name, person)`); `plugins/policy.py:286-314` (`overview` rows); `accounts/settings_views.py:249-254` (`set_choice(request.user, row["name"], on=row["name"] in wanted)`); `templates/settings/plugins.html:64` and `:87` (`id="plugin-{{ row.name }}"`, `for="plugin-{{ row.name }}"`); `core/server_views.py:1044-1100` (`_policy_rows`); `plugins/logos.py:120-142` (`_cache` keyed by name). In postulo-helloworld: `src/postulo_helloworld/source.py:92` and `src/postulo_helloworld/notifier.py:105`, both `name = "helloworld"`. - **What happens:** with postulo-helloworld installed, *Settings → Plugins* draws two rows named `helloworld`, with two checkboxes sharing `id="plugin-helloworld"` (the second label switches the first box). Unticking one of them does nothing: the other still posts `on=helloworld`, and both rows are saved as on. A test with a source and a notifier both named `dup` got two rows, and after ticking only one, `decide("dup", user).on` was `True` for both. One policy row, one instance-level switch and one logo govern both plugins, and the logo URL always resolves to whichever kind `find_any` meets first. - **Why it matters:** a person or an administrator cannot switch one of the two off without the other, the page has duplicate ids (WCAG 4.1.1/1.3.1 for the label association), and a third party copying the reference will ship the same collision. - **Fix:** enforce the rule: at load time log and skip a plugin whose name another kind already holds, and make `postulo.plugins.testing` fail on it; or key policy rows, form fields and logo URLs on kind and name together. Either way, rename one of postulo-helloworld's two plugins so the reference shows the rule. ## Done when - A test in `tests/test_plugin_policy.py` that registers a source and a notifier with the same name and asserts the second is refused (or that the person's page renders two distinct ids and switching one leaves the other on).
tiagoagueda added this to the 0.6.0 milestone 2026-10-01 10:48:08 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#593
No description provided.