From 3fc16e201a30d882691c922a1f2f9718fe7d6a0d Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Fri, 16 Oct 2026 03:04:45 +0200 Subject: [PATCH] fix(auth): lock the default Standard and Read-Only user groups Migration 000125 sets groups.system_managed = TRUE for the seeded Standard Users (...0005) and Read-Only Users (...0006) groups, so the system-managed guard (ErrSystemManagedGroup) refuses API edits and deletes of them like the other seeded groups. Owner decision 2026-10-16 on issue #168. Locking is by id: a group an admin already renamed is locked as-is, permissions untouched. Co-Authored-By: Claude Opus 5.5 --- .../000125_lock_default_user_groups.down.sql | 6 ++ .../000125_lock_default_user_groups.up.sql | 12 ++++ .../000125_lock_default_user_groups_test.go | 56 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 internal/database/postgres/migrations/000125_lock_default_user_groups.down.sql create mode 100644 internal/database/postgres/migrations/000125_lock_default_user_groups.up.sql create mode 100644 internal/database/postgres/migrations/000125_lock_default_user_groups_test.go diff --git a/internal/database/postgres/migrations/000125_lock_default_user_groups.down.sql b/internal/database/postgres/migrations/000125_lock_default_user_groups.down.sql new file mode 100644 index 00000000..ca42c1f5 --- /dev/null +++ b/internal/database/postgres/migrations/000125_lock_default_user_groups.down.sql @@ -0,0 +1,6 @@ +UPDATE groups +SET system_managed = FALSE +WHERE id IN ( + '00000000-0000-5000-8000-000000000005', + '00000000-0000-5000-8000-000000000006' +); diff --git a/internal/database/postgres/migrations/000125_lock_default_user_groups.up.sql b/internal/database/postgres/migrations/000125_lock_default_user_groups.up.sql new file mode 100644 index 00000000..4faf9643 --- /dev/null +++ b/internal/database/postgres/migrations/000125_lock_default_user_groups.up.sql @@ -0,0 +1,12 @@ +-- Lock the seeded Standard Users and Read-Only Users groups (issue #168). +-- +-- Owner decision 2026-10-16: these default groups are system-managed, so the +-- #1737 guard (ErrSystemManagedGroup) refuses API edits and deletes, like +-- Administrators, Purchaser and RI Exchanger. Locking is by id, so a group an +-- admin already renamed or edited is locked as-is; permissions are untouched. +UPDATE groups +SET system_managed = TRUE +WHERE id IN ( + '00000000-0000-5000-8000-000000000005', -- Standard Users (000057) + '00000000-0000-5000-8000-000000000006' -- Read-Only Users (000057) +); diff --git a/internal/database/postgres/migrations/000125_lock_default_user_groups_test.go b/internal/database/postgres/migrations/000125_lock_default_user_groups_test.go new file mode 100644 index 00000000..f64fc280 --- /dev/null +++ b/internal/database/postgres/migrations/000125_lock_default_user_groups_test.go @@ -0,0 +1,56 @@ +//go:build integration + +package migrations_test + +import ( + "context" + "testing" + + "github.com/LeanerCloud/cloud-commitments-platform/internal/database/postgres/migrations" + "github.com/LeanerCloud/cloud-commitments-platform/internal/database/postgres/testhelpers" + "github.com/stretchr/testify/require" +) + +func TestMigration_LockDefaultUserGroups(t *testing.T) { + ctx := context.Background() + container := testhelpers.RequirePostgresContainer(ctx, t) + t.Cleanup(func() { require.NoError(t, container.Cleanup(context.Background())) }) + pool, path := container.DB.Pool(), getMigrationsPath() + ids := []string{ + "00000000-0000-5000-8000-000000000005", // Standard Users + "00000000-0000-5000-8000-000000000006", // Read-Only Users + } + const otherID = "00000000-0000-5000-8000-000000000007" // Purchaser, already locked by its own seed + + locked := func(id string) bool { + var v bool + require.NoError(t, pool.QueryRow(ctx, `SELECT system_managed FROM groups WHERE id=$1`, id).Scan(&v)) + return v + } + + require.NoError(t, migrations.MigrateToVersion(ctx, pool, path, 124)) + for _, id := range ids { + require.False(t, locked(id), "before 000125 %s must be editable", id) + } + // An admin-renamed group is still locked by id, with its permissions kept. + _, err := pool.Exec(ctx, `UPDATE groups SET name='Renamed Standard' WHERE id=$1`, ids[0]) + require.NoError(t, err) + var permsBefore string + require.NoError(t, pool.QueryRow(ctx, `SELECT permissions::text FROM groups WHERE id=$1`, ids[0]).Scan(&permsBefore)) + + require.NoError(t, migrations.MigrateToVersion(ctx, pool, path, 125)) + for _, id := range ids { + require.True(t, locked(id), "after 000125 %s must be system-managed", id) + } + var name, permsAfter string + require.NoError(t, pool.QueryRow(ctx, `SELECT name, permissions::text FROM groups WHERE id=$1`, ids[0]).Scan(&name, &permsAfter)) + require.Equal(t, "Renamed Standard", name) + require.Equal(t, permsBefore, permsAfter) + require.True(t, locked(otherID), "Purchaser must stay locked") + + require.NoError(t, migrations.MigrateToVersion(ctx, pool, path, 124)) + for _, id := range ids { + require.False(t, locked(id), "down must restore %s", id) + } + require.True(t, locked(otherID), "down must not unlock groups it did not lock") +}