Skip to content

test: Add unit test for rbac Authorize() function #853

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

Closed
wants to merge 44 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ab61328
WIP: This is a massive WIP
Emyrk Mar 26, 2022
03e4d0f
More info in the print
Emyrk Mar 26, 2022
9981291
Fix all()
Emyrk Mar 27, 2022
3ab32da
reduce the amount of memoery allocated
Emyrk Mar 27, 2022
e1d5893
Reuse a buffer
Emyrk Mar 28, 2022
84a90f3
fix: use return size over size
Emyrk Mar 29, 2022
1fac0d9
WIP: don't look at this
Emyrk Mar 29, 2022
1e3aac0
WIP: 🍐 auth-> testdata, refactoring and restructuring
johnstcn Mar 30, 2022
e977e84
testdata -> authztest
johnstcn Mar 30, 2022
00a7c3f
WIP: start work on SVO
johnstcn Mar 30, 2022
1f04c01
reduce allocations for union sets
Emyrk Mar 31, 2022
fbf4db1
fix: Fix nil permissions as Strings()
Emyrk Mar 31, 2022
4946897
chore: Make all permission variant levels
Emyrk Mar 31, 2022
7e6cc66
First full draft of the authz authorize test
Emyrk Mar 31, 2022
a0017e5
Tally up failed tests
Emyrk Mar 31, 2022
4b110b3
Change test pkg
Emyrk Mar 31, 2022
65ef4e3
Use an interface for the object
Emyrk Mar 31, 2022
d294786
fix: make authztest.Objects return correct type
johnstcn Apr 1, 2022
c1f8945
refactor: rename consts {Read,Write,Modify,Delete}Action to Action$1
johnstcn Apr 1, 2022
01f3d40
chore: Define object interface
Emyrk Apr 1, 2022
de7de6e
test: Unit test extra properties
Emyrk Apr 1, 2022
4c86e44
Merge remote-tracking branch 'origin/stevenmasley/rbac' into stevenma…
Emyrk Apr 1, 2022
30c6568
put back interface assertion
Emyrk Apr 1, 2022
a419a65
Fix some compile errors from merge
Emyrk Apr 1, 2022
bbd1c4c
test: Roles, sets, permissions, iterators
Emyrk Apr 1, 2022
def010f
Test string functions
Emyrk Apr 1, 2022
c4ee590
test: Unit test permission string
Emyrk Apr 4, 2022
84e3ab9
Add A+ and A-
Emyrk Apr 4, 2022
c2eec18
Parallelize tests
Emyrk Apr 4, 2022
5a2834a
fix code line in readme
Emyrk Apr 4, 2022
913d141
Merge remote-tracking branch 'origin/main' into stevenmasley/rbac
Emyrk Apr 4, 2022
2804b92
test: ParsePermissions from strings
Emyrk Apr 4, 2022
5698938
use fmt over str builder for easier to read
Emyrk Apr 4, 2022
75ed8ef
Linting
Emyrk Apr 4, 2022
b2db661
authz: README.md: update table formatting
johnstcn Apr 5, 2022
26ef1e6
Make action CRUD
Emyrk Apr 5, 2022
19aba30
LevelID -> OrganizationID
Emyrk Apr 5, 2022
ceee9cd
feat: authztest: categorize test failures by test name
johnstcn Apr 5, 2022
ee8bf04
fixup! feat: authztest: categorize test failures by test name
johnstcn Apr 5, 2022
44c02a1
chore: add documentation for authz and authztest
johnstcn Apr 6, 2022
dfb9ad1
fixup! chore: add documentation for authz and authztest
johnstcn Apr 6, 2022
e482d2c
chore: more authz/authztest docs
johnstcn Apr 6, 2022
a4e038f
Remove underscore from test names
Emyrk Apr 6, 2022
9918c16
zObject does not need exported fields
Emyrk Apr 6, 2022
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
Prev Previous commit
Next Next commit
chore: Make all permission variant levels
Playing around with helper functions to make life easy
  • Loading branch information
Emyrk committed Mar 31, 2022
commit 494689732802933495cada7dd6365e18f12a810d
6 changes: 6 additions & 0 deletions coderd/authz/authz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package authz

// TODO: Implement Authorize
func Authorize(subj interface{}, obj Object, action interface{}) error {
return nil
}
150 changes: 150 additions & 0 deletions coderd/authz/authz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package authz_test

import (
"fmt"
"github.com/coder/coder/coderd/authz/authztest"
"math/bits"
"testing"
)

var nilSet = authztest.Set{nil}

func Test_ExhaustiveAuthorize(t *testing.T) {
all := authztest.GroupedPermissions(authztest.AllPermissions())
variants := permissionVariants(all)
for name, v := range variants {
fmt.Printf("%s: %d\n", name, v.Size())
}
}

func permissionVariants(all authztest.SetGroup) map[string]*authztest.Role {
// an is any noise above the impactful set
an := abstain
// ln is any noise below the impactful set
ln := positive | negative | abstain

// Cases are X+/- where X indicates the level where the impactful set is.
// The impactful set determines the result.
return map[string]*authztest.Role{
// Wild
"W+": authztest.NewRole(
pos(all.Wildcard()),
noise(ln, all.Site(), all.Org(), all.User()),
),
"W-": authztest.NewRole(
neg(all.Wildcard()),
noise(ln, all.Site(), all.Org(), all.User()),
),
// Site
"S+": authztest.NewRole(
noise(an, all.Wildcard()),
pos(all.Site()),
noise(ln, all.Org(), all.User()),
),
"S-": authztest.NewRole(
noise(an, all.Wildcard()),
neg(all.Site()),
noise(ln, all.Org(), all.User()),
),
// TODO: Figure out cross org noise between org:* and org:mem
// Org:*
"O+": authztest.NewRole(
noise(an, all.Wildcard(), all.Site()),
pos(all.Org()),
noise(ln, all.User()),
),
"O-": authztest.NewRole(
noise(an, all.Wildcard(), all.Site()),
neg(all.Org()),
noise(ln, all.User()),
),
// Org:Mem
"M+": authztest.NewRole(
noise(an, all.Wildcard(), all.Site()),
pos(all.OrgMem()),
noise(ln, all.User()),
),
"M-": authztest.NewRole(
noise(an, all.Wildcard(), all.Site()),
neg(all.OrgMem()),
noise(ln, all.User()),
),
// User
"U+": authztest.NewRole(
noise(an, all.Wildcard(), all.Site(), all.Org()),
pos(all.User()),
),
"U-": authztest.NewRole(
noise(an, all.Wildcard(), all.Site(), all.Org()),
neg(all.User()),
),
}
}

func l() {
//authztest.Levels
//noise(an, all.Wildcard()),
// neg(all.Site()),
// noise(ln, all.Org(), all.User()),
}

// pos returns the positive impactful variant for a given level. It does not
// include noise at any other level but the one given.
func pos(lvl authztest.LevelGroup) *authztest.Role {
return authztest.NewRole(
lvl.Positive(),
authztest.Union(lvl.Abstain()[:1], nilSet),
)
}

func neg(lvl authztest.LevelGroup) *authztest.Role {
return authztest.NewRole(
lvl.Negative(),
authztest.Union(lvl.Positive()[:1], nilSet),
authztest.Union(lvl.Abstain()[:1], nilSet),
)
}

type noiseBits uint8

const (
none noiseBits = 1 << iota
positive
negative
abstain
)

func flagMatch(flag, in noiseBits) bool {
return flag&in != 0
}

// noise returns the noise permission permutations for a given level. You can
// use this helper function when this level is not impactful.
// The returned role is the permutations including at least one example of
// positive, negative, and neutral permissions. It also includes the set of
// no additional permissions.
func noise(f noiseBits, lvls ...authztest.LevelGroup) *authztest.Role {
rs := make([]authztest.Iterable, 0, len(lvls))
for _, lvl := range lvls {
sets := make([]authztest.Iterable, 0, bits.OnesCount8(uint8(f)))

if flagMatch(positive, f) {
sets = append(sets, authztest.Union(lvl.Positive()[:1], nilSet))
}
if flagMatch(negative, f) {
sets = append(sets, authztest.Union(lvl.Negative()[:1], nilSet))
}
if flagMatch(abstain, f) {
sets = append(sets, authztest.Union(lvl.Abstain()[:1], nilSet))
}

rs = append(rs, authztest.NewRole(
sets...,
))
}

if len(rs) == 1 {
return rs[0].(*authztest.Role)
}
return authztest.NewRole(rs...)
}
20 changes: 10 additions & 10 deletions coderd/authz/authztest/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ var nilSet = Set{nil}
// *.*.*.*
//var PermissionSetWPlus = NewRole(
// all.Wildcard().Positive(),
// union(all.Wildcard().Abstain(), nilSet),
// Union(all.Wildcard().Abstain(), nilSet),
//
// union(all.Site().Positive(), nilSet),
// union(all.Site().Negative(), nilSet),
// union(all.Site().Abstain(), nilSet),
// Union(all.Site().Positive(), nilSet),
// Union(all.Site().Negative(), nilSet),
// Union(all.Site().Abstain(), nilSet),
//
// union(all.AllOrgs().Positive(), nilSet),
// union(all.AllOrgs().Negative(), nilSet),
// union(all.AllOrgs().Abstain(), nilSet),
// Union(all.AllOrgs().Positive(), nilSet),
// Union(all.AllOrgs().Negative(), nilSet),
// Union(all.AllOrgs().Abstain(), nilSet),
//
// union(all.User().Positive(), nilSet),
// union(all.User().Negative(), nilSet),
// union(all.User().Abstain(), nilSet),
// Union(all.User().Positive(), nilSet),
// Union(all.User().Negative(), nilSet),
// Union(all.User().Abstain(), nilSet),
//)
20 changes: 10 additions & 10 deletions coderd/authz/authztest/group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ func Test_PermissionSetWPlusSearchSpace(t *testing.T) {
all := GroupedPermissions(AllPermissions())
wplus := NewRole(
all.Wildcard().Positive(),
union(all.Wildcard().Abstain()[:1], nilSet),
Union(all.Wildcard().Abstain()[:1], nilSet),

union(all.Site().Positive()[:1], nilSet),
union(all.Site().Negative()[:1], nilSet),
union(all.Site().Abstain()[:1], nilSet),
Union(all.Site().Positive()[:1], nilSet),
Union(all.Site().Negative()[:1], nilSet),
Union(all.Site().Abstain()[:1], nilSet),

union(all.AllOrgs().Positive()[:1], nilSet),
union(all.AllOrgs().Negative()[:1], nilSet),
union(all.AllOrgs().Abstain()[:1], nilSet),
Union(all.AllOrgs().Positive()[:1], nilSet),
Union(all.AllOrgs().Negative()[:1], nilSet),
Union(all.AllOrgs().Abstain()[:1], nilSet),

union(all.User().Positive()[:1], nilSet),
union(all.User().Negative()[:1], nilSet),
union(all.User().Abstain()[:1], nilSet),
Union(all.User().Positive()[:1], nilSet),
Union(all.User().Negative()[:1], nilSet),
Union(all.User().Abstain()[:1], nilSet),
)
fmt.Println(wplus.N)
fmt.Println(len(AllPermissions()))
Expand Down
43 changes: 35 additions & 8 deletions coderd/authz/authztest/iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import (
. "github.com/coder/coder/coderd/authz"
)

type iterable interface {
Iterator() iterator
type Iterable interface {
Iterator() Iterator
}

type iterator interface {
iterable
type Iterator interface {
Iterable

Next() bool
Permissions() Set
Expand All @@ -32,7 +32,7 @@ type unionIterator struct {
N int
}

func union(sets ...Set) *unionIterator {
func Union(sets ...Set) *unionIterator {
var n int
for _, s := range sets {
n += len(s)
Expand Down Expand Up @@ -76,18 +76,45 @@ func (si *unionIterator) Size() int {
return si.N
}

func (si *unionIterator) Iterator() iterator {
func (si *unionIterator) Iterator() Iterator {
return si
}

type productI struct {
ReturnSize int
N int
PermissionSets []Iterator

buffer Set
}

func ProductI(sets ...Iterable) *productI {
setInterfaces := make([]Iterator, 0, len(sets))
var retSize int
var size int = 1
for _, s := range sets {
v := s.Iterator()
setInterfaces = append(setInterfaces, v)
retSize += v.ReturnSize()
// size is the cross product of all Iterator sets
size *= v.Size()
}
return &productI{
ReturnSize: retSize,
N: size,
PermissionSets: setInterfaces,
buffer: make([]*Permission, retSize),
}
}

type productIterator struct {
i, j int
a Set
b Set
buffer Set
}

func product(a, b Set) *productIterator {
func Product(a, b Set) *productIterator {
i := &productIterator{
i: 0,
j: 0,
Expand Down Expand Up @@ -128,6 +155,6 @@ func (s *productIterator) Size() int {
return len(s.a) * len(s.b)
}

func (s *productIterator) Iterator() iterator {
func (s *productIterator) Iterator() Iterator {
return s
}
40 changes: 20 additions & 20 deletions coderd/authz/authztest/level.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import "github.com/coder/coder/coderd/authz"
type level string

const (
levelWild level = "level-wild"
levelSite level = "level-site"
levelOrg level = "level-org"
levelOrgMem level = "level-org:mem"
// levelOrgAll is a helper to get both org levels above
levelOrgAll level = "level-org:*"
levelUser level = "level-user"
LevelWildKey level = "level-wild"
LevelSiteKey level = "level-site"
LevelOrgKey level = "level-org"
LevelOrgMemKey level = "level-org:mem"
// LevelOrgAllKey is a helper to get both org levels above
LevelOrgAllKey level = "level-org:*"
LevelUserKey level = "level-user"
)

// LevelGroup is all permissions for a given level
Expand Down Expand Up @@ -43,7 +43,7 @@ func (lg LevelGroup) Abstain() Set {

func GroupedPermissions(perms Set) SetGroup {
groups := make(SetGroup)
allLevelKeys := []level{levelWild, levelSite, levelOrg, levelOrgMem, levelOrgAll, levelUser}
allLevelKeys := []level{LevelWildKey, LevelSiteKey, LevelOrgKey, LevelOrgMemKey, LevelOrgAllKey, LevelUserKey}

for _, l := range allLevelKeys {
groups[l] = make(LevelGroup)
Expand All @@ -53,18 +53,18 @@ func GroupedPermissions(perms Set) SetGroup {
m := Impact(p)
switch {
case p.Level == authz.LevelSite:
groups[levelSite][m] = append(groups[levelSite][m], p)
groups[LevelSiteKey][m] = append(groups[LevelSiteKey][m], p)
case p.Level == authz.LevelOrg:
groups[levelOrgAll][m] = append(groups[levelOrgAll][m], p)
groups[LevelOrgAllKey][m] = append(groups[LevelOrgAllKey][m], p)
if p.LevelID == "" || p.LevelID == "*" {
groups[levelOrg][m] = append(groups[levelOrg][m], p)
groups[LevelOrgKey][m] = append(groups[LevelOrgKey][m], p)
} else {
groups[levelOrgMem][m] = append(groups[levelOrgMem][m], p)
groups[LevelOrgMemKey][m] = append(groups[LevelOrgMemKey][m], p)
}
case p.Level == authz.LevelUser:
groups[levelUser][m] = append(groups[levelUser][m], p)
groups[LevelUserKey][m] = append(groups[LevelUserKey][m], p)
case p.Level == authz.LevelWildcard:
groups[levelWild][m] = append(groups[levelWild][m], p)
groups[LevelWildKey][m] = append(groups[LevelWildKey][m], p)
}
}

Expand All @@ -74,25 +74,25 @@ func GroupedPermissions(perms Set) SetGroup {
type SetGroup map[level]LevelGroup

func (s SetGroup) Wildcard() LevelGroup {
return s[levelWild]
return s[LevelWildKey]
}

func (s SetGroup) Site() LevelGroup {
return s[levelSite]
return s[LevelSiteKey]
}

func (s SetGroup) Org() LevelGroup {
return s[levelOrg]
return s[LevelOrgKey]
}

func (s SetGroup) AllOrgs() LevelGroup {
return s[levelOrgAll]
return s[LevelOrgAllKey]
}

func (s SetGroup) OrgMem() LevelGroup {
return s[levelOrgMem]
return s[LevelOrgMemKey]
}

func (s SetGroup) User() LevelGroup {
return s[levelUser]
return s[LevelUserKey]
}
Loading