Skip to content

Commit 7e6cc66

Browse files
committed
First full draft of the authz authorize test
1 parent 4946897 commit 7e6cc66

File tree

10 files changed

+223
-79
lines changed

10 files changed

+223
-79
lines changed

coderd/authz/action.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package authz
2+
3+
type Action string
4+
5+
const (
6+
ReadAction = "read"
7+
WriteAction = "write"
8+
ModifyAction = "modify"
9+
DeleteAction = "delete"
10+
)

coderd/authz/authz.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package authz
22

33
// TODO: Implement Authorize
4-
func Authorize(subj interface{}, obj Object, action interface{}) error {
4+
func Authorize(subj Subject, obj Object, action Action) error {
5+
// TODO: Expand subject roles into their permissions as appropriate. Apply scopes.
6+
return AuthorizePermissions(subj.ID(), []Permission{}, obj, action)
7+
}
8+
9+
// AuthorizePermissions runs the authorize function with the raw permissions in a single list.
10+
func AuthorizePermissions(subjID string, permissions []Permission, object Object, action Action) error {
511
return nil
612
}

coderd/authz/authz_test.go

Lines changed: 112 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,120 @@ package authz_test
22

33
import (
44
"fmt"
5+
"github.com/coder/coder/coderd/authz"
56
"github.com/coder/coder/coderd/authz/authztest"
67
"math/bits"
8+
"strings"
79
"testing"
810
)
911

1012
var nilSet = authztest.Set{nil}
1113

1214
func Test_ExhaustiveAuthorize(t *testing.T) {
1315
all := authztest.GroupedPermissions(authztest.AllPermissions())
14-
variants := permissionVariants(all)
15-
for name, v := range variants {
16+
roleVariants := permissionVariants(all)
17+
18+
testCases := []struct {
19+
Name string
20+
Objs []authz.Object
21+
// Action is constant
22+
// Subject comes from roleVariants
23+
Result func(pv string) bool
24+
}{
25+
{
26+
Name: "User:Org",
27+
Objs: authztest.Objects(
28+
[]string{authztest.PermMe, authztest.PermOrgID},
29+
),
30+
Result: func(pv string) bool {
31+
return strings.Contains(pv, "+")
32+
},
33+
},
34+
{
35+
// All U+/- tests should fail
36+
Name: "NotUser:Org",
37+
Objs: authztest.Objects(
38+
[]string{"other", authztest.PermOrgID},
39+
[]string{"", authztest.PermOrgID},
40+
),
41+
Result: func(pv string) bool {
42+
if strings.Contains(pv, "U") {
43+
return false
44+
}
45+
return strings.Contains(pv, "+")
46+
},
47+
},
48+
{
49+
// All O+/- and U+/- tests should fail
50+
Name: "NotUser:NotOrg",
51+
Objs: authztest.Objects(
52+
[]string{authztest.PermMe, "non-mem"},
53+
[]string{"other", "non-mem"},
54+
[]string{"other", ""},
55+
[]string{"", "non-mem"},
56+
[]string{"", ""},
57+
),
58+
Result: func(pv string) bool {
59+
if strings.Contains(pv, "U") {
60+
return false
61+
}
62+
if strings.Contains(pv, "O") {
63+
return false
64+
}
65+
return strings.Contains(pv, "+")
66+
},
67+
},
68+
// TODO: @emyrk for this one, we should probably pass a custom roles variant
69+
//{
70+
// // O+, O- no longer pass judgement. Defer to user level judgement (only somewhat tricky case)
71+
// Name: "User:NotOrg",
72+
// Objs: authztest.Objects(
73+
// []string{authztest.PermMe, ""},
74+
// ),
75+
// Result: func(pv string) bool {
76+
// return strings.Contains(pv, "+")
77+
// },
78+
//},
79+
}
80+
81+
var pvars int
82+
for name, v := range roleVariants {
1683
fmt.Printf("%s: %d\n", name, v.Size())
84+
pvars += v.Size()
85+
}
86+
var total int = 0
87+
for _, c := range testCases {
88+
total += len(c.Objs) * pvars
89+
}
90+
fmt.Printf("pvars=%d, total=%d\n", pvars, total)
91+
92+
var tot int
93+
for _, c := range testCases {
94+
t.Run(c.Name, func(t *testing.T) {
95+
for _, o := range c.Objs {
96+
for _, v := range roleVariants {
97+
v.Each(func(set authztest.Set) {
98+
// TODO: Authz.Permissions does allocations at the moment. We should fix that.
99+
err := authz.AuthorizePermissions(
100+
authztest.PermMe,
101+
set.Permissions(),
102+
o,
103+
authztest.PermAction)
104+
var _ = err
105+
tot++
106+
})
107+
v.Reset()
108+
}
109+
}
110+
})
17111
}
18112
}
19113

20114
func permissionVariants(all authztest.SetGroup) map[string]*authztest.Role {
21115
// an is any noise above the impactful set
22-
an := abstain
116+
an := noiseAbstain
23117
// ln is any noise below the impactful set
24-
ln := positive | negative | abstain
118+
ln := noisePositive | noiseNegative | noiseAbstain
25119

26120
// Cases are X+/- where X indicates the level where the impactful set is.
27121
// The impactful set determines the result.
@@ -46,26 +140,25 @@ func permissionVariants(all authztest.SetGroup) map[string]*authztest.Role {
46140
neg(all.Site()),
47141
noise(ln, all.Org(), all.User()),
48142
),
49-
// TODO: Figure out cross org noise between org:* and org:mem
50-
// Org:*
143+
// Org:* -- Added org:mem noise
51144
"O+": authztest.NewRole(
52-
noise(an, all.Wildcard(), all.Site()),
145+
noise(an, all.Wildcard(), all.Site(), all.OrgMem()),
53146
pos(all.Org()),
54147
noise(ln, all.User()),
55148
),
56149
"O-": authztest.NewRole(
57-
noise(an, all.Wildcard(), all.Site()),
150+
noise(an, all.Wildcard(), all.Site(), all.OrgMem()),
58151
neg(all.Org()),
59152
noise(ln, all.User()),
60153
),
61-
// Org:Mem
154+
// Org:Mem -- Added org:* noise
62155
"M+": authztest.NewRole(
63-
noise(an, all.Wildcard(), all.Site()),
156+
noise(an, all.Wildcard(), all.Site(), all.Org()),
64157
pos(all.OrgMem()),
65158
noise(ln, all.User()),
66159
),
67160
"M-": authztest.NewRole(
68-
noise(an, all.Wildcard(), all.Site()),
161+
noise(an, all.Wildcard(), all.Site(), all.Org()),
69162
neg(all.OrgMem()),
70163
noise(ln, all.User()),
71164
),
@@ -78,16 +171,10 @@ func permissionVariants(all authztest.SetGroup) map[string]*authztest.Role {
78171
noise(an, all.Wildcard(), all.Site(), all.Org()),
79172
neg(all.User()),
80173
),
174+
// TODO: @Emyrk the abstain sets
81175
}
82176
}
83177

84-
func l() {
85-
//authztest.Levels
86-
//noise(an, all.Wildcard()),
87-
// neg(all.Site()),
88-
// noise(ln, all.Org(), all.User()),
89-
}
90-
91178
// pos returns the positive impactful variant for a given level. It does not
92179
// include noise at any other level but the one given.
93180
func pos(lvl authztest.LevelGroup) *authztest.Role {
@@ -108,10 +195,10 @@ func neg(lvl authztest.LevelGroup) *authztest.Role {
108195
type noiseBits uint8
109196

110197
const (
111-
none noiseBits = 1 << iota
112-
positive
113-
negative
114-
abstain
198+
_ noiseBits = 1 << iota
199+
noisePositive
200+
noiseNegative
201+
noiseAbstain
115202
)
116203

117204
func flagMatch(flag, in noiseBits) bool {
@@ -128,13 +215,13 @@ func noise(f noiseBits, lvls ...authztest.LevelGroup) *authztest.Role {
128215
for _, lvl := range lvls {
129216
sets := make([]authztest.Iterable, 0, bits.OnesCount8(uint8(f)))
130217

131-
if flagMatch(positive, f) {
218+
if flagMatch(noisePositive, f) {
132219
sets = append(sets, authztest.Union(lvl.Positive()[:1], nilSet))
133220
}
134-
if flagMatch(negative, f) {
221+
if flagMatch(noiseNegative, f) {
135222
sets = append(sets, authztest.Union(lvl.Negative()[:1], nilSet))
136223
}
137-
if flagMatch(abstain, f) {
224+
if flagMatch(noiseAbstain, f) {
138225
sets = append(sets, authztest.Union(lvl.Abstain()[:1], nilSet))
139226
}
140227

coderd/authz/authztest/iterator.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package authztest
22

33
import (
4-
. "github.com/coder/coder/coderd/authz"
4+
"github.com/coder/coder/coderd/authz"
55
)
66

77
type Iterable interface {
@@ -59,7 +59,7 @@ func (si *unionIterator) Permissions() Set {
5959
return si.buffer
6060
}
6161

62-
func (si unionIterator) Permission() *Permission {
62+
func (si unionIterator) Permission() *authz.Permission {
6363
return si.sets[si.setIdx][si.offset]
6464
}
6565

@@ -103,7 +103,7 @@ func ProductI(sets ...Iterable) *productI {
103103
ReturnSize: retSize,
104104
N: size,
105105
PermissionSets: setInterfaces,
106-
buffer: make([]*Permission, retSize),
106+
buffer: make([]*authz.Permission, retSize),
107107
}
108108
}
109109

coderd/authz/authztest/object.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package authztest
2+
3+
import "github.com/coder/coder/coderd/authz"
4+
5+
func Objects(pairs ...[]string) []authz.Object {
6+
objs := make([]authz.Object, 0, len(pairs))
7+
for _, p := range pairs {
8+
objs = append(objs, authz.Object{
9+
ObjectID: PermObjectID,
10+
OwnerID: p[0],
11+
OrgOwnerID: p[1],
12+
ObjectType: PermObjectType,
13+
})
14+
}
15+
return objs
16+
}

coderd/authz/authztest/permissions.go

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,45 @@
11
package authztest
22

33
import (
4-
. "github.com/coder/coder/coderd/authz"
4+
"github.com/coder/coder/coderd/authz"
55
)
66

77
const (
8-
otherOption = "other"
8+
otherOption = "other"
9+
PermObjectType = "resource"
10+
PermAction = "read"
11+
PermOrgID = "mem"
12+
PermObjectID = "rid"
13+
PermMe = "me"
914
)
1015

1116
var (
12-
levelIDs = []string{"", "mem"}
13-
resourceTypes = []string{"resource", "*", otherOption}
14-
resourceIDs = []string{"rid", "*", otherOption}
15-
actions = []string{"action", "*", otherOption}
17+
levelIDs = []string{"", PermOrgID}
18+
resourceTypes = []string{PermObjectType, "*", otherOption}
19+
resourceIDs = []string{PermObjectID, "*", otherOption}
20+
actions = []string{PermAction, "*", otherOption}
1621
)
1722

1823
// AllPermissions returns all the possible permissions ever.
1924
func AllPermissions() Set {
2025
permissionTypes := []bool{true, false}
21-
all := make(Set, 0, len(permissionTypes)*len(PermissionLevels)*len(levelIDs)*len(resourceTypes)*len(resourceIDs)*len(actions))
26+
all := make(Set, 0, len(permissionTypes)*len(authz.PermissionLevels)*len(levelIDs)*len(resourceTypes)*len(resourceIDs)*len(actions))
2227
for _, s := range permissionTypes {
23-
for _, l := range PermissionLevels {
28+
for _, l := range authz.PermissionLevels {
2429
for _, t := range resourceTypes {
2530
for _, i := range resourceIDs {
2631
for _, a := range actions {
27-
if l == LevelOrg {
28-
all = append(all, &Permission{
32+
if l == authz.LevelOrg {
33+
all = append(all, &authz.Permission{
2934
Sign: s,
3035
Level: l,
31-
LevelID: "mem",
36+
LevelID: PermOrgID,
3237
ResourceType: t,
3338
ResourceID: i,
3439
Action: a,
3540
})
3641
}
37-
all = append(all, &Permission{
42+
all = append(all, &authz.Permission{
3843
Sign: s,
3944
Level: l,
4045
LevelID: "",
@@ -51,7 +56,7 @@ func AllPermissions() Set {
5156
}
5257

5358
// Impact returns the impact (positive, negative, abstain) of p
54-
func Impact(p *Permission) PermissionSet {
59+
func Impact(p *authz.Permission) PermissionSet {
5560
if p.ResourceType == otherOption ||
5661
p.ResourceID == otherOption ||
5762
p.Action == otherOption {

coderd/authz/authztest/role.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package authztest
22

33
import (
4-
. "github.com/coder/coder/coderd/authz"
4+
"github.com/coder/coder/coderd/authz"
55
)
66

7-
var _ Permission
8-
97
// Role can print all possible permutations of the given iterators.
108
type Role struct {
119
// returnSize is how many permissions are the returned set for the role
@@ -16,7 +14,7 @@ type Role struct {
1614
// This is kinda werird, but the first scan should not move anything.
1715
first bool
1816

19-
buffer []*Permission
17+
buffer []*authz.Permission
2018
}
2119

2220
func NewRole(sets ...Iterable) *Role {
@@ -34,7 +32,7 @@ func NewRole(sets ...Iterable) *Role {
3432
returnSize: retSize,
3533
N: size,
3634
PermissionSets: setInterfaces,
37-
buffer: make([]*Permission, retSize),
35+
buffer: make([]*authz.Permission, retSize),
3836
}
3937
}
4038

@@ -60,17 +58,14 @@ func (r *Role) Permissions() Set {
6058
}
6159

6260
func (r *Role) Each(ea func(set Set)) {
61+
ea(r.Permissions())
6362
for r.Next() {
6463
ea(r.Permissions())
6564
}
6665
}
6766

6867
// Next will grab the next cross-product permutation of all permissions of r.
6968
func (r *Role) Next() bool {
70-
if !r.first {
71-
r.first = true
72-
return true
73-
}
7469
for i := range r.PermissionSets {
7570
if r.PermissionSets[i].Next() {
7671
break

0 commit comments

Comments
 (0)