fix: Reset 2FA keeps the privileged-group guard
Resetting the second factors of a privileged-group member now needs golem15.users.manage_privileged_groups, as changing their password or deleting them does; without it the action answers 403 before Reset.
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"git.golem15.com/golem15/sm-user-plugin/classes"
|
||||
"git.golem15.com/golem15/sm-user-plugin/models"
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
"git.golem15.com/golem15/summercms/modules/compass"
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -70,7 +71,10 @@ func resetAction(t *testing.T, app *backpack.App) pact.AdminRecordAction {
|
||||
// offered with the danger confirmation when one is published and reports a
|
||||
// second factor; hidden when it reports none; refused without
|
||||
// golem15.users.reset_two_factor; Run needs the action's transaction, runs
|
||||
// Reset in it, and Reset's error rolls the transaction back.
|
||||
// Reset in it, and Reset's error rolls the transaction back. A member of a
|
||||
// privileged group keeps the takeover guard: without
|
||||
// golem15.users.manage_privileged_groups Run answers 403 with the guard's
|
||||
// message before Reset.
|
||||
func TestResetTwoFactorAction(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Postgres")
|
||||
@@ -172,6 +176,50 @@ func TestResetTwoFactorAction(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("privileged_member_needs_the_privileged_permission", func(t *testing.T) {
|
||||
code := "rta-priv-" + adminStamp()
|
||||
env := newAdminEnv(t, func(cfg *compass.Config) {
|
||||
if err := cfg.Set("golem15.user.privileged_groups", []string{"admin", code}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
svc := &scriptedTwoFactorAdmin{has: true}
|
||||
if err := env.app.Publish[classes.TwoFactorAdmin](svc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
limited := env.admin(t, permAccessUsers, permResetTwoFactor)
|
||||
manager := env.admin(t, permAccessUsers, permResetTwoFactor, permManagePrivileged)
|
||||
member := env.seedUser(t, "rta-privileged", nil)
|
||||
env.join(t, member, env.seedGroup(t, "Privileged "+code, code))
|
||||
ordinary := env.seedUser(t, "rta-ordinary", nil)
|
||||
|
||||
rec := env.expect(t, http.StatusForbidden, http.MethodPost, userPath(member.ID, "/actions/reset_two_factor"), limited, map[string]any{})
|
||||
answer := adminDecode(t, rec)
|
||||
const want = "This user belongs to a privileged group. Resetting their two-factor authentication needs the permission to manage privileged groups. Nothing was changed."
|
||||
if answer.Error.Code != "forbidden" || answer.Error.Message != want {
|
||||
t.Fatalf("refusal = %s", rec.Body.String())
|
||||
}
|
||||
if len(svc.calls()) != 0 {
|
||||
t.Fatalf("a refused reset reached Reset: %v", svc.calls())
|
||||
}
|
||||
if name := env.reload(t, member.ID).Name; name == nil || *name != "rta-privileged" {
|
||||
t.Fatalf("the refused reset changed the member: name = %v", name)
|
||||
}
|
||||
// An ordinary user stays resettable with the reset permission alone.
|
||||
if !slices.Contains(env.offered(t, limited, ordinary.ID), "reset_two_factor") {
|
||||
t.Fatal("not offered for an ordinary user")
|
||||
}
|
||||
env.expect(t, http.StatusOK, http.MethodPost, userPath(ordinary.ID, "/actions/reset_two_factor"), limited, map[string]any{})
|
||||
// With the privileged permission the member is offered and reset.
|
||||
if !slices.Contains(env.offered(t, manager, member.ID), "reset_two_factor") {
|
||||
t.Fatal("not offered to an administrator who manages privileged groups")
|
||||
}
|
||||
env.expect(t, http.StatusOK, http.MethodPost, userPath(member.ID, "/actions/reset_two_factor"), manager, map[string]any{})
|
||||
if calls := svc.calls(); len(calls) != 2 || calls[0] != ordinary.ID || calls[1] != member.ID {
|
||||
t.Fatalf("Reset calls = %v", calls)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("run_error_rolls_back", func(t *testing.T) {
|
||||
env := newAdminEnv(t)
|
||||
svc := &scriptedTwoFactorAdmin{has: true, resetErr: errors.New("credential delete failed")}
|
||||
|
||||
Reference in New Issue
Block a user