🚧 Add role deletion
This commit is contained in:
@@ -135,7 +135,18 @@ def client(db_session, monkeypatch):
|
|||||||
return list(self.roles.values())
|
return list(self.roles.values())
|
||||||
|
|
||||||
def delete_role(self, rolename):
|
def delete_role(self, rolename):
|
||||||
|
# Mirrors the real broker (roles.c role__remove_all_clients /
|
||||||
|
# role__remove_all_groups): deleteRole strips the role from
|
||||||
|
# every client and group that holds it.
|
||||||
self.roles.pop(rolename, None)
|
self.roles.pop(rolename, None)
|
||||||
|
for client in self.clients.values():
|
||||||
|
client["roles"] = [
|
||||||
|
r for r in client.get("roles", []) if r["rolename"] != rolename
|
||||||
|
]
|
||||||
|
for group in self.groups.values():
|
||||||
|
group["roles"] = [
|
||||||
|
r for r in group.get("roles", []) if r["rolename"] != rolename
|
||||||
|
]
|
||||||
|
|
||||||
def set_role_acls(self, rolename, acls):
|
def set_role_acls(self, rolename, acls):
|
||||||
self.roles[rolename]["acls"] = acls
|
self.roles[rolename]["acls"] = acls
|
||||||
|
|||||||
@@ -198,7 +198,27 @@ def test_group_role_assignment(admin_client):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_listener_management_and_preview(admin_client):
|
def test_role_deletion(admin_client):
|
||||||
|
"""deleteRole removes the role everywhere (spec section 13): the broker's
|
||||||
|
dynsec deleteRole strips it from every client and group that holds it."""
|
||||||
|
admin_client.post("/api/v1/mqtt/roles", json={"rolename": "obsolete"})
|
||||||
|
admin_client.post("/api/v1/mqtt/clients", json={"username": "ps042"})
|
||||||
|
admin_client.post("/api/v1/mqtt/clients/ps042/roles", json={"rolename": "obsolete"})
|
||||||
|
admin_client.post("/api/v1/mqtt/groups", json={"groupname": "legacy"})
|
||||||
|
admin_client.post("/api/v1/mqtt/groups/legacy/roles", json={"rolename": "obsolete"})
|
||||||
|
|
||||||
|
deleted = admin_client.delete("/api/v1/mqtt/roles/obsolete")
|
||||||
|
assert deleted.status_code == 204
|
||||||
|
|
||||||
|
# Role gone from the listing...
|
||||||
|
roles = {r["rolename"] for r in admin_client.get("/api/v1/mqtt/roles").json()}
|
||||||
|
assert "obsolete" not in roles
|
||||||
|
# ...and the broker's cleanup means clients/groups no longer hold it.
|
||||||
|
client = admin_client.get("/api/v1/mqtt/clients/ps042").json()
|
||||||
|
assert all(r["rolename"] != "obsolete" for r in client["roles"])
|
||||||
|
groups = admin_client.get("/api/v1/mqtt/groups").json()
|
||||||
|
legacy = next(g for g in groups if g["groupname"] == "legacy")
|
||||||
|
assert all(r["rolename"] != "obsolete" for r in legacy["roles"])
|
||||||
response = admin_client.post(
|
response = admin_client.post(
|
||||||
"/api/v1/broker/listeners",
|
"/api/v1/broker/listeners",
|
||||||
json={"name": "Production MQTT", "port": 1883, "protocol": "mqtt"},
|
json={"name": "Production MQTT", "port": 1883, "protocol": "mqtt"},
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ services:
|
|||||||
- mosquitto_log:/mosquitto/log
|
- mosquitto_log:/mosquitto/log
|
||||||
networks:
|
networks:
|
||||||
- backend-net
|
- backend-net
|
||||||
|
ports:
|
||||||
|
- "${MQTT_PORT:-1883}:1883"
|
||||||
|
- "${MQTTS_PORT:-8883}:8883"
|
||||||
# Release 1 ships an internal-only, plaintext listener on the backend
|
# Release 1 ships an internal-only, plaintext listener on the backend
|
||||||
# network for the management channel; production listeners are added
|
# network for the management channel; production listeners are added
|
||||||
# through the platform (spec §60). Nothing is published to the host.
|
# through the platform (spec §60). Nothing is published to the host.
|
||||||
|
|||||||
@@ -91,6 +91,28 @@ export function RolesPage() {
|
|||||||
onError: (e: Error) => setError(e.message),
|
onError: (e: Error) => setError(e.message),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const remove = useMutation({
|
||||||
|
mutationFn: (rolename: string) => api.delete(`/mqtt/roles/${rolename}`),
|
||||||
|
onSuccess: (_data, rolename) => {
|
||||||
|
// The broker's deleteRole removes the role from every client and
|
||||||
|
// group that holds it; refresh those lists so chips disappear.
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["roles"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["clients"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["groups"] });
|
||||||
|
setBanner(
|
||||||
|
`Role ${rolename} deleted and removed from all clients and groups.`,
|
||||||
|
);
|
||||||
|
setError(null);
|
||||||
|
// Close the editor if the deleted role was open — the detail query
|
||||||
|
// would otherwise 404 against a role that no longer exists.
|
||||||
|
if (selected === rolename) {
|
||||||
|
setSelected(null);
|
||||||
|
setDirty(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (e: Error) => setError(e.message),
|
||||||
|
});
|
||||||
|
|
||||||
const submit = (e: FormEvent) => {
|
const submit = (e: FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (name) create.mutate(name);
|
if (name) create.mutate(name);
|
||||||
@@ -176,7 +198,21 @@ export function RolesPage() {
|
|||||||
<tr key={r.rolename}>
|
<tr key={r.rolename}>
|
||||||
<td className="mono">{r.rolename}</td>
|
<td className="mono">{r.rolename}</td>
|
||||||
<td>
|
<td>
|
||||||
<button onClick={() => startEditing(r.rolename)}>Edit ACLs</button>
|
<button onClick={() => startEditing(r.rolename)}>Edit ACLs</button>{" "}
|
||||||
|
<button
|
||||||
|
className="danger"
|
||||||
|
onClick={() => {
|
||||||
|
if (
|
||||||
|
confirm(
|
||||||
|
`Delete role ${r.rolename}? It will be removed from ` +
|
||||||
|
"every client and group that currently holds it.",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
remove.mutate(r.rolename);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user