feat: manage a user's groups from the user form, privileged groups locked
- groups relation field on the user form, the only writer of users_groups - UsersGroup pivot model - AdminRelationLocks: adding or removing a group whose code is in golem15.user.privileged_groups needs golem15.users.manage_privileged_groups, on create and on update; refused saves answer 403 and write nothing - TestAdminUserGroupsField and TestAdminPrivilegedGroups
This commit is contained in:
@@ -336,9 +336,15 @@ func (c usersAdminController) FormAfterCreate(ctx context.Context, model any) er
|
||||
return nil
|
||||
}
|
||||
|
||||
// AdminFieldRelations binds the organisation picker. organisation_id is a
|
||||
// protected column, so the contract opts in to writing it through this one
|
||||
// field; the submitted id is still checked against the options.
|
||||
// groupsField is the form's group membership field.
|
||||
const groupsField = "groups"
|
||||
|
||||
// AdminFieldRelations binds the organisation picker and the groups field.
|
||||
// organisation_id is a protected column, so the contract opts in to writing
|
||||
// it through this one field; the submitted id is still checked against the
|
||||
// options. groups is the users_groups pivot: a save that carries the field
|
||||
// replaces the user's memberships, and AdminRelationLocks decides which of
|
||||
// them the administrator may change.
|
||||
func (usersAdminController) AdminFieldRelations() []cabana.FieldRelationContract {
|
||||
return []cabana.FieldRelationContract{
|
||||
{
|
||||
@@ -349,9 +355,47 @@ func (usersAdminController) AdminFieldRelations() []cabana.FieldRelationContract
|
||||
LabelColumn: "name",
|
||||
WritableForeignKey: true,
|
||||
},
|
||||
{
|
||||
Field: groupsField,
|
||||
Kind: "belongsToMany",
|
||||
NewRelated: func() any { return &models.UserGroup{} },
|
||||
NewPivot: func() any { return &models.UsersGroup{} },
|
||||
ParentForeignKey: "user_id",
|
||||
RelatedForeignKey: "user_group_id",
|
||||
LabelColumn: "name",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// AdminRelationLocks locks the privileged groups in the groups field for an
|
||||
// administrator who lacks golem15.users.manage_privileged_groups: the
|
||||
// framework shows them locked and answers 403, before any row is written, to
|
||||
// a create or update that would add or remove one. Membership of a
|
||||
// privileged group makes a site administrator, so golem15.users.access_users
|
||||
// alone must not be able to grant or revoke it. The privileged codes come
|
||||
// from golem15.user.privileged_groups on every call and the group ids are
|
||||
// read through the write's transaction when there is one. An administrator
|
||||
// without a principal on the context gets the locks. Every other field has
|
||||
// none.
|
||||
func (c usersAdminController) AdminRelationLocks(ctx context.Context, field string) (cabana.RelationLock, error) {
|
||||
if field != groupsField {
|
||||
return cabana.RelationLock{}, nil
|
||||
}
|
||||
principal, _ := bouncer.User(ctx)
|
||||
if cabana.Allows(principal, []string{classes.PermissionManagePrivilegedGroups}) {
|
||||
return cabana.RelationLock{}, nil
|
||||
}
|
||||
db := requestDB(ctx, c.db)
|
||||
if db == nil {
|
||||
return cabana.RelationLock{}, errors.New("users admin: gorm handle is missing")
|
||||
}
|
||||
ids, err := classes.PrivilegedGroupIDs(ctx, db, classes.PrivilegedGroupCodes(c.config()))
|
||||
if err != nil {
|
||||
return cabana.RelationLock{}, err
|
||||
}
|
||||
return cabana.RelationLock{IDs: ids, Message: usersLang + "privileged_group_forbidden"}, nil
|
||||
}
|
||||
|
||||
// permissionsField is the form's permission editor field.
|
||||
const permissionsField = "permissions"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user