snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
732
server/system/service/user_group.go
Normal file
732
server/system/service/user_group.go
Normal file
@@ -0,0 +1,732 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/actionlog"
|
||||
"github.com/cortezaproject/corteza/server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza/server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza/server/pkg/handle"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
"github.com/cortezaproject/corteza/server/pkg/label"
|
||||
"github.com/cortezaproject/corteza/server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/cortezaproject/corteza/server/system/service/event"
|
||||
"github.com/cortezaproject/corteza/server/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
userGroup struct {
|
||||
actionlog actionlog.Recorder
|
||||
|
||||
ac userGroupAccessController
|
||||
eventbus eventDispatcher
|
||||
rbac rbacUserGroupService
|
||||
|
||||
user UserService
|
||||
role RoleService
|
||||
|
||||
rootUserGroup id.ID
|
||||
|
||||
store store.Storer
|
||||
}
|
||||
|
||||
userGroupAccessController interface {
|
||||
CanGrant(context.Context) bool
|
||||
|
||||
CanSearchUserGroups(context.Context) bool
|
||||
CanCreateUserGroup(context.Context) bool
|
||||
CanReadUserGroup(context.Context, *types.UserGroup) bool
|
||||
CanUpdateUserGroup(context.Context, *types.UserGroup) bool
|
||||
CanDeleteUserGroup(context.Context, *types.UserGroup) bool
|
||||
CanManageMembersOnUserGroup(context.Context, *types.UserGroup) bool
|
||||
}
|
||||
|
||||
UserGroupService interface {
|
||||
FindByID(ctx context.Context, userGroupID uint64) (*types.UserGroup, error)
|
||||
FindByHandle(ctx context.Context, handle string) (*types.UserGroup, error)
|
||||
Find(context.Context, types.UserGroupFilter) (types.UserGroupSet, types.UserGroupFilter, error)
|
||||
|
||||
Create(ctx context.Context, userGroup *types.UserGroup) (*types.UserGroup, error)
|
||||
Update(ctx context.Context, userGroup *types.UserGroup) (*types.UserGroup, error)
|
||||
|
||||
Delete(ctx context.Context, ID uint64) error
|
||||
Undelete(ctx context.Context, ID uint64) error
|
||||
|
||||
MemberList(ctx context.Context, userGroupID uint64) (types.UserSet, error)
|
||||
MemberAdd(ctx context.Context, userGroupID, userID uint64) error
|
||||
}
|
||||
|
||||
eventbusUserGroupChangeRegistry interface {
|
||||
Register(eventbus.HandlerFn, ...eventbus.HandlerRegOp) uintptr
|
||||
}
|
||||
|
||||
rbacUserGroupService interface {
|
||||
UpdateUserGroups(rr ...rbac.GroupMembers) (err error)
|
||||
AssignGroupMembers(group id.ID, members ...id.ID) (err error)
|
||||
|
||||
AddNode(id id.ID, handle string, paths ...rbac.GroupNodePath) (err error)
|
||||
UpdateNode(id id.ID, handle string, paths ...rbac.GroupNodePath) (err error)
|
||||
RemoveNode(id id.ID) (err error)
|
||||
}
|
||||
|
||||
userGroupAuth interface {
|
||||
RemoveAccessTokens(context.Context, *types.User) error
|
||||
}
|
||||
)
|
||||
|
||||
func UserGroup(rbac rbacUserGroupService) *userGroup {
|
||||
return &userGroup{
|
||||
ac: DefaultAccessControl,
|
||||
eventbus: eventbus.Service(),
|
||||
rbac: rbac,
|
||||
|
||||
actionlog: DefaultActionlog,
|
||||
|
||||
user: DefaultUser,
|
||||
role: DefaultRole,
|
||||
store: DefaultStore,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *userGroup) Activate(ctx context.Context) (err error) {
|
||||
gMembers := []rbac.GroupMembers{}
|
||||
|
||||
groups, _, err := svc.Find(ctx, types.UserGroupFilter{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, g := range groups {
|
||||
if len(g.Config.Paths) == 0 {
|
||||
svc.rootUserGroup = id.MustNumID(g.ID)
|
||||
}
|
||||
|
||||
roles, _, err := svc.role.Find(ctx, types.RoleFilter{
|
||||
Resource: fmt.Sprintf("corteza::system:user-group/%d", g.ID),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var mm types.UserSet
|
||||
mm, err = svc.MemberList(ctx, g.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var rr []id.ID
|
||||
for _, r := range roles {
|
||||
rr = append(rr, id.MustNumID(r.ID))
|
||||
}
|
||||
|
||||
members := make([]id.ID, len(mm))
|
||||
for i, m := range mm {
|
||||
members[i] = id.MustNumID(m.ID)
|
||||
}
|
||||
|
||||
pp := []rbac.GroupNodePath{}
|
||||
for _, p := range g.Config.Paths {
|
||||
pp = append(pp, rbac.GroupNodePath{
|
||||
SelfID: id.MustNumID(p.SelfID),
|
||||
Name: p.Name,
|
||||
})
|
||||
}
|
||||
|
||||
// @todo we need to reload this on specific changes
|
||||
gMembers = append(gMembers, rbac.ConvUserGroup(id.MustNumID(g.ID), g.Handle, members, rr, pp))
|
||||
}
|
||||
|
||||
err = svc.rbac.UpdateUserGroups(gMembers...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc *userGroup) Find(ctx context.Context, filter types.UserGroupFilter) (rr types.UserGroupSet, f types.UserGroupFilter, err error) {
|
||||
var (
|
||||
raProps = &userGroupActionProps{filter: &filter}
|
||||
)
|
||||
|
||||
// For each fetched item, store backend will check if it is valid or not
|
||||
filter.Check = func(res *types.UserGroup) (bool, error) {
|
||||
if !svc.ac.CanReadUserGroup(ctx, res) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
if !svc.ac.CanSearchUserGroups(ctx) {
|
||||
return UserGroupErrNotAllowedToSearch()
|
||||
}
|
||||
|
||||
if filter.Deleted > 0 {
|
||||
// If list with deleted or suspended users is requested
|
||||
// user must have access permissions to system (ie: is admin)
|
||||
//
|
||||
// not the best solution but ATM it allows us to have at least
|
||||
// some kind of control over who can see deleted or archived userGroups
|
||||
//if !svc.ac.CanAccess(ctx) {
|
||||
// return UserGroupErrNotAllowedToListUserGroups()
|
||||
//}
|
||||
}
|
||||
|
||||
if len(filter.Labels) > 0 {
|
||||
filter.LabeledIDs, err = label.Search(
|
||||
ctx,
|
||||
svc.store,
|
||||
types.UserGroup{}.LabelResourceKind(),
|
||||
filter.Labels,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// labels specified but no labeled resources found
|
||||
if len(filter.LabeledIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if rr, f, err = store.SearchUserGroups(ctx, svc.store, filter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, r := range rr {
|
||||
r.IsRoot = id.MustNumID(r.ID).Equal(svc.rootUserGroup)
|
||||
}
|
||||
|
||||
if err = label.Load(ctx, svc.store, toLabeledUserGroups(rr)...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
return rr, f, svc.recordAction(ctx, raProps, UserGroupActionSearch, err)
|
||||
}
|
||||
|
||||
func (svc *userGroup) FindByID(ctx context.Context, userGroupID uint64) (r *types.UserGroup, err error) {
|
||||
var (
|
||||
raProps = &userGroupActionProps{userGroup: &types.UserGroup{ID: userGroupID}}
|
||||
)
|
||||
|
||||
err = func() error {
|
||||
if r, err = svc.findByID(ctx, userGroupID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.IsRoot = id.MustNumID(r.ID).Equal(svc.rootUserGroup)
|
||||
|
||||
raProps.setUserGroup(r)
|
||||
return nil
|
||||
}()
|
||||
|
||||
return r, svc.recordAction(ctx, raProps, UserGroupActionLookup, err)
|
||||
}
|
||||
|
||||
func (svc *userGroup) findByID(ctx context.Context, userGroupID uint64) (*types.UserGroup, error) {
|
||||
r, err := loadUserGroup(ctx, svc.store, userGroupID)
|
||||
return svc.proc(ctx, r, err)
|
||||
}
|
||||
|
||||
func (svc *userGroup) FindByHandle(ctx context.Context, h string) (r *types.UserGroup, err error) {
|
||||
var (
|
||||
raProps = &userGroupActionProps{userGroup: &types.UserGroup{Handle: h}}
|
||||
)
|
||||
|
||||
err = func() error {
|
||||
r, err = store.LookupUserGroupByHandle(ctx, svc.store, h)
|
||||
if r, err = svc.proc(ctx, r, err); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
raProps.setUserGroup(r)
|
||||
return nil
|
||||
}()
|
||||
|
||||
return r, svc.recordAction(ctx, raProps, UserGroupActionLookup, err)
|
||||
}
|
||||
|
||||
// FindByAny finds userGroup by given identifier (id, handle, name)
|
||||
func (svc *userGroup) FindByAny(ctx context.Context, identifier interface{}) (r *types.UserGroup, err error) {
|
||||
if ID, ok := identifier.(uint64); ok {
|
||||
return svc.FindByID(ctx, ID)
|
||||
} else if strIdentifier, ok := identifier.(string); ok {
|
||||
if ID, _ := strconv.ParseUint(strIdentifier, 10, 64); ID > 0 {
|
||||
return svc.FindByID(ctx, ID)
|
||||
} else {
|
||||
r, err = svc.FindByHandle(ctx, strIdentifier)
|
||||
return r, err
|
||||
}
|
||||
} else {
|
||||
return nil, UserGroupErrInvalidID()
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *userGroup) proc(ctx context.Context, r *types.UserGroup, err error) (*types.UserGroup, error) {
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return nil, UserGroupErrNotFound()
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = label.Load(ctx, svc.store, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (svc *userGroup) Create(ctx context.Context, new *types.UserGroup) (r *types.UserGroup, err error) {
|
||||
var (
|
||||
raProps = &userGroupActionProps{userGroup: new}
|
||||
)
|
||||
|
||||
err = func() (err error) {
|
||||
if !handle.IsValid(new.Handle) {
|
||||
return UserGroupErrInvalidHandle()
|
||||
}
|
||||
|
||||
if !svc.checkPaths(new) {
|
||||
return UserGroupErrInvalidSelfID()
|
||||
}
|
||||
|
||||
if !svc.isValidStructure(ctx, new) {
|
||||
return UserGroupErrInvalidUpdateStructure()
|
||||
}
|
||||
|
||||
if !svc.ac.CanCreateUserGroup(ctx) {
|
||||
return UserGroupErrNotAllowedToCreate()
|
||||
}
|
||||
|
||||
if err = svc.eventbus.WaitFor(ctx, event.UserGroupBeforeCreate(new, r)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = svc.UniqueCheck(ctx, new); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
new.ID = nextID()
|
||||
new.CreatedAt = *now()
|
||||
|
||||
raProps.setNew(new)
|
||||
|
||||
if err = store.CreateUserGroup(ctx, svc.store, new); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = label.Create(ctx, svc.store, new); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r = new
|
||||
|
||||
pp := []rbac.GroupNodePath{}
|
||||
for _, p := range new.Config.Paths {
|
||||
pp = append(pp, rbac.GroupNodePath{
|
||||
SelfID: id.MustNumID(p.SelfID),
|
||||
Name: p.Name,
|
||||
})
|
||||
}
|
||||
|
||||
err = svc.rbac.AddNode(id.MustNumID(new.ID), new.Handle, pp...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
svc.eventbus.Dispatch(ctx, event.UserGroupAfterCreate(new, r))
|
||||
return
|
||||
}()
|
||||
|
||||
return r, svc.recordAction(ctx, raProps, UserGroupActionCreate, err)
|
||||
|
||||
}
|
||||
|
||||
func (svc *userGroup) Update(ctx context.Context, upd *types.UserGroup) (r *types.UserGroup, err error) {
|
||||
var (
|
||||
raProps = &userGroupActionProps{update: upd}
|
||||
)
|
||||
|
||||
err = func() (err error) {
|
||||
if r, err = loadUserGroup(ctx, svc.store, upd.ID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
raProps.setUserGroup(r)
|
||||
|
||||
if !handle.IsValid(upd.Handle) {
|
||||
return UserGroupErrInvalidHandle()
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdateUserGroup(ctx, upd) {
|
||||
return UserGroupErrNotAllowedToUpdate()
|
||||
}
|
||||
|
||||
if len(upd.Config.Paths) == 0 {
|
||||
return UserGroupErrMissingSelfID()
|
||||
}
|
||||
|
||||
if !svc.checkSelfID(ctx, upd) {
|
||||
return UserGroupErrInvalidSelfID()
|
||||
}
|
||||
|
||||
if !svc.isValidStructure(ctx, upd) {
|
||||
return UserGroupErrInvalidUpdateStructure()
|
||||
}
|
||||
|
||||
// Test if stale (update has an older version of data)
|
||||
if isStale(upd.UpdatedAt, r.UpdatedAt, r.CreatedAt) {
|
||||
return UserGroupErrStaleData()
|
||||
}
|
||||
|
||||
if err = svc.eventbus.WaitFor(ctx, event.UserGroupBeforeUpdate(upd, r)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = svc.UniqueCheck(ctx, upd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.Handle = upd.Handle
|
||||
r.Meta = upd.Meta
|
||||
r.UpdatedAt = now()
|
||||
r.Config = upd.Config
|
||||
|
||||
// Assign changed values
|
||||
if err = store.UpdateUserGroup(ctx, svc.store, r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if label.Changed(r.Labels, upd.Labels) {
|
||||
if err = label.Update(ctx, svc.store, upd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.Labels = upd.Labels
|
||||
}
|
||||
|
||||
pp := []rbac.GroupNodePath{}
|
||||
for _, p := range r.Config.Paths {
|
||||
pp = append(pp, rbac.GroupNodePath{
|
||||
SelfID: id.MustNumID(p.SelfID),
|
||||
Name: p.Name,
|
||||
})
|
||||
}
|
||||
|
||||
err = svc.rbac.UpdateNode(id.MustNumID(r.ID), r.Handle, pp...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
svc.eventbus.Dispatch(ctx, event.UserGroupAfterUpdate(upd, r))
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
return r, svc.recordAction(ctx, raProps, UserGroupActionUpdate, err)
|
||||
}
|
||||
|
||||
func (svc *userGroup) UniqueCheck(ctx context.Context, r *types.UserGroup) (err error) {
|
||||
var (
|
||||
raProps = &userGroupActionProps{userGroup: r}
|
||||
)
|
||||
|
||||
if r.Handle != "" {
|
||||
if ex, _ := store.LookupUserGroupByHandle(ctx, svc.store, r.Handle); ex != nil && ex.ID > 0 && ex.ID != r.ID {
|
||||
raProps.setExisting(ex)
|
||||
return UserGroupErrHandleNotUnique()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *userGroup) Delete(ctx context.Context, userGroupID uint64) (err error) {
|
||||
var (
|
||||
r *types.UserGroup
|
||||
raProps = &userGroupActionProps{userGroup: &types.UserGroup{ID: userGroupID}}
|
||||
)
|
||||
|
||||
err = func() (err error) {
|
||||
if r, err = svc.findByID(ctx, userGroupID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
raProps.setUserGroup(r)
|
||||
|
||||
if !svc.ac.CanDeleteUserGroup(ctx, r) {
|
||||
return UserGroupErrNotAllowedToDelete()
|
||||
}
|
||||
|
||||
if err = svc.referencedByAuthClient(ctx, r.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = svc.eventbus.WaitFor(ctx, event.UserGroupBeforeDelete(nil, r)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.DeletedAt = now()
|
||||
|
||||
if err = store.UpdateUserGroup(ctx, svc.store, r); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = svc.rbac.RemoveNode(id.MustNumID(r.ID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
svc.eventbus.Dispatch(ctx, event.UserGroupAfterDelete(nil, r))
|
||||
|
||||
return
|
||||
}()
|
||||
|
||||
return svc.recordAction(ctx, raProps, UserGroupActionDelete, err)
|
||||
}
|
||||
|
||||
// referencedByAuthClient prevents deleting a user group that is used as the
|
||||
// security user group on any auth client
|
||||
func (svc *userGroup) referencedByAuthClient(ctx context.Context, userGroupID uint64) error {
|
||||
if userGroupID == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cc, _, err := store.SearchAuthClients(ctx, svc.store, types.AuthClientFilter{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, c := range cc {
|
||||
if c.Security != nil && c.Security.UserGroup == userGroupID {
|
||||
return UserGroupErrNotAllowedToDelete()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *userGroup) Undelete(ctx context.Context, userGroupID uint64) (err error) {
|
||||
var (
|
||||
r, upd *types.UserGroup
|
||||
raProps = &userGroupActionProps{userGroup: &types.UserGroup{ID: userGroupID}}
|
||||
)
|
||||
|
||||
err = func() (err error) {
|
||||
if r, err = svc.findByID(ctx, userGroupID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
upd = r.Clone()
|
||||
if err = svc.eventbus.WaitFor(ctx, event.UserGroupBeforeUpdate(upd, r)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
raProps.setUserGroup(upd)
|
||||
|
||||
if !svc.ac.CanDeleteUserGroup(ctx, upd) {
|
||||
return UserGroupErrNotAllowedToDelete()
|
||||
}
|
||||
|
||||
upd.DeletedAt = nil
|
||||
if err = store.UpdateUserGroup(ctx, svc.store, upd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pp := []rbac.GroupNodePath{}
|
||||
for _, p := range upd.Config.Paths {
|
||||
pp = append(pp, rbac.GroupNodePath{
|
||||
SelfID: id.MustNumID(p.SelfID),
|
||||
Name: p.Name,
|
||||
})
|
||||
}
|
||||
|
||||
err = svc.rbac.AddNode(id.MustNumID(upd.ID), upd.Handle, pp...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
svc.eventbus.Dispatch(ctx, event.UserGroupAfterUpdate(upd, r))
|
||||
return nil
|
||||
}()
|
||||
|
||||
return svc.recordAction(ctx, raProps, UserGroupActionUndelete, err)
|
||||
}
|
||||
|
||||
func (svc *userGroup) MemberList(ctx context.Context, userGroupID uint64) (mm types.UserSet, err error) {
|
||||
var (
|
||||
r *types.UserGroup
|
||||
|
||||
raProps = &userGroupActionProps{
|
||||
userGroup: &types.UserGroup{ID: userGroupID},
|
||||
}
|
||||
)
|
||||
|
||||
err = func() error {
|
||||
if userGroupID == 0 {
|
||||
return UserGroupErrInvalidID()
|
||||
}
|
||||
|
||||
if r, err = svc.findByID(ctx, userGroupID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !svc.ac.CanReadUserGroup(ctx, r) {
|
||||
return UserGroupErrNotAllowedToRead()
|
||||
}
|
||||
|
||||
mm, _, err = store.SearchUsers(ctx, svc.store, types.UserFilter{
|
||||
UserGroupID: userGroupID,
|
||||
})
|
||||
|
||||
return err
|
||||
}()
|
||||
|
||||
return mm, svc.recordAction(ctx, raProps, UserGroupActionMembers, err)
|
||||
}
|
||||
|
||||
// MemberAdd adds member (user) to a userGroup
|
||||
func (svc *userGroup) MemberAdd(ctx context.Context, userGroupID, memberID uint64) (err error) {
|
||||
var (
|
||||
g *types.UserGroup
|
||||
m *types.User
|
||||
|
||||
raProps = &userGroupActionProps{
|
||||
userGroup: &types.UserGroup{ID: userGroupID},
|
||||
member: &types.User{ID: memberID},
|
||||
}
|
||||
)
|
||||
|
||||
err = func() (err error) {
|
||||
if userGroupID == 0 || memberID == 0 {
|
||||
return UserGroupErrInvalidID()
|
||||
}
|
||||
|
||||
if g, err = svc.findByID(ctx, userGroupID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
raProps.setUserGroup(g)
|
||||
|
||||
if m, err = svc.user.FindByID(ctx, memberID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
raProps.setMember(m)
|
||||
|
||||
m.UserGroupID = g.ID
|
||||
|
||||
if err = svc.eventbus.WaitFor(ctx, event.UserGroupBeforeMemberAdd(g, g)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !svc.ac.CanManageMembersOnUserGroup(ctx, g) {
|
||||
return UserGroupErrNotAllowedToManageMembers()
|
||||
}
|
||||
|
||||
if err = store.UpdateUser(ctx, svc.store, m); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = svc.rbac.AssignGroupMembers(id.MustNumID(m.UserGroupID), id.MustNumID(m.ID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_ = svc.eventbus.WaitFor(ctx, event.UserGroupAfterMemberAdd(g, g))
|
||||
return nil
|
||||
}()
|
||||
|
||||
return svc.recordAction(ctx, raProps, UserGroupActionMemberAdd, err)
|
||||
}
|
||||
|
||||
func loadUserGroup(ctx context.Context, s store.UserGroups, ID uint64) (res *types.UserGroup, err error) {
|
||||
if ID == 0 {
|
||||
return nil, UserGroupErrInvalidID()
|
||||
}
|
||||
|
||||
if res, err = store.LookupUserGroupByID(ctx, s, ID); errors.IsNotFound(err) {
|
||||
return nil, UserGroupErrNotFound()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// toLabeledUserGroups converts to []label.LabeledResource
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func toLabeledUserGroups(set []*types.UserGroup) []label.LabeledResource {
|
||||
if len(set) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ll := make([]label.LabeledResource, len(set))
|
||||
for i := range set {
|
||||
ll[i] = set[i]
|
||||
}
|
||||
|
||||
return ll
|
||||
}
|
||||
|
||||
func (svc *userGroup) isValidStructure(ctx context.Context, g *types.UserGroup) (ok bool) {
|
||||
// @todo :)
|
||||
return true
|
||||
}
|
||||
|
||||
func (svc *userGroup) checkSelfID(ctx context.Context, g *types.UserGroup) bool {
|
||||
for _, p := range g.Config.Paths {
|
||||
// Can't point to itself
|
||||
if p.SelfID == g.ID {
|
||||
return false
|
||||
}
|
||||
|
||||
// The pointed to selfID exists
|
||||
_, err := svc.FindByID(ctx, p.SelfID)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (svc *userGroup) checkPaths(g *types.UserGroup) (ok bool) {
|
||||
if id.MustNumID(g.ID).Equal(svc.rootUserGroup) {
|
||||
return len(g.Config.Paths) == 0
|
||||
}
|
||||
|
||||
if g.Config == nil {
|
||||
g.Config = &types.UserGroupConfig{}
|
||||
}
|
||||
|
||||
if len(g.Config.Paths) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
names := make(map[string]bool, len(g.Config.Paths)/2)
|
||||
for _, p := range g.Config.Paths {
|
||||
if p.SelfID == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Do not allow duplicate paths
|
||||
if names[p.Name] {
|
||||
return false
|
||||
}
|
||||
|
||||
names[p.Name] = true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user