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
733 lines
17 KiB
Go
733 lines
17 KiB
Go
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
|
|
}
|