chore: Allow RecordingAuthorizer to record multiple rbac authz calls (#6024)

* chore: Allow RecordingAuthorizer to record multiple rbac authz calls

Prior iteration only recorded the last call. This is required for
more comprehensive testing
This commit is contained in:
Steven Masley
2023-02-03 19:03:46 +00:00
committed by GitHub
parent 571f5d0e02
commit b359dbbd8b
9 changed files with 720 additions and 53 deletions
+20
View File
@@ -15,6 +15,7 @@ import (
"github.com/coder/coder/coderd/rbac/regosql"
"github.com/coder/coder/coderd/tracing"
"github.com/coder/coder/coderd/util/slice"
)
// Subject is a struct that contains all the elements of a subject in an rbac
@@ -26,6 +27,25 @@ type Subject struct {
Scope ExpandableScope
}
func (s Subject) Equal(b Subject) bool {
if s.ID != b.ID {
return false
}
if !slice.SameElements(s.Groups, b.Groups) {
return false
}
if !slice.SameElements(s.SafeRoleNames(), b.SafeRoleNames()) {
return false
}
if s.SafeScopeName() != b.SafeScopeName() {
return false
}
return true
}
// SafeScopeName prevent nil pointer dereference.
func (s Subject) SafeScopeName() string {
if s.Scope == nil {
+43
View File
@@ -176,6 +176,49 @@ type Object struct {
ACLGroupList map[string][]Action ` json:"acl_group_list"`
}
func (z Object) Equal(b Object) bool {
if z.ID != b.ID {
return false
}
if z.Owner != b.Owner {
return false
}
if z.OrgID != b.OrgID {
return false
}
if z.Type != b.Type {
return false
}
if !equalACLLists(z.ACLUserList, b.ACLUserList) {
return false
}
if !equalACLLists(z.ACLGroupList, b.ACLGroupList) {
return false
}
return true
}
func equalACLLists(a, b map[string][]Action) bool {
if len(a) != len(b) {
return false
}
for k, actions := range a {
if len(actions) != len(b[k]) {
return false
}
for i, a := range actions {
if a != b[k][i] {
return false
}
}
}
return true
}
func (z Object) RBACObject() Object {
return z
}
+176
View File
@@ -0,0 +1,176 @@
package rbac_test
import (
"testing"
"github.com/coder/coder/coderd/rbac"
)
func TestObjectEqual(t *testing.T) {
t.Parallel()
testCases := []struct {
Name string
A rbac.Object
B rbac.Object
Expected bool
}{
{
Name: "Empty",
A: rbac.Object{},
B: rbac.Object{},
Expected: true,
},
{
Name: "NilVs0",
A: rbac.Object{
ACLGroupList: map[string][]rbac.Action{},
ACLUserList: map[string][]rbac.Action{},
},
B: rbac.Object{},
Expected: true,
},
{
Name: "Same",
A: rbac.Object{
ID: "id",
Owner: "owner",
OrgID: "orgID",
Type: "type",
ACLUserList: map[string][]rbac.Action{},
ACLGroupList: map[string][]rbac.Action{},
},
B: rbac.Object{
ID: "id",
Owner: "owner",
OrgID: "orgID",
Type: "type",
ACLUserList: map[string][]rbac.Action{},
ACLGroupList: map[string][]rbac.Action{},
},
Expected: true,
},
{
Name: "DifferentID",
A: rbac.Object{
ID: "id",
},
B: rbac.Object{
ID: "id2",
},
Expected: false,
},
{
Name: "DifferentOwner",
A: rbac.Object{
Owner: "owner",
},
B: rbac.Object{
Owner: "owner2",
},
Expected: false,
},
{
Name: "DifferentOrgID",
A: rbac.Object{
OrgID: "orgID",
},
B: rbac.Object{
OrgID: "orgID2",
},
Expected: false,
},
{
Name: "DifferentType",
A: rbac.Object{
Type: "type",
},
B: rbac.Object{
Type: "type2",
},
Expected: false,
},
{
Name: "DifferentACLUserList",
A: rbac.Object{
ACLUserList: map[string][]rbac.Action{
"user1": {rbac.ActionRead},
},
},
B: rbac.Object{
ACLUserList: map[string][]rbac.Action{
"user2": {rbac.ActionRead},
},
},
Expected: false,
},
{
Name: "ACLUserDiff#Actions",
A: rbac.Object{
ACLUserList: map[string][]rbac.Action{
"user1": {rbac.ActionRead},
},
},
B: rbac.Object{
ACLUserList: map[string][]rbac.Action{
"user1": {rbac.ActionRead, rbac.ActionUpdate},
},
},
Expected: false,
},
{
Name: "ACLUserDiffAction",
A: rbac.Object{
ACLUserList: map[string][]rbac.Action{
"user1": {rbac.ActionRead},
},
},
B: rbac.Object{
ACLUserList: map[string][]rbac.Action{
"user1": {rbac.ActionUpdate},
},
},
Expected: false,
},
{
Name: "ACLUserDiff#Users",
A: rbac.Object{
ACLUserList: map[string][]rbac.Action{
"user1": {rbac.ActionRead},
},
},
B: rbac.Object{
ACLUserList: map[string][]rbac.Action{
"user1": {rbac.ActionRead},
"user2": {rbac.ActionRead},
},
},
Expected: false,
},
{
Name: "DifferentACLGroupList",
A: rbac.Object{
ACLGroupList: map[string][]rbac.Action{
"group1": {rbac.ActionRead},
},
},
B: rbac.Object{
ACLGroupList: map[string][]rbac.Action{
"group2": {rbac.ActionRead},
},
},
Expected: false,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
actual := tc.A.Equal(tc.B)
if actual != tc.Expected {
t.Errorf("expected %v, got %v", tc.Expected, actual)
}
})
}
}
+132
View File
@@ -0,0 +1,132 @@
package rbac_test
import (
"testing"
"github.com/coder/coder/coderd/rbac"
)
func TestSubjectEqual(t *testing.T) {
t.Parallel()
testCases := []struct {
Name string
A rbac.Subject
B rbac.Subject
Expected bool
}{
{
Name: "Empty",
A: rbac.Subject{},
B: rbac.Subject{},
Expected: true,
},
{
Name: "Same",
A: rbac.Subject{
ID: "id",
Roles: rbac.RoleNames{rbac.RoleMember()},
Groups: []string{"group"},
Scope: rbac.ScopeAll,
},
B: rbac.Subject{
ID: "id",
Roles: rbac.RoleNames{rbac.RoleMember()},
Groups: []string{"group"},
Scope: rbac.ScopeAll,
},
Expected: true,
},
{
Name: "DifferentID",
A: rbac.Subject{
ID: "id",
},
B: rbac.Subject{
ID: "id2",
},
Expected: false,
},
{
Name: "RolesNilVs0",
A: rbac.Subject{
Roles: rbac.RoleNames{},
},
B: rbac.Subject{
Roles: nil,
},
Expected: true,
},
{
Name: "GroupsNilVs0",
A: rbac.Subject{
Groups: []string{},
},
B: rbac.Subject{
Groups: nil,
},
Expected: true,
},
{
Name: "DifferentRoles",
A: rbac.Subject{
Roles: rbac.RoleNames{rbac.RoleMember()},
},
B: rbac.Subject{
Roles: rbac.RoleNames{rbac.RoleOwner()},
},
Expected: false,
},
{
Name: "Different#Roles",
A: rbac.Subject{
Roles: rbac.RoleNames{rbac.RoleMember()},
},
B: rbac.Subject{
Roles: rbac.RoleNames{rbac.RoleMember(), rbac.RoleOwner()},
},
Expected: false,
},
{
Name: "DifferentGroups",
A: rbac.Subject{
Groups: []string{"group1"},
},
B: rbac.Subject{
Groups: []string{"group2"},
},
Expected: false,
},
{
Name: "Different#Groups",
A: rbac.Subject{
Groups: []string{"group1"},
},
B: rbac.Subject{
Groups: []string{"group1", "group2"},
},
Expected: false,
},
{
Name: "DifferentScope",
A: rbac.Subject{
Scope: rbac.ScopeAll,
},
B: rbac.Subject{
Scope: rbac.ScopeApplicationConnect,
},
Expected: false,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
actual := tc.A.Equal(tc.B)
if actual != tc.Expected {
t.Errorf("expected %v, got %v", tc.Expected, actual)
}
})
}
}