diff --git a/system/automation/roles_handler.gen.go b/system/automation/roles_handler.gen.go index 6df9a4ea8..b00f0782c 100644 --- a/system/automation/roles_handler.gen.go +++ b/system/automation/roles_handler.gen.go @@ -28,6 +28,10 @@ type ( func (h rolesHandler) register() { h.reg.AddFunctions( h.Lookup(), + h.SearchMembers(), + h.EachMember(), + h.AddMember(), + h.RemoveMember(), h.Search(), h.Each(), h.Create(), @@ -137,6 +141,406 @@ func (h rolesHandler) Lookup() *atypes.Function { } } +type ( + rolesSearchMembersArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupRes *types.Role + } + + rolesSearchMembersResults struct { + Users []*types.User + Total uint64 + } +) + +func (a rolesSearchMembersArgs) GetLookup() (bool, uint64, string, *types.Role) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupRes +} + +// SearchMembers function Role members search +// +// expects implementation of searchMembers function: +// func (h rolesHandler) searchMembers(ctx context.Context, args *rolesSearchMembersArgs) (results *rolesSearchMembersResults, err error) { +// return +// } +func (h rolesHandler) SearchMembers() *atypes.Function { + return &atypes.Function{ + Ref: "rolesSearchMembers", + Kind: "function", + Labels: map[string]string{"users": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "Role members search", + Description: "Find members for a specific roleby ID or handle", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "Role"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "users", + Types: []string{"User"}, + IsArray: true, + }, + + { + Name: "total", + Types: []string{"UnsignedInteger"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &rolesSearchMembersArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("Role").Type(): + args.lookupRes = aux.Get().(*types.Role) + } + } + + var results *rolesSearchMembersResults + if results, err = h.searchMembers(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Users (*types.User) to Array (of User) + var ( + tval expr.TypedValue + tarr = make([]expr.TypedValue, len(results.Users)) + ) + + for i := range results.Users { + if tarr[i], err = h.reg.Type("User").Cast(results.Users[i]); err != nil { + return + } + } + + if tval, err = expr.NewArray(tarr); err != nil { + return + } else if err = expr.Assign(out, "users", tval); err != nil { + return + } + } + + { + // converting results.Total (uint64) to UnsignedInteger + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("UnsignedInteger").Cast(results.Total); err != nil { + return + } else if err = expr.Assign(out, "total", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + rolesEachMemberArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupRes *types.Role + } + + rolesEachMemberResults struct { + User *types.User + Total uint64 + } +) + +func (a rolesEachMemberArgs) GetLookup() (bool, uint64, string, *types.Role) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupRes +} + +// EachMember function Iterate over role members +// +// expects implementation of eachMember function: +// func (h rolesHandler) eachMember(ctx context.Context, args *rolesEachMemberArgs) (results *rolesEachMemberResults, err error) { +// return +// } +func (h rolesHandler) EachMember() *atypes.Function { + return &atypes.Function{ + Ref: "rolesEachMember", + Kind: "iterator", + Labels: map[string]string{"users": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "Iterate over role members", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "Role"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "user", + Types: []string{"User"}, + }, + + { + Name: "total", + Types: []string{"UnsignedInteger"}, + }, + }, + + Iterator: func(ctx context.Context, in *expr.Vars) (out wfexec.IteratorHandler, err error) { + var ( + args = &rolesEachMemberArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("Role").Type(): + args.lookupRes = aux.Get().(*types.Role) + } + } + + return h.eachMember(ctx, args) + }, + } +} + +type ( + rolesAddMemberArgs struct { + hasRole bool + Role interface{} + roleID uint64 + roleHandle string + roleRes *types.Role + + hasUser bool + User interface{} + userID uint64 + userHandle string + userEmail string + userRes *types.User + } +) + +func (a rolesAddMemberArgs) GetRole() (bool, uint64, string, *types.Role) { + return a.hasRole, a.roleID, a.roleHandle, a.roleRes +} + +func (a rolesAddMemberArgs) GetUser() (bool, uint64, string, string, *types.User) { + return a.hasUser, a.userID, a.userHandle, a.userEmail, a.userRes +} + +// AddMember function Role membership add +// +// expects implementation of addMember function: +// func (h rolesHandler) addMember(ctx context.Context, args *rolesAddMemberArgs) (err error) { +// return +// } +func (h rolesHandler) AddMember() *atypes.Function { + return &atypes.Function{ + Ref: "rolesAddMember", + Kind: "function", + Labels: map[string]string{"users": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "Role membership add", + }, + + Parameters: []*atypes.Param{ + { + Name: "role", + Types: []string{"ID", "Handle", "Role"}, Required: true, + }, + { + Name: "user", + Types: []string{"ID", "Handle", "String", "User"}, Required: true, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &rolesAddMemberArgs{ + hasRole: in.Has("role"), + hasUser: in.Has("user"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Role argument + if args.hasRole { + aux := expr.Must(expr.Select(in, "role")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.roleID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.roleHandle = aux.Get().(string) + case h.reg.Type("Role").Type(): + args.roleRes = aux.Get().(*types.Role) + } + } + + // Converting User argument + if args.hasUser { + aux := expr.Must(expr.Select(in, "user")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.userID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.userHandle = aux.Get().(string) + case h.reg.Type("String").Type(): + args.userEmail = aux.Get().(string) + case h.reg.Type("User").Type(): + args.userRes = aux.Get().(*types.User) + } + } + + return out, h.addMember(ctx, args) + }, + } +} + +type ( + rolesRemoveMemberArgs struct { + hasRole bool + Role interface{} + roleID uint64 + roleHandle string + roleRes *types.Role + + hasUser bool + User interface{} + userID uint64 + userHandle string + userEmail string + userRes *types.User + } +) + +func (a rolesRemoveMemberArgs) GetRole() (bool, uint64, string, *types.Role) { + return a.hasRole, a.roleID, a.roleHandle, a.roleRes +} + +func (a rolesRemoveMemberArgs) GetUser() (bool, uint64, string, string, *types.User) { + return a.hasUser, a.userID, a.userHandle, a.userEmail, a.userRes +} + +// RemoveMember function Role membership remove +// +// expects implementation of removeMember function: +// func (h rolesHandler) removeMember(ctx context.Context, args *rolesRemoveMemberArgs) (err error) { +// return +// } +func (h rolesHandler) RemoveMember() *atypes.Function { + return &atypes.Function{ + Ref: "rolesRemoveMember", + Kind: "function", + Labels: map[string]string{"users": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "Role membership remove", + }, + + Parameters: []*atypes.Param{ + { + Name: "role", + Types: []string{"ID", "Handle", "Role"}, Required: true, + }, + { + Name: "user", + Types: []string{"ID", "Handle", "String", "User"}, Required: true, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &rolesRemoveMemberArgs{ + hasRole: in.Has("role"), + hasUser: in.Has("user"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Role argument + if args.hasRole { + aux := expr.Must(expr.Select(in, "role")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.roleID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.roleHandle = aux.Get().(string) + case h.reg.Type("Role").Type(): + args.roleRes = aux.Get().(*types.Role) + } + } + + // Converting User argument + if args.hasUser { + aux := expr.Must(expr.Select(in, "user")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.userID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.userHandle = aux.Get().(string) + case h.reg.Type("String").Type(): + args.userEmail = aux.Get().(string) + case h.reg.Type("User").Type(): + args.userRes = aux.Get().(*types.User) + } + } + + return out, h.removeMember(ctx, args) + }, + } +} + type ( rolesSearchArgs struct { hasQuery bool diff --git a/system/automation/roles_handler.go b/system/automation/roles_handler.go index c69f6d102..4058326f6 100644 --- a/system/automation/roles_handler.go +++ b/system/automation/roles_handler.go @@ -2,6 +2,7 @@ package automation import ( "context" + "errors" "fmt" . "github.com/cortezaproject/corteza-server/pkg/expr" @@ -23,11 +24,17 @@ type ( Archive(ctx context.Context, id uint64) error Unarchive(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 } rolesHandler struct { reg rolesHandlerRegistry rSvc roleService + uSvc userService } roleSetIterator struct { @@ -41,10 +48,11 @@ type ( } ) -func RolesHandler(reg rolesHandlerRegistry, rSvc roleService) *rolesHandler { +func RolesHandler(reg rolesHandlerRegistry, rSvc roleService, uSvc userService) *rolesHandler { h := &rolesHandler{ reg: reg, rSvc: rSvc, + uSvc: uSvc, } h.register() @@ -57,6 +65,143 @@ func (h rolesHandler) lookup(ctx context.Context, args *rolesLookupArgs) (result return } +func (h rolesHandler) searchMembers(ctx context.Context, args *rolesSearchMembersArgs) (results *rolesSearchMembersResults, err error) { + results = &rolesSearchMembersResults{} + + rl, err := lookupRole(ctx, h.rSvc, args) + if err != nil { + return + } + if rl == nil { + return nil, errors.New("role not found") + } + + // Get membership info + mm, err := h.rSvc.MemberList(ctx, rl.ID) + if err != nil { + return + } + + if len(mm) == 0 { + results.Users = []*types.User{} + return + } + + // Get actual users + uu := make([]uint64, len(mm)) + for i, m := range mm { + uu[i] = m.UserID + } + results.Users, _, err = h.uSvc.Find(ctx, types.UserFilter{ + UserID: uu, + }) + results.Total = uint64(len(results.Users)) + + return +} + +func (h rolesHandler) eachMember(ctx context.Context, args *rolesEachMemberArgs) (out wfexec.IteratorHandler, err error) { + var ( + i = &userSetIterator{} + ) + + rl, err := lookupRole(ctx, h.rSvc, args) + if err != nil { + return + } + if rl == nil { + return nil, errors.New("role not found") + } + + // Get membership info + mm, err := h.rSvc.MemberList(ctx, rl.ID) + if err != nil { + return + } + + if len(mm) == 0 { + i.set = []*types.User{} + i.filter = types.UserFilter{} + return i, nil + } + + // Get actual users + uu := make([]uint64, len(mm)) + for i, m := range mm { + uu[i] = m.UserID + } + i.set, i.filter, err = h.uSvc.Find(ctx, types.UserFilter{ + UserID: uu, + }) + return i, err +} + +func (h rolesHandler) addMember(ctx context.Context, args *rolesAddMemberArgs) (err error) { + role, 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 role == nil { + return errors.New("role not found") + } + + user, 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 role == nil { + return errors.New("user not found") + } + + return h.rSvc.MemberAdd(ctx, role.ID, user.ID) +} + +func (h rolesHandler) removeMember(ctx context.Context, args *rolesRemoveMemberArgs) (err error) { + role, 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 role == nil { + return errors.New("role not found") + } + + user, 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 role == nil { + return errors.New("user not found") + } + + return h.rSvc.MemberRemove(ctx, role.ID, user.ID) +} + func (h rolesHandler) search(ctx context.Context, args *rolesSearchArgs) (results *rolesSearchResults, err error) { results = &rolesSearchResults{} diff --git a/system/automation/roles_handler.yaml b/system/automation/roles_handler.yaml index 3114914cd..648748a57 100644 --- a/system/automation/roles_handler.yaml +++ b/system/automation/roles_handler.yaml @@ -16,6 +16,8 @@ params: rvRole: &rvRole wf: Role + rvUser: &rvUser + wf: User rvTotal: &rvTotal wf: UnsignedInteger @@ -23,6 +25,14 @@ params: rvPageCursor: &rvPageCursor wf: String + lookupUser: &lookupUser + required: true + types: + - { wf: ID } + - { wf: Handle } + - { wf: String, suffix: email } + - { wf: User, suffix: res } + filterParams: &filterParams query: types: @@ -76,6 +86,50 @@ functions: results: role: *rvRole + searchMembers: + meta: + short: Role members search + description: Find members for a specific roleby ID or handle + labels: + <<: *labels + params: + lookup: *lookup + results: + users: + <<: *rvUser + isArray: true + total: *rvTotal + + eachMember: + kind: iterator + meta: + short: Iterate over role members + labels: + <<: *labels + params: + lookup: *lookup + results: + user: *rvUser + total: *rvTotal + + addMember: + meta: + short: Role membership add + labels: + <<: *labels + params: + role: *lookup + user: *lookupUser + + removeMember: + meta: + short: Role membership remove + labels: + <<: *labels + params: + role: *lookup + user: *lookupUser + search: meta: short: Roles search diff --git a/system/automation/users_handler.gen.go b/system/automation/users_handler.gen.go index 9dd60d8ad..ab1b85384 100644 --- a/system/automation/users_handler.gen.go +++ b/system/automation/users_handler.gen.go @@ -28,6 +28,8 @@ type ( func (h usersHandler) register() { h.reg.AddFunctions( h.Lookup(), + h.SearchMembership(), + h.CheckMembership(), h.Search(), h.Each(), h.Create(), @@ -140,6 +142,263 @@ func (h usersHandler) Lookup() *atypes.Function { } } +type ( + usersSearchMembershipArgs struct { + hasLookup bool + Lookup interface{} + lookupID uint64 + lookupHandle string + lookupEmail string + lookupRes *types.User + } + + usersSearchMembershipResults struct { + Roles []*types.Role + Total uint64 + } +) + +func (a usersSearchMembershipArgs) GetLookup() (bool, uint64, string, string, *types.User) { + return a.hasLookup, a.lookupID, a.lookupHandle, a.lookupEmail, a.lookupRes +} + +// SearchMembership function User role search +// +// expects implementation of searchMembership function: +// func (h usersHandler) searchMembership(ctx context.Context, args *usersSearchMembershipArgs) (results *usersSearchMembershipResults, err error) { +// return +// } +func (h usersHandler) SearchMembership() *atypes.Function { + return &atypes.Function{ + Ref: "usersSearchMembership", + Kind: "function", + Labels: map[string]string{"users": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "User role search", + Description: "Search user role membership by ID, handle or string", + }, + + Parameters: []*atypes.Param{ + { + Name: "lookup", + Types: []string{"ID", "Handle", "String", "User"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "roles", + Types: []string{"Role"}, + IsArray: true, + }, + + { + Name: "total", + Types: []string{"UnsignedInteger"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &usersSearchMembershipArgs{ + hasLookup: in.Has("lookup"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Lookup argument + if args.hasLookup { + aux := expr.Must(expr.Select(in, "lookup")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.lookupID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.lookupHandle = aux.Get().(string) + case h.reg.Type("String").Type(): + args.lookupEmail = aux.Get().(string) + case h.reg.Type("User").Type(): + args.lookupRes = aux.Get().(*types.User) + } + } + + var results *usersSearchMembershipResults + if results, err = h.searchMembership(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Roles (*types.Role) to Array (of Role) + var ( + tval expr.TypedValue + tarr = make([]expr.TypedValue, len(results.Roles)) + ) + + for i := range results.Roles { + if tarr[i], err = h.reg.Type("Role").Cast(results.Roles[i]); err != nil { + return + } + } + + if tval, err = expr.NewArray(tarr); err != nil { + return + } else if err = expr.Assign(out, "roles", tval); err != nil { + return + } + } + + { + // converting results.Total (uint64) to UnsignedInteger + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("UnsignedInteger").Cast(results.Total); err != nil { + return + } else if err = expr.Assign(out, "total", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + usersCheckMembershipArgs struct { + hasUser bool + User interface{} + userID uint64 + userHandle string + userEmail string + userRes *types.User + + hasRole bool + Role interface{} + roleID uint64 + roleHandle string + roleRes *types.Role + } + + usersCheckMembershipResults struct { + Member bool + } +) + +func (a usersCheckMembershipArgs) GetUser() (bool, uint64, string, string, *types.User) { + return a.hasUser, a.userID, a.userHandle, a.userEmail, a.userRes +} + +func (a usersCheckMembershipArgs) GetRole() (bool, uint64, string, *types.Role) { + return a.hasRole, a.roleID, a.roleHandle, a.roleRes +} + +// CheckMembership function User membership check +// +// expects implementation of checkMembership function: +// func (h usersHandler) checkMembership(ctx context.Context, args *usersCheckMembershipArgs) (results *usersCheckMembershipResults, err error) { +// return +// } +func (h usersHandler) CheckMembership() *atypes.Function { + return &atypes.Function{ + Ref: "usersCheckMembership", + Kind: "function", + Labels: map[string]string{"users": "step,workflow"}, + Meta: &atypes.FunctionMeta{ + Short: "User membership check", + Description: "Find user role membership by ID, handle or string", + }, + + Parameters: []*atypes.Param{ + { + Name: "user", + Types: []string{"ID", "Handle", "String", "User"}, Required: true, + }, + { + Name: "role", + Types: []string{"ID", "Handle", "Role"}, Required: true, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "member", + Types: []string{"Boolean"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &usersCheckMembershipArgs{ + hasUser: in.Has("user"), + hasRole: in.Has("role"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting User argument + if args.hasUser { + aux := expr.Must(expr.Select(in, "user")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.userID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.userHandle = aux.Get().(string) + case h.reg.Type("String").Type(): + args.userEmail = aux.Get().(string) + case h.reg.Type("User").Type(): + args.userRes = aux.Get().(*types.User) + } + } + + // Converting Role argument + if args.hasRole { + aux := expr.Must(expr.Select(in, "role")) + switch aux.Type() { + case h.reg.Type("ID").Type(): + args.roleID = aux.Get().(uint64) + case h.reg.Type("Handle").Type(): + args.roleHandle = aux.Get().(string) + case h.reg.Type("Role").Type(): + args.roleRes = aux.Get().(*types.Role) + } + } + + var results *usersCheckMembershipResults + if results, err = h.checkMembership(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Member (bool) to Boolean + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("Boolean").Cast(results.Member); err != nil { + return + } else if err = expr.Assign(out, "member", tval); err != nil { + return + } + } + + return + }, + } +} + type ( usersSearchArgs struct { hasQuery bool diff --git a/system/automation/users_handler.go b/system/automation/users_handler.go index b936f21f7..655c60a99 100644 --- a/system/automation/users_handler.go +++ b/system/automation/users_handler.go @@ -2,6 +2,7 @@ package automation import ( "context" + "errors" "fmt" . "github.com/cortezaproject/corteza-server/pkg/expr" @@ -29,6 +30,7 @@ type ( usersHandler struct { reg usersHandlerRegistry uSvc userService + rSvc roleService } userSetIterator struct { @@ -42,10 +44,11 @@ type ( } ) -func UsersHandler(reg usersHandlerRegistry, uSvc userService) *usersHandler { +func UsersHandler(reg usersHandlerRegistry, uSvc userService, rSvc roleService) *usersHandler { h := &usersHandler{ reg: reg, uSvc: uSvc, + rSvc: rSvc, } h.register() @@ -58,6 +61,98 @@ func (h usersHandler) lookup(ctx context.Context, args *usersLookupArgs) (result 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, errors.New("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([]uint64, len(mm)) + for i, r := range mm { + rr[i] = r.RoleID + } + + 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, errors.New("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, errors.New("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{} diff --git a/system/automation/users_handler.yaml b/system/automation/users_handler.yaml index 93a4bdd1c..b72576a28 100644 --- a/system/automation/users_handler.yaml +++ b/system/automation/users_handler.yaml @@ -10,6 +10,13 @@ snippets: - { wf: String, suffix: email } - { wf: User, suffix: res } + lookupRole: &lookupRole + required: true + types: + - { wf: ID, } + - { wf: Handle, } + - { wf: Role, suffix: res } + user: &user types: - { wf: User } @@ -17,9 +24,15 @@ snippets: rvUser: &rvUser wf: User + rvRole: &rvRole + wf: Role + rvTotal: &rvTotal wf: UnsignedInteger + rvInclusion: &rvInclusion + wf: Boolean + rvPageCursor: &rvPageCursor wf: String @@ -73,6 +86,32 @@ functions: results: user: *rvUser + searchMembership: + meta: + short: User role search + description: Search user role membership by ID, handle or string + params: + lookup: *lookup + labels: + <<: *labels + results: + roles: + <<: *rvRole + isArray: true + total: *rvTotal + + checkMembership: + meta: + short: User membership check + description: Find user role membership by ID, handle or string + params: + user: *lookup + role: *lookupRole + labels: + <<: *labels + results: + member: *rvInclusion + search: meta: short: User search diff --git a/system/service/service.go b/system/service/service.go index 71c6403c4..d9f84b962 100644 --- a/system/service/service.go +++ b/system/service/service.go @@ -187,6 +187,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websock automation.UsersHandler( automationService.Registry(), DefaultUser, + DefaultRole, ) automation.TemplatesHandler( @@ -197,6 +198,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websock automation.RolesHandler( automationService.Registry(), DefaultRole, + DefaultUser, ) automation.RbacHandler( diff --git a/tests/workflows/0003_role_members_test.go b/tests/workflows/0003_role_members_test.go new file mode 100644 index 000000000..75d8d71a0 --- /dev/null +++ b/tests/workflows/0003_role_members_test.go @@ -0,0 +1,50 @@ +package workflows + +import ( + "context" + "testing" + + autTypes "github.com/cortezaproject/corteza-server/automation/types" + sysTypes "github.com/cortezaproject/corteza-server/system/types" + "github.com/stretchr/testify/require" +) + +func Test0003_role_members(t *testing.T) { + var ( + ctx = superUser(context.Background()) + req = require.New(t) + ) + + req.NoError(defStore.TruncateRoleMembers(ctx)) + req.NoError(defStore.TruncateUsers(ctx)) + req.NoError(defStore.TruncateRoles(ctx)) + + loadScenario(ctx, t) + + var ( + aux = struct { + UsersPre_r1 sysTypes.UserSet + TotalPre_r1 uint64 + UsersPre_r2 sysTypes.UserSet + TotalPre_r2 uint64 + + UsersPost_r1 sysTypes.UserSet + TotalPost_r1 uint64 + UsersPost_r2 sysTypes.UserSet + TotalPost_r2 uint64 + }{} + vars, _ = mustExecWorkflow(ctx, t, "role-members", autTypes.WorkflowExecParams{}) + ) + + req.NoError(vars.Decode(&aux)) + + req.Len(aux.UsersPre_r1, 1) + req.Equal(uint64(1), aux.TotalPre_r1) + req.Len(aux.UsersPre_r2, 1) + req.Equal(uint64(1), aux.TotalPre_r2) + + req.Len(aux.UsersPost_r1, 0) + req.Equal(uint64(0), aux.TotalPost_r1) + req.Len(aux.UsersPost_r2, 1) + req.Equal(uint64(1), aux.TotalPost_r2) +} diff --git a/tests/workflows/0004_user_membership_test.go b/tests/workflows/0004_user_membership_test.go new file mode 100644 index 000000000..af48aec3b --- /dev/null +++ b/tests/workflows/0004_user_membership_test.go @@ -0,0 +1,58 @@ +package workflows + +import ( + "context" + "testing" + + autTypes "github.com/cortezaproject/corteza-server/automation/types" + sysTypes "github.com/cortezaproject/corteza-server/system/types" + "github.com/stretchr/testify/require" +) + +func Test0004_user_membership(t *testing.T) { + var ( + ctx = superUser(context.Background()) + req = require.New(t) + ) + + req.NoError(defStore.TruncateRoleMembers(ctx)) + req.NoError(defStore.TruncateUsers(ctx)) + req.NoError(defStore.TruncateRoles(ctx)) + + loadScenario(ctx, t) + + var ( + aux = struct { + RolesPre_u1 sysTypes.RoleSet + TotalPre_u1 uint64 + RolesPre_u2 sysTypes.RoleSet + TotalPre_u2 uint64 + MemberPre_u1 bool + MemberPre_u2 bool + + RolesPost_u1 sysTypes.RoleSet + TotalPost_u1 uint64 + RolesPost_u2 sysTypes.RoleSet + TotalPost_u2 uint64 + MemberPost_u1 bool + MemberPost_u2 bool + }{} + vars, _ = mustExecWorkflow(ctx, t, "user-membership", autTypes.WorkflowExecParams{}) + ) + + req.NoError(vars.Decode(&aux)) + + req.Len(aux.RolesPre_u1, 2) + req.Equal(uint64(2), aux.TotalPre_u1) + req.True(aux.MemberPre_u1) + req.Len(aux.RolesPre_u2, 0) + req.Equal(uint64(0), aux.TotalPre_u2) + req.False(aux.MemberPre_u2) + + req.Len(aux.RolesPost_u1, 1) + req.Equal(uint64(1), aux.TotalPost_u1) + req.False(aux.MemberPost_u1) + req.Len(aux.RolesPost_u2, 0) + req.Equal(uint64(0), aux.TotalPost_u2) + req.False(aux.MemberPost_u2) +} diff --git a/tests/workflows/testdata/S0003_role_members/data_model.yaml b/tests/workflows/testdata/S0003_role_members/data_model.yaml new file mode 100644 index 000000000..f5ea109e7 --- /dev/null +++ b/tests/workflows/testdata/S0003_role_members/data_model.yaml @@ -0,0 +1,7 @@ +users: + u1: u1@example.tld + u2: u2@example.tld + +roles: + r1: Role1 + r2: Role2 diff --git a/tests/workflows/testdata/S0003_role_members/workflow.yaml b/tests/workflows/testdata/S0003_role_members/workflow.yaml new file mode 100644 index 000000000..554ce6b77 --- /dev/null +++ b/tests/workflows/testdata/S0003_role_members/workflow.yaml @@ -0,0 +1,74 @@ +workflows: + role-members: + enabled: true + trace: true + triggers: + - enabled: true + stepID: 10 + + steps: + # Adding membership + - stepID: 10 + kind: function + ref: rolesAddMember + arguments: + - { target: role, type: Handle, value: r1 } + - { target: user, type: Handle, value: u1 } + - stepID: 11 + kind: function + ref: rolesAddMember + arguments: + - { target: role, type: Handle, value: r2 } + - { target: user, type: Handle, value: u1 } + + # Get current state + - stepID: 20 + kind: function + ref: rolesSearchMembers + arguments: + - { target: lookup, type: Handle, value: r1 } + results: + - { target: usersPre_r1, expr: "users" } + - { target: totalPre_r1, expr: "total" } + - stepID: 21 + kind: function + ref: rolesSearchMembers + arguments: + - { target: lookup, type: Handle, value: r2 } + results: + - { target: usersPre_r2, expr: "users" } + - { target: totalPre_r2, expr: "total" } + + # Removing membership + - stepID: 30 + kind: function + ref: rolesRemoveMember + arguments: + - { target: role, type: Handle, value: r1 } + - { target: user, type: Handle, value: u1 } + + # Get final state + - stepID: 40 + kind: function + ref: rolesSearchMembers + arguments: + - { target: lookup, type: Handle, value: r1 } + results: + - { target: usersPost_r1, expr: "users" } + - { target: totalPost_r1, expr: "total" } + - stepID: 41 + kind: function + ref: rolesSearchMembers + arguments: + - { target: lookup, type: Handle, value: r2 } + results: + - { target: usersPost_r2, expr: "users" } + - { target: totalPost_r2, expr: "total" } + + paths: + - { parentID: 10, childID: 11 } + - { parentID: 11, childID: 20 } + - { parentID: 20, childID: 21 } + - { parentID: 21, childID: 30 } + - { parentID: 30, childID: 40 } + - { parentID: 40, childID: 41 } diff --git a/tests/workflows/testdata/S0004_user_membership/data_model.yaml b/tests/workflows/testdata/S0004_user_membership/data_model.yaml new file mode 100644 index 000000000..f5ea109e7 --- /dev/null +++ b/tests/workflows/testdata/S0004_user_membership/data_model.yaml @@ -0,0 +1,7 @@ +users: + u1: u1@example.tld + u2: u2@example.tld + +roles: + r1: Role1 + r2: Role2 diff --git a/tests/workflows/testdata/S0004_user_membership/workflow.yaml b/tests/workflows/testdata/S0004_user_membership/workflow.yaml new file mode 100644 index 000000000..c17a20638 --- /dev/null +++ b/tests/workflows/testdata/S0004_user_membership/workflow.yaml @@ -0,0 +1,115 @@ +workflows: + user-membership: + enabled: true + trace: true + triggers: + - enabled: true + stepID: 10 + + steps: + # Adding membership + - stepID: 10 + kind: function + ref: rolesAddMember + arguments: + - { target: role, type: Handle, value: r1 } + - { target: user, type: Handle, value: u1 } + - stepID: 11 + kind: function + ref: rolesAddMember + arguments: + - { target: role, type: Handle, value: r2 } + - { target: user, type: Handle, value: u1 } + + # Get current state + - stepID: 20 + kind: function + ref: usersSearchMembership + arguments: + - { target: lookup, type: Handle, value: u1 } + results: + - { target: rolesPre_u1, expr: "roles" } + - { target: totalPre_u1, expr: "total" } + - stepID: 21 + kind: function + ref: usersSearchMembership + arguments: + - { target: lookup, type: Handle, value: u2 } + results: + - { target: rolesPre_u2, expr: "roles" } + - { target: totalPre_u2, expr: "total" } + + - stepID: 22 + kind: function + ref: usersCheckMembership + arguments: + - { target: user, type: Handle, value: u1 } + - { target: role, type: Handle, value: r1 } + results: + - { target: memberPre_u1, expr: "member" } + - stepID: 23 + kind: function + ref: usersCheckMembership + arguments: + - { target: user, type: Handle, value: u2 } + - { target: role, type: Handle, value: r1 } + results: + - { target: memberPre_u2, expr: "member" } + + + # Removing membership + - stepID: 30 + kind: function + ref: rolesRemoveMember + arguments: + - { target: role, type: Handle, value: r1 } + - { target: user, type: Handle, value: u1 } + + # Get final state + - stepID: 40 + kind: function + ref: usersSearchMembership + arguments: + - { target: lookup, type: Handle, value: u1 } + results: + - { target: rolesPost_u1, expr: "roles" } + - { target: totalPost_u1, expr: "total" } + - stepID: 41 + kind: function + ref: usersSearchMembership + arguments: + - { target: lookup, type: Handle, value: u2 } + results: + - { target: rolesPost_u2, expr: "roles" } + - { target: totalPost_u2, expr: "total" } + + - stepID: 42 + kind: function + ref: usersCheckMembership + arguments: + - { target: user, type: Handle, value: u1 } + - { target: role, type: Handle, value: r1 } + results: + - { target: memberPost_u1, expr: "member" } + - stepID: 43 + kind: function + ref: usersCheckMembership + arguments: + - { target: user, type: Handle, value: u2 } + - { target: role, type: Handle, value: r1 } + results: + - { target: memberPost_u2, expr: "member" } + + paths: + - { parentID: 10, childID: 11 } + - { parentID: 11, childID: 20 } + - { parentID: 20, childID: 21 } + - { parentID: 21, childID: 22 } + - { parentID: 22, childID: 23 } + + - { parentID: 23, childID: 30 } + + - { parentID: 30, childID: 40 } + - { parentID: 40, childID: 41 } + - { parentID: 41, childID: 42 } + - { parentID: 42, childID: 43 }