mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 23:53:33 +08:00
Public groups have always been bindable by every user: CanBindGroup returned true for any non-exclusive group, and user_allowed_groups only ever carried the exclusive groups an admin had granted. Admins had no way to hand a single user a subset of the public groups short of converting a group to exclusive, which changes it for everyone already using it. A user now carries restrict_public_groups. While it is false, which is the default and what every existing row migrates to, nothing changes: every public group stays bindable. Once an admin turns it on for a user, that user's public groups are narrowed to the ones listed in user_allowed_groups, the same table that already gates exclusive groups. The flag is an administrative control, so it rides on the admin user DTO only and leaves the shape of the end-user endpoints alone. The model plaza filter honours the flag as well, so a restricted user is not shown groups they would be refused when binding a key. Anonymous visitors have no user record and keep the previous view. Enforcement rides on the existing CanBindGroup choke point, so it covers key creation, key updates, and per-request authorization together. An API key bound to a group that is later withdrawn stops working at request time rather than lingering as a key that can be listed but not used. The admin dialog gains a toggle over the public group list. Turning it off re-checks every public group, so an admin cannot save a list that reads as restrictive while the restriction itself is disabled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
88 lines
2.3 KiB
Go
88 lines
2.3 KiB
Go
package service
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestUserCanBindGroup(t *testing.T) {
|
|
const (
|
|
publicGroupA int64 = 10
|
|
publicGroupB int64 = 11
|
|
exclusiveGroupA int64 = 20
|
|
)
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
user User
|
|
groupID int64
|
|
isExclusive bool
|
|
want bool
|
|
}{
|
|
{
|
|
name: "public group is bindable by default",
|
|
user: User{},
|
|
groupID: publicGroupA,
|
|
want: true,
|
|
},
|
|
{
|
|
name: "exclusive group needs an explicit grant",
|
|
user: User{},
|
|
groupID: exclusiveGroupA,
|
|
isExclusive: true,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "granted exclusive group is bindable",
|
|
user: User{AllowedGroups: []int64{exclusiveGroupA}},
|
|
groupID: exclusiveGroupA,
|
|
isExclusive: true,
|
|
want: true,
|
|
},
|
|
{
|
|
name: "an unrestricted user keeps every public group even with a grant list",
|
|
user: User{AllowedGroups: []int64{exclusiveGroupA}},
|
|
groupID: publicGroupA,
|
|
want: true,
|
|
},
|
|
{
|
|
name: "a restricted user keeps the public groups listed for them",
|
|
user: User{RestrictPublicGroups: true, AllowedGroups: []int64{publicGroupA}},
|
|
groupID: publicGroupA,
|
|
want: true,
|
|
},
|
|
{
|
|
name: "a restricted user loses the public groups not listed for them",
|
|
user: User{RestrictPublicGroups: true, AllowedGroups: []int64{publicGroupA}},
|
|
groupID: publicGroupB,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "a restricted user with no list keeps no public group",
|
|
user: User{RestrictPublicGroups: true},
|
|
groupID: publicGroupA,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "restricting public groups does not widen exclusive access",
|
|
user: User{RestrictPublicGroups: true, AllowedGroups: []int64{publicGroupA}},
|
|
groupID: exclusiveGroupA,
|
|
isExclusive: true,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "a restricted user can still hold both kinds of grant",
|
|
user: User{RestrictPublicGroups: true, AllowedGroups: []int64{publicGroupA, exclusiveGroupA}},
|
|
groupID: exclusiveGroupA,
|
|
isExclusive: true,
|
|
want: true,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
user := tc.user
|
|
require.Equal(t, tc.want, user.CanBindGroup(tc.groupID, tc.isExclusive))
|
|
})
|
|
}
|
|
}
|