Files
summercms/modules/cabana/action_extensions_postgres_test.go
Jakub Zych 068adaabf9 fix(19.6.1-04): refuse code-declared role capture and wrap Administrators Runs
- Roles create and update refuse a submitted code that code-declared grants attach to
- compileActionExtensions wraps contributed Administrators Runs with guardOutranked
- Prove both holes on Postgres; keep the boot smoke callable with a superuser principal

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 13:29:38 +02:00

92 lines
4.7 KiB
Go

package cabana_test
import (
"net/http"
"testing"
"git.golem15.com/golem15/summercms/modules/cabana"
)
// TestActionExtensionRun (D-14, D-15): a contributed record action on the
// Administrators screen is offered and runs only for principals holding both
// the screen's and the action's permissions, answers 409 once it no longer
// applies, and runs in the row-locked transaction with the record.
func TestActionExtensionRun(t *testing.T) {
env := newScreensEnv(t)
both := env.seedAdmin(t, adminSeed{Name: "ext-both", RolePermissions: map[string]int{cabana.PermissionManageUsers: 1, pingPermission: 1}})
bothToken := env.login(t, both.Login)
screenOnly := env.seedAdmin(t, adminSeed{Name: "ext-screen", RolePermissions: manageUsers})
screenToken := env.login(t, screenOnly.Login)
actionOnly := env.seedAdmin(t, adminSeed{Name: "ext-action", RolePermissions: map[string]int{pingPermission: 1}})
actionToken := env.login(t, actionOnly.Login)
target := env.seedAdmin(t, adminSeed{Name: "ext-target"})
ping := userURL(target.ID, "actions", "acme_ping")
names := recordActionNames(env.expect(t, http.StatusOK, http.MethodGet, userURL(target.ID), bothToken, nil))
if len(names) == 0 || names[len(names)-1] != "acme_ping" || names[0] != "deactivate" {
t.Fatalf("offered to the holder of both = %v, want the owner's actions then acme_ping", names)
}
if names := recordActionNames(env.expect(t, http.StatusOK, http.MethodGet, userURL(target.ID), screenToken, nil)); containsName(names, "acme_ping") {
t.Fatalf("offered without %s = %v", pingPermission, names)
}
env.expectRefusal(t, http.StatusForbidden, http.MethodPost, ping, screenToken, map[string]any{}, "")
env.expectRefusal(t, http.StatusForbidden, http.MethodGet, userURL(target.ID), actionToken, nil, "")
env.expectRefusal(t, http.StatusForbidden, http.MethodPost, ping, actionToken, map[string]any{}, "")
if runs, _ := env.spy.runs(); len(runs) != 0 {
t.Fatalf("refused requests ran the action: %v", runs)
}
// Shown while it applies, then Applies turns false before the POST.
env.spy.refuse.Store(true)
if rec := env.call(t, http.MethodPost, ping, bothToken, map[string]any{}); rec.Code != http.StatusConflict {
t.Fatalf("POST after Applies turned false status=%d body=%s", rec.Code, rec.Body.String())
}
if names := recordActionNames(env.expect(t, http.StatusOK, http.MethodGet, userURL(target.ID), bothToken, nil)); containsName(names, "acme_ping") {
t.Fatalf("offered while it does not apply: %v", names)
}
env.spy.refuse.Store(false)
if runs, _ := env.spy.runs(); len(runs) != 0 {
t.Fatalf("a 409 ran the action: %v", runs)
}
result := dataOf(env.expect(t, http.StatusOK, http.MethodPost, ping, bothToken, map[string]any{}))
if result["message"] != "Pinged" {
t.Fatalf("result = %v", result)
}
runs, withTx := env.spy.runs()
if len(runs) != 1 || runs[0] != uint64(target.ID) || !withTx[0] {
t.Fatalf("runs = %v tx = %v, want one run on %d inside the transaction", runs, withTx, target.ID)
}
// A record the screen hides is not found for the action either.
hidden := env.seedAdmin(t, adminSeed{Name: "ext-hidden", Superuser: true})
env.expectRefusal(t, http.StatusNotFound, http.MethodPost, userURL(hidden.ID, "actions", "acme_ping"), bothToken, map[string]any{}, "")
}
// TestActionExtensionOutranked (WR-03, D-19): a contributed record action
// on an outranking Administrators target is 403 user.outranked and Run is
// not called. Built-in deactivate stays 403 on that target. A covered
// target still runs.
func TestActionExtensionOutranked(t *testing.T) {
env := newScreensEnv(t)
delegate := env.seedAdmin(t, adminSeed{Name: "extout-delegate", RolePermissions: map[string]int{cabana.PermissionManageUsers: 1, pingPermission: 1}})
delegateToken := env.login(t, delegate.Login)
target := env.seedAdmin(t, adminSeed{Name: "extout-target", RolePermissions: map[string]int{"acme.demo.access": 1}})
ping := userURL(target.ID, "actions", "acme_ping")
env.expectRefusal(t, http.StatusForbidden, http.MethodPost, ping, delegateToken, map[string]any{}, enOutranked)
if runs, _ := env.spy.runs(); len(runs) != 0 {
t.Fatalf("outranked acme_ping ran: %v", runs)
}
env.expectRefusal(t, http.StatusForbidden, http.MethodPost, userURL(target.ID, "actions", "deactivate"), delegateToken, map[string]any{}, enOutranked)
covered := env.seedAdmin(t, adminSeed{Name: "extout-covered"})
result := dataOf(env.expect(t, http.StatusOK, http.MethodPost, userURL(covered.ID, "actions", "acme_ping"), delegateToken, map[string]any{}))
if result["message"] != "Pinged" {
t.Fatalf("covered result = %v", result)
}
runs, withTx := env.spy.runs()
if len(runs) != 1 || runs[0] != uint64(covered.ID) || !withTx[0] {
t.Fatalf("runs = %v tx = %v, want one run on %d inside the transaction", runs, withTx, covered.ID)
}
}