-
Notifications
You must be signed in to change notification settings - Fork 943
test: Unit test to assert role capabilities #1781
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,249 @@ | ||
package rbac_test | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/google/uuid" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/coder/coder/coderd/rbac" | ||
) | ||
|
||
type authSubject struct { | ||
// Name is helpful for test assertions | ||
Name string | ||
UserID string | ||
Roles []string | ||
} | ||
|
||
func TestRolePermissions(t *testing.T) { | ||
t.Parallel() | ||
|
||
auth, err := rbac.NewAuthorizer() | ||
require.NoError(t, err, "new rego authorizer") | ||
|
||
meID := uuid.New() | ||
adminID := uuid.New() | ||
orgID := uuid.New() | ||
otherOrg := uuid.New() | ||
|
||
// Subjects to user | ||
member := authSubject{Name: "member", UserID: meID.String(), Roles: []string{rbac.RoleMember()}} | ||
admin := authSubject{Name: "admin", UserID: adminID.String(), Roles: []string{rbac.RoleMember(), rbac.RoleAdmin()}} | ||
|
||
orgMember := authSubject{Name: "org_member", UserID: meID.String(), Roles: []string{rbac.RoleMember(), rbac.RoleOrgMember(orgID)}} | ||
orgAdmin := authSubject{Name: "org_admin", UserID: adminID.String(), Roles: []string{rbac.RoleMember(), rbac.RoleOrgMember(orgID), rbac.RoleOrgAdmin(orgID)}} | ||
otherOrgMember := authSubject{Name: "other_org_member", UserID: uuid.NewString(), Roles: []string{rbac.RoleMember(), rbac.RoleOrgMember(otherOrg)}} | ||
otherOrgAdmin := authSubject{Name: "other_org_admin", UserID: uuid.NewString(), Roles: []string{rbac.RoleMember(), rbac.RoleOrgMember(otherOrg), rbac.RoleOrgAdmin(otherOrg)}} | ||
|
||
// requiredSubjects are required to be asserted in each test case. This is | ||
// to make sure one is not forgotten. | ||
requiredSubjects := []authSubject{member, admin, orgMember, orgAdmin, otherOrgAdmin, otherOrgMember} | ||
|
||
testCases := []struct { | ||
// Name the test case to better locate the failing test case. | ||
Name string | ||
Resource rbac.Object | ||
Actions []rbac.Action | ||
// Assertions must cover all subjects in 'requiredSubjects' | ||
Assertions map[bool][]authSubject | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I felt it was easier to just list out subjects in a 1 line list. |
||
}{ | ||
{ | ||
Name: "MyUser", | ||
Actions: []rbac.Action{rbac.ActionRead}, | ||
Resource: rbac.ResourceUser.WithID(meID.String()), | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin, member, orgMember, orgAdmin, otherOrgMember, otherOrgAdmin}, | ||
false: {}, | ||
}, | ||
}, | ||
{ | ||
Name: "AUser", | ||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete}, | ||
Resource: rbac.ResourceUser, | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin}, | ||
false: {member, orgMember, orgAdmin, otherOrgMember, otherOrgAdmin}, | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Could we rename these fields for clarity? When I initially read the test I got a bit confused, as it appears to me that this test was asserting that any random user resource should have the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct. I can rename. I changed this struct up a few times, probably why it ended as it did. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added comments too. |
||
}, | ||
{ | ||
Name: "MyWorkspaceInOrg", | ||
// When creating the WithID won't be set, but it does not change the result. | ||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete}, | ||
Resource: rbac.ResourceWorkspace.InOrg(orgID).WithOwner(meID.String()).WithID(uuid.NewString()), | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin, orgMember, orgAdmin}, | ||
false: {member, otherOrgAdmin, otherOrgMember}, | ||
}, | ||
}, | ||
{ | ||
Name: "Templates", | ||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete}, | ||
Resource: rbac.ResourceTemplate.InOrg(orgID).WithID(uuid.NewString()), | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin, orgAdmin}, | ||
false: {member, orgMember, otherOrgAdmin, otherOrgMember}, | ||
}, | ||
}, | ||
{ | ||
Name: "ReadTemplates", | ||
Actions: []rbac.Action{rbac.ActionRead}, | ||
Resource: rbac.ResourceTemplate.InOrg(orgID).WithID(uuid.NewString()), | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin, orgMember, orgAdmin}, | ||
false: {member, otherOrgAdmin, otherOrgMember}, | ||
}, | ||
}, | ||
{ | ||
Name: "Files", | ||
Actions: []rbac.Action{rbac.ActionCreate}, | ||
Resource: rbac.ResourceFile, | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin}, | ||
false: {orgMember, orgAdmin, member, otherOrgAdmin, otherOrgMember}, | ||
}, | ||
}, | ||
{ | ||
Name: "MyFile", | ||
Actions: []rbac.Action{rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete}, | ||
Resource: rbac.ResourceFile.WithID(uuid.NewString()).WithOwner(meID.String()), | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin, member, orgMember}, | ||
false: {orgAdmin, otherOrgAdmin, otherOrgMember}, | ||
}, | ||
}, | ||
{ | ||
Name: "CreateOrganizations", | ||
Actions: []rbac.Action{rbac.ActionCreate}, | ||
Resource: rbac.ResourceOrganization, | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin}, | ||
false: {orgAdmin, otherOrgAdmin, otherOrgMember, member, orgMember}, | ||
}, | ||
}, | ||
{ | ||
Name: "Organizations", | ||
Actions: []rbac.Action{rbac.ActionUpdate, rbac.ActionDelete}, | ||
Resource: rbac.ResourceOrganization.InOrg(orgID).WithID(orgID.String()), | ||
Assertions: map[bool][]authSubje 10000 ct{ | ||
true: {admin, orgAdmin}, | ||
false: {otherOrgAdmin, otherOrgMember, member, orgMember}, | ||
}, | ||
}, | ||
{ | ||
Name: "ReadOrganizations", | ||
Actions: []rbac.Action{rbac.ActionRead}, | ||
Resource: rbac.ResourceOrganization.InOrg(orgID).WithID(orgID.String()), | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin, orgAdmin, orgMember}, | ||
false: {otherOrgAdmin, otherOrgMember, member}, | ||
}, | ||
}, | ||
{ | ||
Name: "RoleAssignment", | ||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete}, | ||
Resource: rbac.ResourceRoleAssignment, | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin}, | ||
false: {orgAdmin, orgMember, otherOrgAdmin, otherOrgMember, member}, | ||
}, | ||
}, | ||
{ | ||
Name: "ReadRoleAssignment", | ||
Actions: []rbac.Action{rbac.ActionRead}, | ||
Resource: rbac.ResourceRoleAssignment, | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin, orgAdmin, orgMember, otherOrgAdmin, otherOrgMember, member}, | ||
false: {}, | ||
}, | ||
}, | ||
{ | ||
Name: "OrgRoleAssignment", | ||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete}, | ||
Resource: rbac.ResourceOrgRoleAssignment.InOrg(orgID), | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin, orgAdmin}, | ||
false: {orgMember, otherOrgAdmin, otherOrgMember, member}, | ||
}, | ||
}, | ||
{ | ||
Name: "ReadOrgRoleAssignment", | ||
Actions: []rbac.Action{rbac.ActionRead}, | ||
Resource: rbac.ResourceOrgRoleAssignment.InOrg(orgID), | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin, orgAdmin, orgMember}, | ||
false: {otherOrgAdmin, otherOrgMember, member}, | ||
}, | ||
}, | ||
{ | ||
Name: "APIKey", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably clarify that the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is. I changed |
||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete}, | ||
Resource: rbac.ResourceAPIKey.WithOwner(meID.String()).WithID(uuid.NewString()), | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin, orgMember, member}, | ||
false: {orgAdmin, otherOrgAdmin, otherOrgMember}, | ||
}, | ||
}, | ||
{ | ||
Name: "UserData", | ||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete}, | ||
Resource: rbac.ResourceUserData.WithOwner(meID.String()).WithID(meID.String()), | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin, orgMember, member}, | ||
false: {orgAdmin, otherOrgAdmin, otherOrgMember}, | ||
}, | ||
}, | ||
{ | ||
Name: "ManageOrgMember", | ||
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete}, | ||
Resource: rbac.ResourceOrganizationMember.InOrg(orgID).WithID(uuid.NewString()), | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin, orgAdmin}, | ||
false: {orgMember, member, otherOrgAdmin, otherOrgMember}, | ||
}, | ||
}, | ||
{ | ||
Name: "ReadOrgMember", | ||
Actions: []rbac.Action{rbac.ActionRead}, | ||
Resource: rbac.ResourceOrganizationMember.InOrg(orgID).WithID(uuid.NewString()), | ||
Assertions: map[bool][]authSubject{ | ||
true: {admin, orgAdmin, orgMember}, | ||
false: {member, otherOrgAdmin, otherOrgMember}, | ||
}, | ||
}, | ||
} | ||
|
||
for _, c := range testCases { | ||
c := c | ||
t.Run(c.Name, func(t *testing.T) { | ||
t.Parallel() | ||
remainingSubjs := make(map[string]struct{}) | ||
for _, subj := range requiredSubjects { | ||
remainingSubjs[subj.Name] = struct{}{} | ||
} | ||
|
||
for _, action := range c.Actions { | ||
for result, subjs := range c.Assertions { | ||
for _, subj := range subjs { | ||
delete(remainingSubjs, subj.Name) | ||
msg := fmt.Sprintf("%s as %q doing %q on %q", c.Name, subj.Name, action, c.Resource.Type) | ||
err := auth.ByRoleName(context.Background(), subj.UserID, subj.Roles, action, c.Resource) | ||
if result { | ||
assert.NoError(t, err, fmt.Sprintf("Should pass: %s", msg)) | ||
} else { | ||
assert.ErrorContains(t, err, "forbidden", fmt.Sprintf("Should fail: %s", msg)) | ||
} | ||
} | ||
} | ||
} | ||
require.Empty(t, remainingSubjs, "test should cover all subjects") | ||
}) | ||
} | ||
} | ||
|
||
func TestIsOrgRole(t *testing.T) { | ||
t.Parallel() | ||
randomUUID := uuid.New() | ||
|
Uh oh!
There was an error while loading. Please reload this page.