Skip to content

Commit 2806058

Browse files
committed
feat: role filter while listing organization users
- raystack/proton#371 - a list of role name/ids are accepted as inputs, it can't be used when `with_roles` is toggled on. Signed-off-by: Kush Sharma <thekushsharma@gmail.com>
1 parent 89fbe30 commit 2806058

File tree

4 files changed

+160
-49
lines changed

4 files changed

+160
-49
lines changed

core/policy/filter.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ type Filter struct {
55
ProjectID string
66
GroupID string
77
RoleID string
8+
RoleIDs []string
89

910
PrincipalType string
1011
PrincipalID string

internal/api/v1beta1/org.go

Lines changed: 75 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,20 @@ package v1beta1
33
import (
44
"context"
55

6-
"github.com/raystack/frontier/core/serviceuser"
7-
8-
"github.com/raystack/frontier/core/authenticate"
9-
10-
"go.uber.org/zap"
11-
6+
grpczap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
7+
"github.com/pkg/errors"
128
"github.com/raystack/frontier/core/audit"
13-
"github.com/raystack/frontier/core/role"
14-
"github.com/raystack/frontier/pkg/pagination"
15-
"github.com/raystack/frontier/pkg/utils"
16-
17-
"github.com/raystack/frontier/internal/bootstrap/schema"
18-
9+
"github.com/raystack/frontier/core/authenticate"
10+
"github.com/raystack/frontier/core/policy"
1911
"github.com/raystack/frontier/core/project"
20-
21-
"github.com/pkg/errors"
12+
"github.com/raystack/frontier/core/role"
13+
"github.com/raystack/frontier/core/serviceuser"
2214
"github.com/raystack/frontier/core/user"
15+
"github.com/raystack/frontier/internal/bootstrap/schema"
2316
"github.com/raystack/frontier/pkg/metadata"
24-
25-
grpczap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
17+
"github.com/raystack/frontier/pkg/pagination"
18+
"github.com/raystack/frontier/pkg/utils"
19+
"go.uber.org/zap"
2620

2721
"github.com/raystack/frontier/core/organization"
2822

@@ -248,6 +242,10 @@ func (h Handler) ListOrganizationAdmins(ctx context.Context, request *frontierv1
248242
}
249243

250244
func (h Handler) ListOrganizationUsers(ctx context.Context, request *frontierv1beta1.ListOrganizationUsersRequest) (*frontierv1beta1.ListOrganizationUsersResponse, error) {
245+
if len(request.GetRoleFilters()) > 0 && request.GetWithRoles() {
246+
return nil, status.Errorf(codes.InvalidArgument, "cannot use role filters and with_roles together")
247+
}
248+
251249
logger := grpczap.Extract(ctx)
252250
orgResp, err := h.orgService.Get(ctx, request.GetId())
253251
if err != nil {
@@ -261,9 +259,67 @@ func (h Handler) ListOrganizationUsers(ctx context.Context, request *frontierv1b
261259
}
262260
}
263261

264-
users, err := h.userService.ListByOrg(ctx, orgResp.ID, request.GetPermissionFilter())
265-
if err != nil {
266-
return nil, err
262+
var users []user.User
263+
var rolePairPBs []*frontierv1beta1.ListOrganizationUsersResponse_RolePair
264+
265+
if len(request.GetRoleFilters()) > 0 {
266+
// convert role names to ids if needed
267+
roleIDs := request.GetRoleFilters()
268+
for i, roleFilter := range request.GetRoleFilters() {
269+
if !utils.IsValidUUID(roleFilter) {
270+
role, err := h.roleService.Get(ctx, roleFilter)
271+
if err != nil {
272+
return nil, err
273+
}
274+
roleIDs[i] = role.ID
275+
}
276+
}
277+
278+
// need to fetch users with roles assigned to them
279+
policies, err := h.policyService.List(ctx, policy.Filter{
280+
OrgID: request.GetId(),
281+
PrincipalType: schema.UserPrincipal,
282+
ResourceType: schema.OrganizationNamespace,
283+
RoleIDs: roleIDs,
284+
})
285+
if err != nil {
286+
return nil, err
287+
}
288+
users = utils.Filter(utils.Map(policies, func(pol policy.Policy) user.User {
289+
u, _ := h.userService.GetByID(ctx, pol.PrincipalID)
290+
return u
291+
}), func(u user.User) bool {
292+
return u.ID != ""
293+
})
294+
} else {
295+
// list all users
296+
users, err = h.userService.ListByOrg(ctx, orgResp.ID, request.GetPermissionFilter())
297+
if err != nil {
298+
return nil, err
299+
}
300+
if request.GetWithRoles() {
301+
for _, user := range users {
302+
roles, err := h.policyService.ListRoles(ctx, schema.UserPrincipal, user.ID, schema.OrganizationNamespace, request.GetId())
303+
if err != nil {
304+
return nil, err
305+
}
306+
307+
rolesPb := utils.Filter(utils.Map(roles, func(role role.Role) *frontierv1beta1.Role {
308+
pb, err := transformRoleToPB(role)
309+
if err != nil {
310+
logger.Error("failed to transform role for group", zap.Error(err))
311+
return nil
312+
}
313+
return &pb
314+
}), func(role *frontierv1beta1.Role) bool {
315+
return role != nil
316+
})
317+
rolePairPBs = append(rolePairPBs, &frontierv1beta1.ListOrganizationUsersResponse_RolePair{
318+
UserId: user.ID,
319+
Roles: rolesPb,
320+
})
321+
}
322+
}
267323
}
268324

269325
var usersPB []*frontierv1beta1.User
@@ -272,35 +328,8 @@ func (h Handler) ListOrganizationUsers(ctx context.Context, request *frontierv1b
272328
if err != nil {
273329
return nil, err
274330
}
275-
276331
usersPB = append(usersPB, u)
277332
}
278-
279-
var rolePairPBs []*frontierv1beta1.ListOrganizationUsersResponse_RolePair
280-
if request.GetWithRoles() {
281-
for _, user := range users {
282-
roles, err := h.policyService.ListRoles(ctx, schema.UserPrincipal, user.ID, schema.OrganizationNamespace, request.GetId())
283-
if err != nil {
284-
return nil, err
285-
}
286-
287-
rolesPb := utils.Filter(utils.Map(roles, func(role role.Role) *frontierv1beta1.Role {
288-
pb, err := transformRoleToPB(role)
289-
if err != nil {
290-
logger.Error("failed to transform role for group", zap.Error(err))
291-
return nil
292-
}
293-
return &pb
294-
}), func(role *frontierv1beta1.Role) bool {
295-
return role != nil
296-
})
297-
rolePairPBs = append(rolePairPBs, &frontierv1beta1.ListOrganizationUsersResponse_RolePair{
298-
UserId: user.ID,
299-
Roles: rolesPb,
300-
})
301-
}
302-
}
303-
304333
return &frontierv1beta1.ListOrganizationUsersResponse{
305334
Users: usersPB,
306335
RolePairs: rolePairPBs,

internal/store/postgres/policy_repository.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ func applyListFilter(stmt *goqu.SelectDataset, flt policy.Filter) *goqu.SelectDa
118118
"role_id": flt.RoleID,
119119
})
120120
}
121+
if len(flt.RoleIDs) > 0 {
122+
stmt = stmt.Where(goqu.Ex{
123+
"role_id": flt.RoleIDs,
124+
})
125+
}
121126
return stmt
122127
}
123128

test/e2e/regression/api_test.go

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,51 @@ func (s *APIRegressionTestSuite) TestOrganizationAPI() {
345345
})
346346
s.Assert().NoError(err)
347347
})
348+
s.Run("6. a user should successfully list organization users via it's filter", func() {
349+
createOrgResp, err := s.testBench.Client.CreateOrganization(ctxOrgAdminAuth, &frontierv1beta1.CreateOrganizationRequest{
350+
Body: &frontierv1beta1.OrganizationRequestBody{
351+
Title: "org acme 1-6",
352+
Name: "org-acme-1-6",
353+
},
354+
})
355+
s.Assert().NoError(err)
356+
357+
createUser1Resp, err := s.testBench.Client.CreateUser(ctxOrgAdminAuth, &frontierv1beta1.CreateUserRequest{
358+
Body: &frontierv1beta1.UserRequestBody{
359+
Email: "user-for-org-1-6-p1@raystack.org",
360+
Name: "user-for-org-1-6-p1",
361+
},
362+
})
363+
s.Assert().NoError(err)
364+
s.Assert().NotNil(createUser1Resp)
365+
366+
// add user to org
367+
_, err = s.testBench.Client.AddOrganizationUsers(ctxOrgAdminAuth, &frontierv1beta1.AddOrganizationUsersRequest{
368+
Id: createOrgResp.GetOrganization().GetId(),
369+
UserIds: []string{createUser1Resp.GetUser().GetId()},
370+
})
371+
s.Assert().NoError(err)
372+
373+
orgUsersResp, err := s.testBench.Client.ListOrganizationUsers(ctxOrgAdminAuth, &frontierv1beta1.ListOrganizationUsersRequest{
374+
Id: createOrgResp.GetOrganization().GetId(),
375+
})
376+
s.Assert().NoError(err)
377+
s.Assert().Equal(2, len(orgUsersResp.GetUsers()))
378+
emails := utils.Map(orgUsersResp.GetUsers(), func(u *frontierv1beta1.User) string {
379+
return u.GetEmail()
380+
})
381+
s.Assert().Contains(emails, createUser1Resp.GetUser().GetEmail())
382+
s.Assert().Contains(emails, testbench.OrgAdminEmail)
383+
384+
// list only owner
385+
orgUsersRespOwner, err := s.testBench.Client.ListOrganizationUsers(ctxOrgAdminAuth, &frontierv1beta1.ListOrganizationUsersRequest{
386+
Id: createOrgResp.GetOrganization().GetId(),
387+
RoleFilters: []string{schema.RoleOrganizationOwner},
388+
})
389+
s.Assert().NoError(err)
390+
s.Assert().Equal(1, len(orgUsersRespOwner.GetUsers()))
391+
s.Assert().Equal(testbench.OrgAdminEmail, orgUsersRespOwner.GetUsers()[0].GetEmail())
392+
})
348393
}
349394

350395
func (s *APIRegressionTestSuite) TestProjectAPI() {
@@ -501,12 +546,15 @@ func (s *APIRegressionTestSuite) TestProjectAPI() {
501546
})
502547
s.Assert().NoError(err)
503548
s.Assert().NotNil(createUserResp)
549+
createUserRespAuth := metadata.NewOutgoingContext(context.Background(), metadata.New(map[string]string{
550+
testbench.IdentityHeader: createUserResp.GetUser().GetEmail(),
551+
}))
504552

505553
// add user to project
506-
_, err = s.testBench.Client.CreatePolicy(ctxOrgAdminAuth, &frontierv1beta1.CreatePolicyRequest{
507-
Body: &frontierv1beta1.PolicyRequestBody{
554+
_, err = s.testBench.Client.CreatePolicyForProject(ctxOrgAdminAuth, &frontierv1beta1.CreatePolicyForProjectRequest{
555+
ProjectId: createProjectP1Response.GetProject().GetId(),
556+
Body: &frontierv1beta1.CreatePolicyForProjectBody{
508557
RoleId: schema.RoleProjectViewer,
509-
Resource: schema.JoinNamespaceAndResourceID(schema.ProjectNamespace, createProjectP1Response.GetProject().GetId()),
510558
Principal: schema.JoinNamespaceAndResourceID(schema.UserPrincipal, createUserResp.GetUser().GetId()),
511559
},
512560
})
@@ -527,6 +575,34 @@ func (s *APIRegressionTestSuite) TestProjectAPI() {
527575
return p.GetName() == "org-project-2-p2"
528576
}))
529577

578+
// viewer should only have get permission
579+
listProjCurrentUsersResp, err = s.testBench.Client.ListProjectsByCurrentUser(createUserRespAuth, &frontierv1beta1.ListProjectsByCurrentUserRequest{
580+
WithPermissions: []string{
581+
"update",
582+
"get",
583+
"delete",
584+
},
585+
})
586+
s.Assert().NoError(err)
587+
s.Assert().True(slices.ContainsFunc[[]*frontierv1beta1.Project](listProjCurrentUsersResp.GetProjects(), func(p *frontierv1beta1.Project) bool {
588+
return p.GetName() == "org-project-2-p1"
589+
}))
590+
s.Assert().Len(listProjCurrentUsersResp.GetAccessPairs(), 1)
591+
592+
// check permission for viewer
593+
checkResourcePermissionResp, err := s.testBench.Client.CheckResourcePermission(createUserRespAuth, &frontierv1beta1.CheckResourcePermissionRequest{
594+
Resource: schema.JoinNamespaceAndResourceID(schema.ProjectNamespace, createProjectP1Response.GetProject().GetId()),
595+
Permission: schema.GetPermission,
596+
})
597+
s.Assert().NoError(err)
598+
s.Assert().True(checkResourcePermissionResp.GetStatus())
599+
checkResourcePermissionResp, err = s.testBench.Client.CheckResourcePermission(createUserRespAuth, &frontierv1beta1.CheckResourcePermissionRequest{
600+
Resource: schema.JoinNamespaceAndResourceID(schema.ProjectNamespace, createProjectP1Response.GetProject().GetId()),
601+
Permission: schema.UpdatePermission,
602+
})
603+
s.Assert().NoError(err)
604+
s.Assert().False(checkResourcePermissionResp.GetStatus())
605+
530606
// create a group and add user to it
531607
createGroupResp, err := s.testBench.Client.CreateGroup(ctxOrgAdminAuth, &frontierv1beta1.CreateGroupRequest{
532608
OrgId: existingOrg.GetOrganization().GetId(),

0 commit comments

Comments
 (0)