Plugins from the command line: disable and remove skip the lock that keeps the mail transport on #595
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#595
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, reading the plugin install, disable and remove paths against the page that drives them.Checked again on 2026-10-01 against
mainat0a5cad587: still present. The code is now atcore/server_views.py:1469(PluginActionView._switch) andcore/server_views.py:1495-1497(PluginActionView._remove),plugins/installing.py:834(remove, which asksdata.refuse_removing(name)at :850) andplugins/installing.py:932(set_disabled);plugins/management/commands/plugins.py:99-119is where it was.The rule from #104 is that the package carrying the selected mail transport cannot be switched off or removed while mail is the last way back into accounts. That rule is checked only in the view behind Server settings → Plugins.
installing.set_disabled()andinstalling.remove()do not ask it, andmanage.py plugins disableandmanage.py plugins removecall those two directly. Writing a plugin says the command line "is the same code" as the page; for this lock it is not.core/server_views.py:1468(PluginActionView._switch) andcore/server_views.py:1494-1496(PluginActionView._remove) calltransport.refuse_removing_distribution(name);plugins/installing.py:1008(remove) asks onlydata.refuse_removing(name), andplugins/installing.py:1106(set_disabled) asks nothing;plugins/management/commands/plugins.py:99-119(_remove,_switch) callinstallingdirectly.manage.py plugins disable <that package>(orremove). The command succeeds. In every process the transport stops loading,transport.selected()falls back to the built-in SMTP transport, which cannot deliver on that host, and verification mail, password resets and sign-in links stop going out. The same request on the page is refused with "mail is the only way back into an account here".refuse_switching_offreturns nothing whensite.mail_delivers()is false), so the command line gains nothing as an escape hatch by skipping it.installing.remove()and intoinstalling.set_disabled()whendisabledis true, next to thedata.refuse_removingcheck thatremove()already calls "the last door", raisingInstallErrorwith the refusal.PluginActionView._switchand_removecan then drop their own copies (the data refusal in_removeis already a duplicate of the one inremove()).Done when
tests/test_mail_transport.py, withtransport._distribution_ofpatched as the existing page tests do, runscall_command("plugins", "disable", "postulo-smtp")andcall_command("plugins", "remove", "postulo-smtp")and expectsCommandErrorcarrying "only way back into an account", with the record unchanged.test_switching_off_the_package_that_carries_the_mail_is_refused,test_removing_the_package_that_carries_the_mail_is_refused) still pass once the view no longer checks for itself.