Skip to content

Commit

Permalink
settings: Add ListRoleAssignmentByRole
Browse files Browse the repository at this point in the history
This adds the implementation for ListRolesAssignments by role-id to the
metadata backend. Because of the current layout of the account folders
and assignment files this is currently still very inefficient.

Related Issue: #8939
  • Loading branch information
rhafer committed Jun 4, 2024
1 parent 90f7cc2 commit d7f10f3
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 4 deletions.
3 changes: 2 additions & 1 deletion services/settings/pkg/service/v0/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@ func (g Service) ListRoleAssignmentsFiltered(ctx context.Context, req *settingss
accountUUID := getValidatedAccountUUID(ctx, filters[0].GetAccountUuid())
r, err = g.manager.ListRoleAssignments(accountUUID)
case settingsmsg.UserRoleAssignmentFilter_TYPE_ROLE:
err = fmt.Errorf("filtering by role not implemented")
roleID := filters[0].GetRoleId()
r, err = g.manager.ListRoleAssignmentsByRole(roleID)
}
if err != nil {
return merrors.NotFound(g.id, "%s", err)
Expand Down
116 changes: 115 additions & 1 deletion services/settings/pkg/service/v0/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ func TestListPermissionsOfOtherUser(t *testing.T) {
assert.Contains(t, err.Error(), req.AccountUuid)
}

func TestListRoleAssignmentsFiltered(t *testing.T) {
func TestListRoleAssignmentsFilteredValidation(t *testing.T) {
manager := &mocks.Manager{}
svc := Service{
manager: manager,
Expand Down Expand Up @@ -336,3 +336,117 @@ func TestListRoleAssignmentsFiltered(t *testing.T) {
})
}
}

func TestListRoleAssignmentsFilteredByAccount(t *testing.T) {
accountUUID := "61445573-4dbe-4d56-88dc-88ab47aceba7"

tests := map[string]struct {
result []*settingsmsg.UserRoleAssignment
err error
status int32
}{
"handles manager error": {
result: nil,
err: assert.AnError,
status: http.StatusNotFound,
},
"succeeds with results": {
result: []*settingsmsg.UserRoleAssignment{
{
Id: "00000000-0000-0000-0000-000000000001",
AccountUuid: accountUUID,
RoleId: "aceb15b8-7486-479f-ae32-c91118e07a39",
},
},
err: nil,
status: http.StatusOK,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
manager := &mocks.Manager{}
svc := Service{
manager: manager,
}
manager.On("ListRoleAssignments", mock.Anything).Return(test.result, test.err)
req := &v0.ListRoleAssignmentsFilteredRequest{
Filters: []*settingsmsg.UserRoleAssignmentFilter{
{
Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ACCOUNT,
Term: &settingsmsg.UserRoleAssignmentFilter_AccountUuid{
AccountUuid: accountUUID,
},
},
},
}
res := v0.ListRoleAssignmentsResponse{}
err := svc.ListRoleAssignmentsFiltered(ctxWithUUID, req, &res)
switch test.err {
case nil:
assert.Nil(t, err)
default:
merr, ok := merrors.As(err)
assert.True(t, ok)
assert.Equal(t, int32(test.status), merr.Code)
}
})
}
}

func TestListRoleAssignmentsFilteredByRole(t *testing.T) {
roleID := "61445573-4dbe-4d56-88dc-88ab47aceba7"

tests := map[string]struct {
result []*settingsmsg.UserRoleAssignment
err error
status int32
}{
"handles manager error": {
result: nil,
err: assert.AnError,
status: http.StatusNotFound,
},
"succeeds with results": {
result: []*settingsmsg.UserRoleAssignment{
{
Id: "00000000-0000-0000-0000-000000000001",
AccountUuid: "aceb15b8-7486-479f-ae32-c91118e07a39",
RoleId: roleID,
},
},
err: nil,
status: http.StatusOK,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
manager := &mocks.Manager{}
svc := Service{
manager: manager,
}
manager.On("ListRoleAssignmentsByRole", mock.Anything).Return(test.result, test.err)
req := &v0.ListRoleAssignmentsFilteredRequest{
Filters: []*settingsmsg.UserRoleAssignmentFilter{
{
Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ROLE,
Term: &settingsmsg.UserRoleAssignmentFilter_RoleId{
RoleId: roleID,
},
},
},
}
res := v0.ListRoleAssignmentsResponse{}
err := svc.ListRoleAssignmentsFiltered(ctxWithUUID, req, &res)
switch test.err {
case nil:
assert.Nil(t, err)
default:
merr, ok := merrors.As(err)
assert.True(t, ok)
assert.Equal(t, int32(test.status), merr.Code)
}
})
}
}
58 changes: 58 additions & 0 deletions services/settings/pkg/settings/mocks/manager.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions services/settings/pkg/settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type ValueManager interface {
// RoleAssignmentManager is a role assignment service interface for abstraction of storage implementations
type RoleAssignmentManager interface {
ListRoleAssignments(accountUUID string) ([]*settingsmsg.UserRoleAssignment, error)
ListRoleAssignmentsByRole(roleID string) ([]*settingsmsg.UserRoleAssignment, error)
WriteRoleAssignment(accountUUID, roleID string) (*settingsmsg.UserRoleAssignment, error)
RemoveRoleAssignment(assignmentID string) error
}
Expand Down
53 changes: 53 additions & 0 deletions services/settings/pkg/store/metadata/assignments.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,59 @@ func (s *Store) ListRoleAssignments(accountUUID string) ([]*settingsmsg.UserRole
return ass, nil
}

// ListRoleAssignmentsByRole returns all role assignmentes matching the give roleID
func (s *Store) ListRoleAssignmentsByRole(roleID string) ([]*settingsmsg.UserRoleAssignment, error) {
s.Init()
ctx := context.TODO()
accountIDs, err := s.mdc.ReadDir(ctx, accountsFolderLocation)
switch err.(type) {
case nil:
// continue
case errtypes.NotFound:
return make([]*settingsmsg.UserRoleAssignment, 0), nil
default:
return nil, err
}
assignments := make([]*settingsmsg.UserRoleAssignment, 0, len(accountIDs))

// This is very inefficient, with the current layout we need to iterated through all
// account folders and read each assignment file in there to check if that contains
// the give role ID.
for _, account := range accountIDs {
assignmentIDs, err := s.mdc.ReadDir(ctx, accountPath(account))
switch err.(type) {
case nil:
// continue
case errtypes.NotFound:
return make([]*settingsmsg.UserRoleAssignment, 0), nil
default:
return nil, err
}

for _, assignmentID := range assignmentIDs {
b, err := s.mdc.SimpleDownload(ctx, assignmentPath(account, assignmentID))
switch err.(type) {
case nil:
// continue
case errtypes.NotFound:
continue
default:
return nil, err
}

a := &settingsmsg.UserRoleAssignment{}
err = json.Unmarshal(b, a)
if err != nil {
return nil, err
}
if a.GetRoleId() == roleID {
assignments = append(assignments, a)
}
}
}
return assignments, nil
}

// WriteRoleAssignment appends the given role assignment to the existing assignments of the respective account.
func (s *Store) WriteRoleAssignment(accountUUID, roleID string) (*settingsmsg.UserRoleAssignment, error) {
s.Init()
Expand Down
81 changes: 79 additions & 2 deletions services/settings/pkg/store/metadata/assignments_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ import (
)

var (
einstein = "a4d07560-a670-4be9-8d60-9b547751a208"
//marie = "3c054db3-eec1-4ca4-b985-bc56dcf560cb"
einstein = "00000000-0000-0000-0000-000000000001"
marie = "00000000-0000-0000-0000-000000000002"
moss = "00000000-0000-0000-0000-000000000003"

role1 = "11111111-1111-1111-1111-111111111111"
role2 = "22222222-2222-2222-2222-222222222222"

s = &Store{
Logger: logger,
Expand Down Expand Up @@ -149,6 +153,79 @@ func TestAssignmentUniqueness(t *testing.T) {
}
}

func TestListRoleAssignmentByRole(t *testing.T) {
type assignment struct {
userID string
roleID string
}

var scenarios = map[string]struct {
assignments []assignment
queryRole string
numResults int
}{
"just 2 assignments": {
assignments: []assignment{
{
userID: einstein,
roleID: role1,
}, {
userID: marie,
roleID: role1,
},
},
queryRole: role1,
numResults: 2,
},
"no assignments match": {
assignments: []assignment{
{
userID: einstein,
roleID: role1,
}, {
userID: marie,
roleID: role1,
},
},
queryRole: role2,
numResults: 0,
},
"only one assignment matches": {
assignments: []assignment{
{
userID: einstein,
roleID: role1,
}, {
userID: marie,
roleID: role1,
}, {
userID: moss,
roleID: role2,
},
},
queryRole: role2,
numResults: 1,
},
}

for name, scenario := range scenarios {
scenario := scenario
t.Run(name, func(t *testing.T) {
for _, a := range scenario.assignments {
ass, err := s.WriteRoleAssignment(a.userID, a.roleID)
require.NoError(t, err)
require.Equal(t, ass.RoleId, a.roleID)
}

list, err := s.ListRoleAssignmentsByRole(scenario.queryRole)
require.NoError(t, err)
require.Equal(t, scenario.numResults, len(list))
for _, ass := range list {
require.Equal(t, ass.RoleId, scenario.queryRole)
}
})
}
}
func TestDeleteAssignment(t *testing.T) {
var scenarios = []struct {
name string
Expand Down

0 comments on commit d7f10f3

Please sign in to comment.