3
0
Files
corteza/system/service/role.go
T

615 lines
14 KiB
Go

package service
import (
"context"
"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/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 (
role struct {
actionlog actionlog.Recorder
ac roleAccessController
eventbus eventDispatcher
user UserService
store store.Storer
}
roleAccessController interface {
CanCreateRole(context.Context) bool
CanReadRole(context.Context, *types.Role) bool
CanUpdateRole(context.Context, *types.Role) bool
CanDeleteRole(context.Context, *types.Role) bool
CanManageRoleMembers(context.Context, *types.Role) bool
}
RoleService interface {
FindByID(ctx context.Context, roleID uint64) (*types.Role, error)
FindByName(ctx context.Context, name string) (*types.Role, error)
FindByHandle(ctx context.Context, handle string) (*types.Role, error)
FindByAny(ctx context.Context, identifier interface{}) (*types.Role, error)
Find(context.Context, types.RoleFilter) (types.RoleSet, types.RoleFilter, error)
Create(ctx context.Context, role *types.Role) (*types.Role, error)
Update(ctx context.Context, role *types.Role) (*types.Role, error)
Archive(ctx context.Context, ID uint64) error
Unarchive(ctx context.Context, ID uint64) error
Delete(ctx context.Context, ID uint64) error
Undelete(ctx context.Context, ID uint64) error
Membership(ctx context.Context, userID uint64) (types.RoleMemberSet, error)
MemberList(ctx context.Context, roleID uint64) (types.RoleMemberSet, error)
MemberAdd(ctx context.Context, roleID, userID uint64) error
MemberRemove(ctx context.Context, roleID, userID uint64) error
}
)
func Role(ctx context.Context) RoleService {
return (&role{
ac: DefaultAccessControl,
eventbus: eventbus.Service(),
actionlog: DefaultActionlog,
user: DefaultUser,
store: DefaultStore,
})
}
func (svc role) Find(ctx context.Context, filter types.RoleFilter) (rr types.RoleSet, f types.RoleFilter, err error) {
var (
raProps = &roleActionProps{filter: &filter}
)
// For each fetched item, store backend will check if it is valid or not
filter.Check = func(res *types.Role) (bool, error) {
if !svc.ac.CanReadRole(ctx, res) {
return false, nil
}
return true, nil
}
err = func() error {
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 roles
//if !svc.ac.CanAccess(ctx) {
// return RoleErrNotAllowedToListRoles()
//}
}
if len(filter.Labels) > 0 {
filter.LabeledIDs, err = label.Search(
ctx,
svc.store,
types.Role{}.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.SearchRoles(ctx, svc.store, filter); err != nil {
return err
}
if err = label.Load(ctx, svc.store, toLabeledRoles(rr)...); err != nil {
return err
}
return nil
}()
return rr, f, svc.recordAction(ctx, raProps, RoleActionSearch, err)
}
func (svc role) FindByID(ctx context.Context, roleID uint64) (r *types.Role, err error) {
var (
raProps = &roleActionProps{role: &types.Role{ID: roleID}}
)
err = func() error {
if r, err = svc.findByID(ctx, roleID); err != nil {
return err
}
raProps.setRole(r)
return nil
}()
return r, svc.recordAction(ctx, raProps, RoleActionLookup, err)
}
func (svc role) findByID(ctx context.Context, roleID uint64) (*types.Role, error) {
if roleID == 0 {
return nil, RoleErrInvalidID()
}
r, err := store.LookupRoleByID(ctx, svc.store, roleID)
return svc.proc(ctx, r, err)
}
func (svc role) FindByName(ctx context.Context, name string) (r *types.Role, err error) {
var (
raProps = &roleActionProps{role: &types.Role{Name: name}}
)
err = func() error {
r, err := store.LookupRoleByName(ctx, svc.store, name)
if r, err = svc.proc(ctx, r, err); err != nil {
return err
}
raProps.setRole(r)
return nil
}()
return r, svc.recordAction(ctx, raProps, RoleActionLookup, err)
}
func (svc role) FindByHandle(ctx context.Context, h string) (r *types.Role, err error) {
var (
raProps = &roleActionProps{role: &types.Role{Handle: h}}
)
err = func() error {
r, err = store.LookupRoleByHandle(ctx, svc.store, h)
if r, err = svc.proc(ctx, r, err); err != nil {
return err
}
raProps.setRole(r)
return nil
}()
return r, svc.recordAction(ctx, raProps, RoleActionLookup, err)
}
// FindByAny finds role by given identifier (id, handle, name)
func (svc role) FindByAny(ctx context.Context, identifier interface{}) (r *types.Role, 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)
if (err == nil && r.ID == 0) || errors.IsNotFound(err) {
return svc.FindByName(ctx, strIdentifier)
}
return r, err
}
} else {
return nil, RoleErrInvalidID()
}
}
func (svc role) proc(ctx context.Context, r *types.Role, err error) (*types.Role, error) {
if err != nil {
if errors.IsNotFound(err) {
return nil, RoleErrNotFound()
}
return nil, err
}
if err = label.Load(ctx, svc.store, r); err != nil {
return nil, err
}
return r, nil
}
func (svc role) Create(ctx context.Context, new *types.Role) (r *types.Role, err error) {
var (
raProps = &roleActionProps{new: new}
)
err = func() (err error) {
if !handle.IsValid(new.Handle) {
return RoleErrInvalidHandle()
}
if !svc.ac.CanCreateRole(ctx) {
return RoleErrNotAllowedToCreate()
}
if err = svc.eventbus.WaitFor(ctx, event.RoleBeforeCreate(new, r)); err != nil {
return
}
if err = svc.UniqueCheck(ctx, new); err != nil {
return
}
new.ID = nextID()
new.CreatedAt = *now()
if err = store.CreateRole(ctx, svc.store, new); err != nil {
return
}
if err = label.Create(ctx, svc.store, new); err != nil {
return
}
r = new
_ = svc.eventbus.WaitFor(ctx, event.RoleAfterCreate(new, r))
return
}()
return r, svc.recordAction(ctx, raProps, RoleActionCreate, err)
}
func (svc role) Update(ctx context.Context, upd *types.Role) (r *types.Role, err error) {
var (
raProps = &roleActionProps{update: upd}
)
err = func() (err error) {
if upd.ID == 0 {
return RoleErrInvalidID()
}
if !handle.IsValid(upd.Handle) {
return RoleErrInvalidHandle()
}
if !svc.ac.CanUpdateRole(ctx, upd) {
return RoleErrNotAllowedToUpdate()
}
if r, err = store.LookupRoleByID(ctx, svc.store, upd.ID); err != nil {
return
}
raProps.setRole(r)
if err = svc.eventbus.WaitFor(ctx, event.RoleBeforeUpdate(upd, r)); err != nil {
return
}
if err = svc.UniqueCheck(ctx, upd); err != nil {
return
}
r.Handle = upd.Handle
r.Name = upd.Name
r.UpdatedAt = now()
// Assign changed values
if err = store.UpdateRole(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
}
_ = svc.eventbus.WaitFor(ctx, event.RoleAfterUpdate(upd, r))
return nil
}()
return r, svc.recordAction(ctx, raProps, RoleActionUpdate, err)
}
func (svc role) UniqueCheck(ctx context.Context, r *types.Role) (err error) {
var (
raProps = &roleActionProps{role: r}
)
if r.Handle != "" {
if ex, _ := store.LookupRoleByHandle(ctx, svc.store, r.Handle); ex != nil && ex.ID > 0 && ex.ID != r.ID {
raProps.setExisting(ex)
return RoleErrHandleNotUnique()
}
}
if r.Name != "" {
if ex, _ := store.LookupRoleByName(ctx, svc.store, r.Name); ex != nil && ex.ID > 0 && ex.ID != r.ID {
raProps.setExisting(ex)
return RoleErrNameNotUnique()
}
}
return nil
}
func (svc role) Delete(ctx context.Context, roleID uint64) (err error) {
var (
r *types.Role
raProps = &roleActionProps{role: &types.Role{ID: roleID}}
)
err = func() (err error) {
if r, err = svc.findByID(ctx, roleID); err != nil {
return err
}
raProps.setRole(r)
if !svc.ac.CanDeleteRole(ctx, r) {
return RoleErrNotAllowedToDelete()
}
if err = svc.eventbus.WaitFor(ctx, event.RoleBeforeDelete(nil, r)); err != nil {
return
}
r.DeletedAt = now()
if err = store.UpdateRole(ctx, svc.store, r); err != nil {
return
}
_ = svc.eventbus.WaitFor(ctx, event.RoleAfterDelete(nil, r))
return
}()
return svc.recordAction(ctx, raProps, RoleActionDelete, err)
}
func (svc role) Undelete(ctx context.Context, roleID uint64) (err error) {
var (
r *types.Role
raProps = &roleActionProps{role: &types.Role{ID: roleID}}
)
err = func() (err error) {
if r, err = svc.findByID(ctx, roleID); err != nil {
return err
}
raProps.setRole(r)
if !svc.ac.CanDeleteRole(ctx, r) {
return RoleErrNotAllowedToDelete()
}
r.DeletedAt = nil
if err = store.UpdateRole(ctx, svc.store, r); err != nil {
return
}
return nil
}()
return svc.recordAction(ctx, raProps, RoleActionUndelete, err)
}
func (svc role) Archive(ctx context.Context, roleID uint64) (err error) {
var (
r *types.Role
raProps = &roleActionProps{role: &types.Role{ID: roleID}}
)
err = func() (err error) {
if r, err = svc.findByID(ctx, roleID); err != nil {
return err
}
raProps.setRole(r)
if !svc.ac.CanUpdateRole(ctx, r) {
return RoleErrNotAllowedToArchive()
}
r.ArchivedAt = now()
if err = store.UpdateRole(ctx, svc.store, r); err != nil {
return
}
return
}()
return svc.recordAction(ctx, raProps, RoleActionArchive, err)
}
func (svc role) Unarchive(ctx context.Context, roleID uint64) (err error) {
var (
r *types.Role
raProps = &roleActionProps{role: &types.Role{ID: roleID}}
)
err = func() (err error) {
if r, err = svc.findByID(ctx, roleID); err != nil {
return err
}
raProps.setRole(r)
if !svc.ac.CanDeleteRole(ctx, r) {
return RoleErrNotAllowedToDelete()
}
r.ArchivedAt = nil
if err = store.UpdateRole(ctx, svc.store, r); err != nil {
return
}
return nil
}()
return svc.recordAction(ctx, raProps, RoleActionUnarchive, err)
}
func (svc role) Membership(ctx context.Context, userID uint64) (types.RoleMemberSet, error) {
mm, _, err := store.SearchRoleMembers(ctx, svc.store, types.RoleMemberFilter{UserID: userID})
return mm, err
}
func (svc role) MemberList(ctx context.Context, roleID uint64) (mm types.RoleMemberSet, err error) {
var (
r *types.Role
raProps = &roleActionProps{
role: &types.Role{ID: roleID},
}
)
err = func() error {
if roleID == rbac.EveryoneRoleID || roleID == 0 {
return RoleErrInvalidID()
}
if r, err = svc.findByID(ctx, roleID); err != nil {
return err
}
if !svc.ac.CanReadRole(ctx, r) {
return RoleErrNotAllowedToRead()
}
mm, _, err = store.SearchRoleMembers(ctx, svc.store, types.RoleMemberFilter{RoleID: roleID})
return err
}()
return mm, svc.recordAction(ctx, raProps, RoleActionMembers, err)
}
// MemberAdd adds member (user) to a role
func (svc role) MemberAdd(ctx context.Context, roleID, memberID uint64) (err error) {
var (
r *types.Role
m *types.User
raProps = &roleActionProps{
role: &types.Role{ID: roleID},
member: &types.User{ID: memberID},
}
)
err = func() (err error) {
if roleID == rbac.EveryoneRoleID || roleID == 0 || memberID == 0 {
return RoleErrInvalidID()
}
if r, err = svc.findByID(ctx, roleID); err != nil {
return
}
raProps.setRole(r)
if m, err = svc.user.FindByID(ctx, memberID); err != nil {
return
}
raProps.setMember(m)
if err = svc.eventbus.WaitFor(ctx, event.RoleMemberBeforeAdd(m, r)); err != nil {
return
}
if !svc.ac.CanManageRoleMembers(ctx, r) {
return RoleErrNotAllowedToManageMembers()
}
if err = store.CreateRoleMember(ctx, svc.store, &types.RoleMember{RoleID: r.ID, UserID: m.ID}); err != nil {
return
}
_ = svc.eventbus.WaitFor(ctx, event.RoleMemberAfterAdd(m, r))
return nil
}()
return svc.recordAction(ctx, raProps, RoleActionMemberAdd, err)
}
// MemberRemove removes member (user) from a role
func (svc role) MemberRemove(ctx context.Context, roleID, memberID uint64) (err error) {
var (
r *types.Role
m *types.User
raProps = &roleActionProps{
role: &types.Role{ID: roleID},
member: &types.User{ID: memberID},
}
)
err = func() (err error) {
if roleID == rbac.EveryoneRoleID || roleID == 0 || memberID == 0 {
return RoleErrInvalidID()
}
if r, err = svc.findByID(ctx, roleID); err != nil {
return
}
raProps.setRole(r)
if m, err = svc.user.FindByID(ctx, memberID); err != nil {
return
}
raProps.setMember(m)
if err = svc.eventbus.WaitFor(ctx, event.RoleMemberBeforeRemove(m, r)); err != nil {
return
}
if !svc.ac.CanManageRoleMembers(ctx, r) {
return RoleErrNotAllowedToManageMembers()
}
if err = store.DeleteRoleMember(ctx, svc.store, &types.RoleMember{RoleID: r.ID, UserID: m.ID}); err != nil {
return
}
_ = svc.eventbus.WaitFor(ctx, event.RoleMemberAfterRemove(m, r))
return nil
}()
return svc.recordAction(ctx, raProps, RoleActionMemberRemove, err)
}
// toLabeledRoles converts to []label.LabeledResource
//
// This function is auto-generated.
func toLabeledRoles(set []*types.Role) []label.LabeledResource {
if len(set) == 0 {
return nil
}
ll := make([]label.LabeledResource, len(set))
for i := range set {
ll[i] = set[i]
}
return ll
}