package automation import ( "context" "fmt" "strconv" . "github.com/cortezaproject/corteza/server/pkg/expr" "github.com/cortezaproject/corteza/server/pkg/filter" "github.com/cortezaproject/corteza/server/pkg/wfexec" "github.com/cortezaproject/corteza/server/system/types" ) type ( userService interface { FindByID(ctx context.Context, userID uint64) (*types.User, error) FindByHandle(ctx context.Context, handle string) (*types.User, error) FindByEmail(ctx context.Context, email string) (*types.User, error) Find(ctx context.Context, filter types.UserFilter) (set types.UserSet, f types.UserFilter, err error) Create(ctx context.Context, user *types.User) (*types.User, error) Update(ctx context.Context, user *types.User) (*types.User, error) Delete(ctx context.Context, id uint64) error Suspend(ctx context.Context, id uint64) error Unsuspend(ctx context.Context, id uint64) error Undelete(ctx context.Context, id uint64) error } usersHandler struct { reg usersHandlerRegistry uSvc userService rSvc roleService } userSetIterator struct { // Item buffer, current item pointer, and total items traversed ptr uint buffer types.UserSet total uint // When filter limit is set, this constraints it iterLimit uint useIterLimit bool // Item loader for additional chunks filter types.UserFilter loader func() error // Total reported by the first page resTotal uint } userLookup interface { GetLookup() (bool, uint64, string, string, *types.User) } ) func UsersHandler(reg usersHandlerRegistry, uSvc userService, rSvc roleService) *usersHandler { h := &usersHandler{ reg: reg, uSvc: uSvc, rSvc: rSvc, } h.register() return h } func (h usersHandler) lookup(ctx context.Context, args *usersLookupArgs) (results *usersLookupResults, err error) { results = &usersLookupResults{} results.User, err = lookupUser(ctx, h.uSvc, args) return } func (h usersHandler) searchMembership(ctx context.Context, args *usersSearchMembershipArgs) (results *usersSearchMembershipResults, err error) { results = &usersSearchMembershipResults{} u, err := lookupUser(ctx, h.uSvc, args) if err != nil { return } if u == nil { return nil, fmt.Errorf("user not found") } // Get the roles mm, err := h.rSvc.Membership(ctx, u.ID) if err != nil { return } if len(mm) == 0 { results.Roles = []*types.Role{} return } rr := make([]string, len(mm)) for i, r := range mm { rr[i] = strconv.FormatUint(r.RoleID, 10) } results.Roles, _, err = h.rSvc.Find(ctx, types.RoleFilter{ RoleID: rr, }) results.Total = uint64(len(results.Roles)) return } func (h usersHandler) checkMembership(ctx context.Context, args *usersCheckMembershipArgs) (results *usersCheckMembershipResults, err error) { results = &usersCheckMembershipResults{} // Resolve user u, err := lookupUser(ctx, h.uSvc, usersLookupArgs{ hasLookup: args.hasUser, Lookup: args.User, lookupID: args.userID, lookupHandle: args.userHandle, lookupEmail: args.userEmail, lookupRes: args.userRes, }) if err != nil { return } if u == nil { return nil, fmt.Errorf("user not found") } // Get user membershio mm, err := h.rSvc.Membership(ctx, u.ID) if err != nil { return } if len(mm) == 0 { return &usersCheckMembershipResults{ Member: false, }, nil } // Resolve role r, err := lookupRole(ctx, h.rSvc, rolesLookupArgs{ hasLookup: args.hasRole, Lookup: args.Role, lookupID: args.roleID, lookupHandle: args.roleHandle, lookupRes: args.roleRes, }) if err != nil { return } if r == nil { return nil, fmt.Errorf("role not found") } // Check if there for _, m := range mm { if m.RoleID == r.ID { return &usersCheckMembershipResults{ Member: true, }, nil } } return &usersCheckMembershipResults{ Member: false, }, nil } func (h usersHandler) search(ctx context.Context, args *usersSearchArgs) (results *usersSearchResults, err error) { results = &usersSearchResults{} var ( f = types.UserFilter{ Query: args.Query, Email: args.Email, Handle: args.Handle, Labels: args.Labels, Deleted: filter.State(args.Deleted), Suspended: filter.State(args.Suspended), } ) if args.hasSort { if err = f.Sort.Set(args.Sort); err != nil { return } } if args.hasPageCursor && args.PageCursor != "" { f.PageCursor = &filter.PagingCursor{} if err = f.PageCursor.UnmarshalJSON([]byte(args.PageCursor)); err != nil { return } } if args.hasLabels { f.Labels = args.Labels } if args.hasLimit { f.Limit = uint(args.Limit) } // Total cannot be fetched together with a page cursor f.IncTotal = args.IncTotal && f.PageCursor == nil var auxf types.UserFilter results.Users, auxf, err = h.uSvc.Find(ctx, f) results.Total = uint64(auxf.Total) return } func (h usersHandler) each(ctx context.Context, args *usersEachArgs) (out wfexec.IteratorHandler, err error) { var ( i = &userSetIterator{} f = types.UserFilter{ Query: args.Query, Email: args.Email, Handle: args.Handle, Labels: args.Labels, Deleted: filter.State(args.Deleted), Suspended: filter.State(args.Suspended), } ) if args.hasSort { if err = f.Sort.Set(args.Sort); err != nil { return } } if args.hasPageCursor && args.PageCursor != "" { f.NextPage = &filter.PagingCursor{} if err = f.NextPage.UnmarshalJSON([]byte(args.PageCursor)); err != nil { return } } f.IncTotal = args.IncTotal if args.hasLabels { f.Labels = args.Labels } if args.hasLimit { i.useIterLimit = true i.iterLimit = uint(args.Limit) f.Limit = uint(args.Limit) if args.Limit > uint64(wfexec.MaxIteratorBufferSize) { f.Limit = wfexec.MaxIteratorBufferSize } i.iterLimit = uint(args.Limit) } else { f.Limit = wfexec.MaxIteratorBufferSize } i.filter = f i.loader = func() (err error) { // Edgecase if i.filter.PageCursor != nil && i.filter.NextPage == nil { return } i.total += i.ptr i.ptr = 0 i.filter.PageCursor = i.filter.NextPage i.filter.NextPage = nil // Total is fetched with the first page only; a paged query cannot carry it i.filter.IncTotal = i.filter.IncTotal && i.filter.PageCursor == nil i.buffer, i.filter, err = h.uSvc.Find(ctx, i.filter) if i.filter.IncTotal { i.resTotal = i.filter.Total } return } // Initial load return i, i.loader() } func (h usersHandler) create(ctx context.Context, args *usersCreateArgs) (results *usersCreateResults, err error) { results = &usersCreateResults{} results.User, err = h.uSvc.Create(ctx, args.User) return } func (h usersHandler) update(ctx context.Context, args *usersUpdateArgs) (results *usersUpdateResults, err error) { results = &usersUpdateResults{} results.User, err = h.uSvc.Update(ctx, args.User) return } func (h usersHandler) delete(ctx context.Context, args *usersDeleteArgs) error { if id, err := getUserID(ctx, h.uSvc, args); err != nil { return err } else { return h.uSvc.Delete(ctx, id) } } func (h usersHandler) recover(ctx context.Context, args *usersRecoverArgs) error { if id, err := getUserID(ctx, h.uSvc, args); err != nil { return err } else { return h.uSvc.Undelete(ctx, id) } } func (h usersHandler) suspend(ctx context.Context, args *usersSuspendArgs) error { if id, err := getUserID(ctx, h.uSvc, args); err != nil { return err } else { return h.uSvc.Suspend(ctx, id) } } func (h usersHandler) unsuspend(ctx context.Context, args *usersUnsuspendArgs) error { if id, err := getUserID(ctx, h.uSvc, args); err != nil { return err } else { return h.uSvc.Unsuspend(ctx, id) } } func getUserID(ctx context.Context, svc userService, args userLookup) (uint64, error) { _, ID, _, _, _ := args.GetLookup() if ID > 0 { return ID, nil } user, err := lookupUser(ctx, svc, args) if err != nil { return 0, err } return user.ID, nil } func lookupUser(ctx context.Context, svc userService, args userLookup) (*types.User, error) { _, ID, handle, email, user := args.GetLookup() switch { case user != nil: return user, nil case ID > 0: return svc.FindByID(ctx, ID) case len(email) > 0: return svc.FindByEmail(ctx, email) case len(handle) > 0: return svc.FindByHandle(ctx, handle) } return nil, fmt.Errorf("empty lookup params") } func (i *userSetIterator) More(context.Context, *Vars) (bool, error) { return wfexec.GenericResourceNextCheck(i.useIterLimit, i.ptr, uint(len(i.buffer)), i.total, i.iterLimit, i.filter.NextPage != nil), nil } func (i *userSetIterator) Start(context.Context, *Vars) error { i.ptr = 0; return nil } func (i *userSetIterator) Next(context.Context, *Vars) (out *Vars, err error) { if len(i.buffer)-int(i.ptr) <= 0 { if err = i.loader(); err != nil { panic(err) } } out = &Vars{} out.Set("user", Must(NewUser(i.buffer[i.ptr]))) out.Set("index", Must(NewInteger(i.total+i.ptr))) out.Set("total", Must(NewInteger(i.resTotal))) i.ptr++ return out, nil }