Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/policy/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type Filter struct {
ProjectID string
GroupID string
RoleID string
RoleIDs []string

PrincipalType string
PrincipalID string
Expand Down
121 changes: 75 additions & 46 deletions internal/api/v1beta1/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,20 @@ package v1beta1
import (
"context"

"github.com/raystack/frontier/core/serviceuser"

"github.com/raystack/frontier/core/authenticate"

"go.uber.org/zap"

grpczap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
"github.com/pkg/errors"
"github.com/raystack/frontier/core/audit"
"github.com/raystack/frontier/core/role"
"github.com/raystack/frontier/pkg/pagination"
"github.com/raystack/frontier/pkg/utils"

"github.com/raystack/frontier/internal/bootstrap/schema"

"github.com/raystack/frontier/core/authenticate"
"github.com/raystack/frontier/core/policy"
"github.com/raystack/frontier/core/project"

"github.com/pkg/errors"
"github.com/raystack/frontier/core/role"
"github.com/raystack/frontier/core/serviceuser"
"github.com/raystack/frontier/core/user"
"github.com/raystack/frontier/internal/bootstrap/schema"
"github.com/raystack/frontier/pkg/metadata"

grpczap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
"github.com/raystack/frontier/pkg/pagination"
"github.com/raystack/frontier/pkg/utils"
"go.uber.org/zap"

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

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

func (h Handler) ListOrganizationUsers(ctx context.Context, request *frontierv1beta1.ListOrganizationUsersRequest) (*frontierv1beta1.ListOrganizationUsersResponse, error) {
if len(request.GetRoleFilters()) > 0 && request.GetWithRoles() {
return nil, status.Errorf(codes.InvalidArgument, "cannot use role filters and with_roles together")
}

logger := grpczap.Extract(ctx)
orgResp, err := h.orgService.Get(ctx, request.GetId())
if err != nil {
Expand All @@ -261,9 +259,67 @@ func (h Handler) ListOrganizationUsers(ctx context.Context, request *frontierv1b
}
}

users, err := h.userService.ListByOrg(ctx, orgResp.ID, request.GetPermissionFilter())
if err != nil {
return nil, err
var users []user.User
var rolePairPBs []*frontierv1beta1.ListOrganizationUsersResponse_RolePair

if len(request.GetRoleFilters()) > 0 {
// convert role names to ids if needed
roleIDs := request.GetRoleFilters()
for i, roleFilter := range request.GetRoleFilters() {
if !utils.IsValidUUID(roleFilter) {
role, err := h.roleService.Get(ctx, roleFilter)
if err != nil {
return nil, err
}
roleIDs[i] = role.ID
}
}

// need to fetch users with roles assigned to them
policies, err := h.policyService.List(ctx, policy.Filter{
OrgID: request.GetId(),
PrincipalType: schema.UserPrincipal,
ResourceType: schema.OrganizationNamespace,
RoleIDs: roleIDs,
})
if err != nil {
return nil, err
}
users = utils.Filter(utils.Map(policies, func(pol policy.Policy) user.User {
u, _ := h.userService.GetByID(ctx, pol.PrincipalID)
return u
}), func(u user.User) bool {
return u.ID != ""
})
} else {
// list all users
users, err = h.userService.ListByOrg(ctx, orgResp.ID, request.GetPermissionFilter())
if err != nil {
return nil, err
}
if request.GetWithRoles() {
for _, user := range users {
roles, err := h.policyService.ListRoles(ctx, schema.UserPrincipal, user.ID, schema.OrganizationNamespace, request.GetId())
if err != nil {
return nil, err
}

rolesPb := utils.Filter(utils.Map(roles, func(role role.Role) *frontierv1beta1.Role {
pb, err := transformRoleToPB(role)
if err != nil {
logger.Error("failed to transform role for group", zap.Error(err))
return nil
}
return &pb
}), func(role *frontierv1beta1.Role) bool {
return role != nil
})
rolePairPBs = append(rolePairPBs, &frontierv1beta1.ListOrganizationUsersResponse_RolePair{
UserId: user.ID,
Roles: rolesPb,
})
}
}
}

var usersPB []*frontierv1beta1.User
Expand All @@ -272,35 +328,8 @@ func (h Handler) ListOrganizationUsers(ctx context.Context, request *frontierv1b
if err != nil {
return nil, err
}

usersPB = append(usersPB, u)
}

var rolePairPBs []*frontierv1beta1.ListOrganizationUsersResponse_RolePair
if request.GetWithRoles() {
for _, user := range users {
roles, err := h.policyService.ListRoles(ctx, schema.UserPrincipal, user.ID, schema.OrganizationNamespace, request.GetId())
if err != nil {
return nil, err
}

rolesPb := utils.Filter(utils.Map(roles, func(role role.Role) *frontierv1beta1.Role {
pb, err := transformRoleToPB(role)
if err != nil {
logger.Error("failed to transform role for group", zap.Error(err))
return nil
}
return &pb
}), func(role *frontierv1beta1.Role) bool {
return role != nil
})
rolePairPBs = append(rolePairPBs, &frontierv1beta1.ListOrganizationUsersResponse_RolePair{
UserId: user.ID,
Roles: rolesPb,
})
}
}

return &frontierv1beta1.ListOrganizationUsersResponse{
Users: usersPB,
RolePairs: rolePairPBs,
Expand Down
5 changes: 5 additions & 0 deletions internal/store/postgres/policy_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ func applyListFilter(stmt *goqu.SelectDataset, flt policy.Filter) *goqu.SelectDa
"role_id": flt.RoleID,
})
}
if len(flt.RoleIDs) > 0 {
stmt = stmt.Where(goqu.Ex{
"role_id": flt.RoleIDs,
})
}
return stmt
}

Expand Down
82 changes: 79 additions & 3 deletions test/e2e/regression/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,51 @@ func (s *APIRegressionTestSuite) TestOrganizationAPI() {
})
s.Assert().NoError(err)
})
s.Run("6. a user should successfully list organization users via it's filter", func() {
createOrgResp, err := s.testBench.Client.CreateOrganization(ctxOrgAdminAuth, &frontierv1beta1.CreateOrganizationRequest{
Body: &frontierv1beta1.OrganizationRequestBody{
Title: "org acme 1-6",
Name: "org-acme-1-6",
},
})
s.Assert().NoError(err)

createUser1Resp, err := s.testBench.Client.CreateUser(ctxOrgAdminAuth, &frontierv1beta1.CreateUserRequest{
Body: &frontierv1beta1.UserRequestBody{
Email: "user-for-org-1-6-p1@raystack.org",
Name: "user-for-org-1-6-p1",
},
})
s.Assert().NoError(err)
s.Assert().NotNil(createUser1Resp)

// add user to org
_, err = s.testBench.Client.AddOrganizationUsers(ctxOrgAdminAuth, &frontierv1beta1.AddOrganizationUsersRequest{
Id: createOrgResp.GetOrganization().GetId(),
UserIds: []string{createUser1Resp.GetUser().GetId()},
})
s.Assert().NoError(err)

orgUsersResp, err := s.testBench.Client.ListOrganizationUsers(ctxOrgAdminAuth, &frontierv1beta1.ListOrganizationUsersRequest{
Id: createOrgResp.GetOrganization().GetId(),
})
s.Assert().NoError(err)
s.Assert().Equal(2, len(orgUsersResp.GetUsers()))
emails := utils.Map(orgUsersResp.GetUsers(), func(u *frontierv1beta1.User) string {
return u.GetEmail()
})
s.Assert().Contains(emails, createUser1Resp.GetUser().GetEmail())
s.Assert().Contains(emails, testbench.OrgAdminEmail)

// list only owner
orgUsersRespOwner, err := s.testBench.Client.ListOrganizationUsers(ctxOrgAdminAuth, &frontierv1beta1.ListOrganizationUsersRequest{
Id: createOrgResp.GetOrganization().GetId(),
RoleFilters: []string{schema.RoleOrganizationOwner},
})
s.Assert().NoError(err)
s.Assert().Equal(1, len(orgUsersRespOwner.GetUsers()))
s.Assert().Equal(testbench.OrgAdminEmail, orgUsersRespOwner.GetUsers()[0].GetEmail())
})
}

func (s *APIRegressionTestSuite) TestProjectAPI() {
Expand Down Expand Up @@ -501,12 +546,15 @@ func (s *APIRegressionTestSuite) TestProjectAPI() {
})
s.Assert().NoError(err)
s.Assert().NotNil(createUserResp)
createUserRespAuth := metadata.NewOutgoingContext(context.Background(), metadata.New(map[string]string{
testbench.IdentityHeader: createUserResp.GetUser().GetEmail(),
}))

// add user to project
_, err = s.testBench.Client.CreatePolicy(ctxOrgAdminAuth, &frontierv1beta1.CreatePolicyRequest{
Body: &frontierv1beta1.PolicyRequestBody{
_, err = s.testBench.Client.CreatePolicyForProject(ctxOrgAdminAuth, &frontierv1beta1.CreatePolicyForProjectRequest{
ProjectId: createProjectP1Response.GetProject().GetId(),
Body: &frontierv1beta1.CreatePolicyForProjectBody{
RoleId: schema.RoleProjectViewer,
Resource: schema.JoinNamespaceAndResourceID(schema.ProjectNamespace, createProjectP1Response.GetProject().GetId()),
Principal: schema.JoinNamespaceAndResourceID(schema.UserPrincipal, createUserResp.GetUser().GetId()),
},
})
Expand All @@ -527,6 +575,34 @@ func (s *APIRegressionTestSuite) TestProjectAPI() {
return p.GetName() == "org-project-2-p2"
}))

// viewer should only have get permission
listProjCurrentUsersResp, err = s.testBench.Client.ListProjectsByCurrentUser(createUserRespAuth, &frontierv1beta1.ListProjectsByCurrentUserRequest{
WithPermissions: []string{
"update",
"get",
"delete",
},
})
s.Assert().NoError(err)
s.Assert().True(slices.ContainsFunc[[]*frontierv1beta1.Project](listProjCurrentUsersResp.GetProjects(), func(p *frontierv1beta1.Project) bool {
return p.GetName() == "org-project-2-p1"
}))
s.Assert().Len(listProjCurrentUsersResp.GetAccessPairs(), 1)

// check permission for viewer
checkResourcePermissionResp, err := s.testBench.Client.CheckResourcePermission(createUserRespAuth, &frontierv1beta1.CheckResourcePermissionRequest{
Resource: schema.JoinNamespaceAndResourceID(schema.ProjectNamespace, createProjectP1Response.GetProject().GetId()),
Permission: schema.GetPermission,
})
s.Assert().NoError(err)
s.Assert().True(checkResourcePermissionResp.GetStatus())
checkResourcePermissionResp, err = s.testBench.Client.CheckResourcePermission(createUserRespAuth, &frontierv1beta1.CheckResourcePermissionRequest{
Resource: schema.JoinNamespaceAndResourceID(schema.ProjectNamespace, createProjectP1Response.GetProject().GetId()),
Permission: schema.UpdatePermission,
})
s.Assert().NoError(err)
s.Assert().False(checkResourcePermissionResp.GetStatus())

// create a group and add user to it
createGroupResp, err := s.testBench.Client.CreateGroup(ctxOrgAdminAuth, &frontierv1beta1.CreateGroupRequest{
OrgId: existingOrg.GetOrganization().GetId(),
Expand Down
Loading