修复管理员用户列表成员版本缺失导致的多分组保存失败,并保留管理员不可管理分组的成员关系,补充回归测试。
This commit is contained in:
parent
342d0ab0a5
commit
4cf1de87d3
@ -125,14 +125,27 @@ func SetAccountGroupMembership(ctx *context.Context, w http.ResponseWriter, req
|
||||
writeAccountGroupError(w, accountgroup.ErrConflict)
|
||||
return
|
||||
}
|
||||
if err := accountgroup.SetMembership(
|
||||
db.Instance,
|
||||
user,
|
||||
input.GroupIDs,
|
||||
guards := []accountgroup.WriteGuard{
|
||||
accountWriteGuard(ctx, user),
|
||||
accountgroup.MembershipScopeGuard(ctx.UserID, ctx.EffectiveRole(), input.GroupIDs),
|
||||
); err != nil {
|
||||
writeAccountGroupError(w, err)
|
||||
}
|
||||
var setErr error
|
||||
if ctx.EffectiveRole() == access.RoleAdmin {
|
||||
visibleGroups, visibleErr := accountgroup.VisibleGroups(db.Instance, ctx.UserID, ctx.EffectiveRole())
|
||||
if visibleErr != nil {
|
||||
writeAccountGroupError(w, visibleErr)
|
||||
return
|
||||
}
|
||||
editableGroupIDs := make([]int, 0, len(visibleGroups))
|
||||
for _, group := range visibleGroups {
|
||||
editableGroupIDs = append(editableGroupIDs, group.ID)
|
||||
}
|
||||
setErr = accountgroup.SetScopedMembership(db.Instance, user, input.GroupIDs, editableGroupIDs, guards...)
|
||||
} else {
|
||||
setErr = accountgroup.SetMembership(db.Instance, user, input.GroupIDs, guards...)
|
||||
}
|
||||
if setErr != nil {
|
||||
writeAccountGroupError(w, setErr)
|
||||
return
|
||||
}
|
||||
InvalidateSecurityRuntimeUser(user.ID, user.Account)
|
||||
|
||||
@ -273,7 +273,9 @@ func UserList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
items := make([]userListItem, 0, len(users))
|
||||
memberships := map[int][]int{}
|
||||
if ctx.EffectiveRole() == access.RoleSuperAdmin {
|
||||
groupManager := ctx.EffectiveRole() == access.RoleSuperAdmin || ctx.EffectiveRole() == access.RoleAdmin
|
||||
visibleMemberships := map[int]struct{}{}
|
||||
if groupManager {
|
||||
ids := make([]int, 0, len(users))
|
||||
for _, user := range users {
|
||||
ids = append(ids, user.ID)
|
||||
@ -283,10 +285,30 @@ func UserList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
||||
writeAccountGroupError(w, err)
|
||||
return
|
||||
}
|
||||
if ctx.EffectiveRole() == access.RoleAdmin {
|
||||
visibleGroups, visibleErr := accountgroup.VisibleGroups(db.Instance, ctx.UserID, ctx.EffectiveRole())
|
||||
if visibleErr != nil {
|
||||
writeAccountGroupError(w, visibleErr)
|
||||
return
|
||||
}
|
||||
for _, group := range visibleGroups {
|
||||
visibleMemberships[group.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, user := range users {
|
||||
groupIDs := memberships[user.ID]
|
||||
if ctx.EffectiveRole() == access.RoleAdmin {
|
||||
filtered := make([]int, 0, len(groupIDs))
|
||||
for _, groupID := range groupIDs {
|
||||
if _, ok := visibleMemberships[groupID]; ok {
|
||||
filtered = append(filtered, groupID)
|
||||
}
|
||||
}
|
||||
groupIDs = filtered
|
||||
}
|
||||
items = append(items, userListItem{
|
||||
GroupIDs: memberships[user.ID],
|
||||
GroupIDs: groupIDs,
|
||||
ID: user.ID,
|
||||
Account: user.Account,
|
||||
Name: user.Name,
|
||||
@ -298,7 +320,7 @@ func UserList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
||||
ExternalEmailEnabled: user.ExternalEmailEnabled == 1,
|
||||
ExternalEmailConfigured: externalEmailConfigured[user.ID],
|
||||
})
|
||||
if ctx.EffectiveRole() == access.RoleSuperAdmin {
|
||||
if groupManager {
|
||||
items[len(items)-1].MembershipVersion = user.SecurityVersion
|
||||
}
|
||||
}
|
||||
|
||||
@ -296,6 +296,15 @@ func Guard(actorID int, role access.Role, sessionVersion, securityVersion int, t
|
||||
}
|
||||
|
||||
func SetMembership(engine *xorm.Engine, user models.User, input []int, guards ...WriteGuard) error {
|
||||
return setMembership(engine, user, input, nil, guards...)
|
||||
}
|
||||
|
||||
// SetScopedMembership 只替换可管理分组,保留目标账号在其他分组中的成员关系。
|
||||
func SetScopedMembership(engine *xorm.Engine, user models.User, input []int, editableGroupIDs []int, guards ...WriteGuard) error {
|
||||
return setMembership(engine, user, input, editableGroupIDs, guards...)
|
||||
}
|
||||
|
||||
func setMembership(engine *xorm.Engine, user models.User, input []int, editableGroupIDs []int, guards ...WriteGuard) error {
|
||||
if engine == nil || user.ID <= 0 {
|
||||
return ErrDenied
|
||||
}
|
||||
@ -323,6 +332,24 @@ func SetMembership(engine *xorm.Engine, user models.User, input []int, guards ..
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if editableGroupIDs != nil {
|
||||
editable := make(map[int]struct{}, len(editableGroupIDs))
|
||||
for _, groupID := range editableGroupIDs {
|
||||
editable[groupID] = struct{}{}
|
||||
}
|
||||
existing := make([]models.AccountGroupMember, 0)
|
||||
if err := tx.Where(`user_id = ?`, user.ID).Find(&existing); err != nil {
|
||||
return err
|
||||
}
|
||||
preserved := make([]int, 0, len(existing)+len(ids))
|
||||
for _, row := range existing {
|
||||
if _, ok := editable[row.GroupID]; !ok {
|
||||
preserved = append(preserved, row.GroupID)
|
||||
}
|
||||
}
|
||||
ids = append(preserved, ids...)
|
||||
sort.Ints(ids)
|
||||
}
|
||||
if err := ReplaceMembership(tx, user.ID, ids); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -135,3 +135,35 @@ func TestValidateMembershipKeepsAdminMultiGroupAndUserSingleGroupRules(t *testin
|
||||
t.Fatalf("普通用户不应允许多个分组,得到:%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetScopedMembershipPreservesGroupsOutsideAdminScope(t *testing.T) {
|
||||
engine, err := xorm.NewEngine(`sqlite`, `:memory:`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer engine.Close()
|
||||
if err := engine.Sync2(new(models.User), new(models.AccountGroup), new(models.AccountGroupMember)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
target := models.User{Account: `target`, Name: `Target`, Role: string(access.RoleUser), SessionVersion: 1, SecurityVersion: 1}
|
||||
if _, err := engine.Insert(&target); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := engine.Insert(&models.AccountGroup{ID: 1, Name: `visible`}, &models.AccountGroup{ID: 2, Name: `hidden`}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := engine.Insert(&models.AccountGroupMember{UserID: target.ID, GroupID: 1}, &models.AccountGroupMember{UserID: target.ID, GroupID: 2}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
allow := func(*xorm.Session) error { return nil }
|
||||
if err := SetScopedMembership(engine, target, []int{0}, []int{0, 1}, allow); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rows := make([]models.AccountGroupMember, 0)
|
||||
if err := engine.Where(`user_id = ?`, target.ID).Asc(`group_id`).Find(&rows); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(rows) != 2 || rows[0].GroupID != 0 || rows[1].GroupID != 2 {
|
||||
t.Fatalf(`scoped membership changed an inaccessible group: %+v`, rows)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user