|
1 | 1 | package authz_test
|
2 | 2 |
|
3 | 3 | import (
|
4 |
| - "math/bits" |
5 |
| - "strings" |
6 | 4 | "testing"
|
7 | 5 |
|
| 6 | + "github.com/stretchr/testify/require" |
| 7 | + |
8 | 8 | "github.com/coder/coder/coderd/authz"
|
9 |
| - "github.com/coder/coder/coderd/authz/authztest" |
10 | 9 | )
|
11 | 10 |
|
12 |
| -var nilSet = authztest.Set{nil} |
13 |
| - |
14 |
| -func TestExhaustiveAuthorize(t *testing.T) { |
| 11 | +func TestAuthorize(t *testing.T) { |
15 | 12 | t.Parallel()
|
16 | 13 |
|
17 |
| - all := authztest.GroupedPermissions(authztest.AllPermissions()) |
18 |
| - roleVariants := permissionVariants(all) |
19 |
| - res := authz.ResourceType(authztest.PermObjectType).AsID(authztest.PermObjectID) |
20 |
| - |
21 | 14 | testCases := []struct {
|
22 |
| - Name string |
23 |
| - Objs []authz.Resource |
24 |
| - // Action is constant |
25 |
| - // Subject comes from roleVariants |
26 |
| - Result func(pv string) bool |
| 15 | + name string |
| 16 | + subject authz.Subject |
| 17 | + resource authz.Resource |
| 18 | + actions []authz.Action |
| 19 | + error string |
27 | 20 | }{
|
28 | 21 | {
|
29 |
| - Name: "User:Org", |
30 |
| - Objs: []authz.Resource{ |
31 |
| - res.Owner(authztest.PermMe).Org(authztest.PermOrgID), |
32 |
| - }, |
33 |
| - Result: func(pv string) bool { |
34 |
| - return strings.Contains(pv, "+") |
| 22 | + name: "unauthenticated user cannot perform an action", |
| 23 | + subject: authz.SubjectTODO{ |
| 24 | + UserID: "", |
| 25 | + Site: []authz.Role{authz.RoleNoPerm}, |
35 | 26 | },
|
| 27 | + resource: authz.ResourceWorkspace, |
| 28 | + actions: []authz.Action{authz.ActionRead, authz.ActionCreate, authz.ActionDelete, authz.ActionUpdate}, |
| 29 | + error: "unauthorized", |
36 | 30 | },
|
37 | 31 | {
|
38 |
| - // All U+/- tests should fail |
39 |
| - Name: "NotUser:Org", |
40 |
| - Objs: []authz.Resource{ |
41 |
| - res.Owner("other").Org(authztest.PermOrgID), |
42 |
| - res.Owner("").Org(authztest.PermOrgID), |
43 |
| - }, |
44 |
| - Result: func(pv string) bool { |
45 |
| - if strings.Contains(pv, "U") { |
46 |
| - return false |
47 |
| - } |
48 |
| - return strings.Contains(pv, "+") |
| 32 | + name: "admin can do anything", |
| 33 | + subject: authz.SubjectTODO{ |
| 34 | + UserID: "admin", |
| 35 | + Site: []authz.Role{authz.RoleAllowAll}, |
49 | 36 | },
|
| 37 | + resource: authz.ResourceWorkspace, |
| 38 | + actions: []authz.Action{authz.ActionRead, authz.ActionCreate, authz.ActionDelete, authz.ActionUpdate}, |
| 39 | + error: "", |
50 | 40 | },
|
51 |
| - { |
52 |
| - // All O+/- and U+/- tests should fail |
53 |
| - Name: "NotUser:NotOrg", |
54 |
| - Objs: []authz.Resource{ |
55 |
| - res.Owner(authztest.PermMe).Org("non-mem"), |
56 |
| - res.Owner("other").Org("non-mem"), |
57 |
| - res.Owner("other").Org(""), |
58 |
| - res.Owner("").Org("non-mem"), |
59 |
| - res.Owner("").Org(""), |
60 |
| - }, |
61 |
| - |
62 |
| - Result: func(pv string) bool { |
63 |
| - if strings.Contains(pv, "U") { |
64 |
| - return false |
65 |
| - } |
66 |
| - if strings.Contains(pv, "O") { |
67 |
| - return false |
68 |
| - } |
69 |
| - return strings.Contains(pv, "+") |
70 |
| - }, |
71 |
| - }, |
72 |
| - // TODO: @emyrk for this one, we should probably pass a custom roles variant |
73 |
| - //{ |
74 |
| - // // O+, O- no longer pass judgement. Defer to user level judgement (only somewhat tricky case) |
75 |
| - // Name: "User:NotOrg", |
76 |
| - // Objs: authztest.Objects( |
77 |
| - // []string{authztest.PermMe, ""}, |
78 |
| - // ), |
79 |
| - // Result: func(pv string) bool { |
80 |
| - // return strings.Contains(pv, "+") |
81 |
| - // }, |
82 |
| - // }, |
83 | 41 | }
|
84 | 42 |
|
85 |
| - failedTests := make(map[string]int) |
86 |
| - //nolint:paralleltest |
87 |
| - for _, c := range testCases { |
88 |
| - t.Run(c.Name, func(t *testing.T) { |
89 |
| - for _, o := range c.Objs { |
90 |
| - for name, v := range roleVariants { |
91 |
| - v.Each(func(set authztest.Set) { |
92 |
| - // TODO: Authz.Permissions does allocations at the moment. We should fix that. |
93 |
| - err := authz.AuthorizePermissions( |
94 |
| - authztest.PermMe, |
95 |
| - set.Permissions(), |
96 |
| - o, |
97 |
| - authztest.PermAction) |
98 |
| - if c.Result(name) && err != nil { |
99 |
| - failedTests[name]++ |
100 |
| - } else if !c.Result(name) && err == nil { |
101 |
| - failedTests[name]++ |
102 |
| - } |
103 |
| - }) |
104 |
| - v.Reset() |
| 43 | + for _, testCase := range testCases { |
| 44 | + testCase := testCase |
| 45 | + t.Run(testCase.name, func(t *testing.T) { |
| 46 | + t.Parallel() |
| 47 | + for _, action := range testCase.actions { |
| 48 | + err := authz.Authorize(testCase.subject, testCase.resource, action) |
| 49 | + if testCase.error == "" { |
| 50 | + require.NoError(t, err, "expected no error for testcase testcase %q action %s", testCase.name, action) |
| 51 | + continue |
105 | 52 | }
|
| 53 | + require.EqualError(t, err, testCase.error, "unexpected error") |
106 | 54 | }
|
107 | 55 | })
|
108 | 56 | }
|
109 |
| - // TODO: @emyrk when we implement the correct authorize, we can enable this check. |
110 |
| - // for testName, numFailed := range failedTests { |
111 |
| - // require.Zero(t, failedTests[testName], fmt.Sprintf("%s: %d tests failed", testName, numFailed)) |
112 |
| - // } |
113 |
| -} |
114 |
| - |
115 |
| -func permissionVariants(all authztest.SetGroup) map[string]*authztest.Role { |
116 |
| - // an is any noise above the impactful set |
117 |
| - an := noiseAbstain |
118 |
| - // ln is any noise below the impactful set |
119 |
| - ln := noisePositive | noiseNegative | noiseAbstain |
120 |
| - |
121 |
| - // Cases are X+/- where X indicates the level where the impactful set is. |
122 |
| - // The impactful set determines the result. |
123 |
| - return map[string]*authztest.Role{ |
124 |
| - // Wild |
125 |
| - "W+": authztest.NewRole( |
126 |
| - pos(all.Wildcard()), |
127 |
| - noise(ln, all.Site(), all.Org(), all.User()), |
128 |
| - ), |
129 |
| - "W-": authztest.NewRole( |
130 |
| - neg(all.Wildcard()), |
131 |
| - noise(ln, all.Site(), all.Org(), all.User()), |
132 |
| - ), |
133 |
| - // Site |
134 |
| - "S+": authztest.NewRole( |
135 |
| - noise(an, all.Wildcard()), |
136 |
| - pos(all.Site()), |
137 |
| - noise(ln, all.Org(), all.User()), |
138 |
| - ), |
139 |
| - "S-": authztest.NewRole( |
140 |
| - noise(an, all.Wildcard()), |
141 |
| - neg(all.Site()), |
142 |
| - noise(ln, all.Org(), all.User()), |
143 |
| - ), |
144 |
| - // Org:* -- Added org:mem noise |
145 |
| - "O+": authztest.NewRole( |
146 |
| - noise(an, all.Wildcard(), all.Site(), all.OrgMem()), |
147 |
| - pos(all.Org()), |
148 |
| - noise(ln, all.User()), |
149 |
| - ), |
150 |
| - "O-": authztest.NewRole( |
151 |
| - noise(an, all.Wildcard(), all.Site(), all.OrgMem()), |
152 |
| - neg(all.Org()), |
153 |
| - noise(ln, all.User()), |
154 |
| - ), |
155 |
| - // Org:Mem -- Added org:* noise |
156 |
| - "M+": authztest.NewRole( |
157 |
| - noise(an, all.Wildcard(), all.Site(), all.Org()), |
158 |
| - pos(all.OrgMem()), |
159 |
| - noise(ln, all.User()), |
160 |
| - ), |
161 |
| - "M-": authztest.NewRole( |
162 |
| - noise(an, all.Wildcard(), all.Site(), all.Org()), |
163 |
| - neg(all.OrgMem()), |
164 |
| - noise(ln, all.User()), |
165 |
| - ), |
166 |
| - // User |
167 |
| - "U+": authztest.NewRole( |
168 |
| - noise(an, all.Wildcard(), all.Site(), all.Org()), |
169 |
| - pos(all.User()), |
170 |
| - ), |
171 |
| - "U-": authztest.NewRole( |
172 |
| - noise(an, all.Wildcard(), all.Site(), all.Org()), |
173 |
| - neg(all.User()), |
174 |
| - ), |
175 |
| - // Abstain |
176 |
| - "A+": authztest.NewRole( |
177 |
| - authztest.Union( |
178 |
| - all.Wildcard().Abstain(), |
179 |
| - all.Site().Abstain(), |
180 |
| - all.Org().Abstain(), |
181 |
| - all.OrgMem().Abstain(), |
182 |
| - all.User().Abstain(), |
183 |
| - ), |
184 |
| - all.User().Positive()[:1], |
185 |
| - ), |
186 |
| - "A-": authztest.NewRole( |
187 |
| - authztest.Union( |
188 |
| - all.Wildcard().Abstain(), |
189 |
| - all.Site().Abstain(), |
190 |
| - all.Org().Abstain(), |
191 |
| - all.OrgMem().Abstain(), |
192 |
| - all.User().Abstain(), |
193 |
| - ), |
194 |
| - ), |
195 |
| - } |
196 |
| -} |
197 |
| - |
198 |
| -// pos returns the positive impactful variant for a given level. It does not |
199 |
| -// include noise at any other level but the one given. |
200 |
| -func pos(lvl authztest.LevelGroup) *authztest.Role { |
201 |
| - return authztest.NewRole( |
202 |
| - lvl.Positive(), |
203 |
| - authztest.Union(lvl.Abstain()[:1], nilSet), |
204 |
| - ) |
205 |
| -} |
206 |
| - |
207 |
| -func neg(lvl authztest.LevelGroup) *authztest.Role { |
208 |
| - return authztest.NewRole( |
209 |
| - lvl.Negative(), |
210 |
| - authztest.Union(lvl.Positive()[:1], nilSet), |
211 |
| - authztest.Union(lvl.Abstain()[:1], nilSet), |
212 |
| - ) |
213 |
| -} |
214 |
| - |
215 |
| -type noiseBits uint8 |
216 |
| - |
217 |
| -const ( |
218 |
| - _ noiseBits = 1 << iota |
219 |
| - noisePositive |
220 |
| - noiseNegative |
221 |
| - noiseAbstain |
222 |
| -) |
223 |
| - |
224 |
| -func flagMatch(flag, in noiseBits) bool { |
225 |
| - return flag&in != 0 |
226 |
| -} |
227 |
| - |
228 |
| -// noise returns the noise permission permutations for a given level. You can |
229 |
| -// use this helper function when this level is not impactful. |
230 |
| -// The returned role is the permutations including at least one example of |
231 |
| -// positive, negative, and neutral permissions. It also includes the set of |
232 |
| -// no additional permissions. |
233 |
| -func noise(f noiseBits, lvls ...authztest.LevelGroup) *authztest.Role { |
234 |
| - rs := make([]authztest.Iterable, 0, len(lvls)) |
235 |
| - for _, lvl := range lvls { |
236 |
| - sets := make([]authztest.Iterable, 0, bits.OnesCount8(uint8(f))) |
237 |
| - |
238 |
| - if flagMatch(noisePositive, f) { |
239 |
| - sets = append(sets, authztest.Union(lvl.Positive()[:1], nilSet)) |
240 |
| - } |
241 |
| - if flagMatch(noiseNegative, f) { |
242 |
| - sets = append(sets, authztest.Union(lvl.Negative()[:1], nilSet)) |
243 |
| - } |
244 |
| - if flagMatch(noiseAbstain, f) { |
245 |
| - sets = append(sets, authztest.Union(lvl.Abstain()[:1], nilSet)) |
246 |
| - } |
247 |
| - |
248 |
| - rs = append(rs, authztest.NewRole( |
249 |
| - sets..., |
250 |
| - )) |
251 |
| - } |
252 |
| - |
253 |
| - if len(rs) == 1 { |
254 |
| - role, _ := rs[0].(*authztest.Role) |
255 |
| - return role |
256 |
| - } |
257 |
| - return authztest.NewRole(rs...) |
258 | 57 | }
|
0 commit comments