Skip to content

Commit 98d81fc

Browse files
committed
Merge branch 'main' into bq/ui-tweaks-editor
2 parents f0d2fc6 + af59e2b commit 98d81fc

File tree

20 files changed

+664
-64
lines changed

20 files changed

+664
-64
lines changed

coderd/apidoc/docs.go

Lines changed: 47 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderdtest/authorize.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ func NewAuthTester(ctx context.Context, t *testing.T, client *codersdk.Client, a
445445
func (a *AuthTester) Test(ctx context.Context, assertRoute map[string]RouteCheck, skipRoutes map[string]string) {
446446
// Always fail auth from this point forward
447447
a.authorizer.Wrapped = &FakeAuthorizer{
448-
AlwaysReturn: rbac.ForbiddenWithInternal(xerrors.New("fake implementation"), nil, nil),
448+
AlwaysReturn: rbac.ForbiddenWithInternal(xerrors.New("fake implementation"), rbac.Subject{}, "", rbac.Object{}, nil),
449449
}
450450

451451
routeMissing := make(map[string]bool)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
ALTER TABLE workspace_builds
2-
ADD COLUMN name character varying(64) NOT NULL;
2+
ADD COLUMN name character varying(64) NOT NULL DEFAULT '';

coderd/database/migrations/migrate_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,10 @@ func TestMigrateUpWithFixtures(t *testing.T) {
331331
s.Add(table, count)
332332
}
333333
}
334+
335+
// Test that migration down is successful after up.
336+
err = migrations.Down(db)
337+
require.NoError(t, err, "final migration down should be successful")
334338
})
335339
}
336340
}

coderd/rbac/astvalue.go

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
package rbac
2+
3+
import (
4+
"github.com/open-policy-agent/opa/ast"
5+
"golang.org/x/xerrors"
6+
)
7+
8+
// regoInputValue returns a rego input value for the given subject, action, and
9+
// object. This rego input is already parsed and can be used directly in a
10+
// rego query.
11+
func regoInputValue(subject Subject, action Action, object Object) (ast.Value, error) {
12+
regoSubj, err := subject.regoValue()
13+
if err != nil {
14+
return nil, xerrors.Errorf("subject: %w", err)
15+
}
16+
17+
s := [2]*ast.Term{
18+
ast.StringTerm("subject"),
19+
ast.NewTerm(regoSubj),
20+
}
21+
a := [2]*ast.Term{
22+
ast.StringTerm("action"),
23+
ast.StringTerm(string(action)),
24+
}
25+
o := [2]*ast.Term{
26+
ast.StringTerm("object"),
27+
ast.NewTerm(object.regoValue()),
28+
}
29+
30+
input := ast.NewObject(s, a, o)
31+
32+
return input, nil
33+
}
34+
35+
// regoPartialInputValue is the same as regoInputValue but only includes the
36+
// object type. This is for partial evaluations.
37+
func regoPartialInputValue(subject Subject, action Action, objectType string) (ast.Value, error) {
38+
regoSubj, err := subject.regoValue()
39+
if err != nil {
40+
return nil, xerrors.Errorf("subject: %w", err)
41+
}
42+
43+
s := [2]*ast.Term{
44+
ast.StringTerm("subject"),
45+
ast.NewTerm(regoSubj),
46+
}
47+
a := [2]*ast.Term{
48+
ast.StringTerm("action"),
49+
ast.StringTerm(string(action)),
50+
}
51+
o := [2]*ast.Term{
52+
ast.StringTerm("object"),
53+
ast.NewTerm(ast.NewObject(
54+
[2]*ast.Term{
55+
ast.StringTerm("type"),
56+
ast.StringTerm(objectType),
57+
}),
58+
),
59+
}
60+
61+
input := ast.NewObject(s, a, o)
62+
63+
return input, nil
64+
}
65+
66+
// regoValue returns the ast.Object representation of the subject.
67+
func (s Subject) regoValue() (ast.Value, error) {
68+
subjRoles, err := s.Roles.Expand()
69+
if err != nil {
70+
return nil, xerrors.Errorf("expand roles: %w", err)
71+
}
72+
73+
subjScope, err := s.Scope.Expand()
74+
if err != nil {
75+
return nil, xerrors.Errorf("expand scope: %w", err)
76+
}
77+
subj := ast.NewObject(
78+
[2]*ast.Term{
79+
ast.StringTerm("id"),
80+
ast.StringTerm(s.ID),
81+
},
82+
[2]*ast.Term{
83+
ast.StringTerm("roles"),
84+
ast.NewTerm(regoSlice(subjRoles)),
85+
},
86+
[2]*ast.Term{
87+
ast.StringTerm("scope"),
88+
ast.NewTerm(subjScope.regoValue()),
89+
},
90+
[2]*ast.Term{
91+
ast.StringTerm("groups"),
92+
ast.NewTerm(regoSliceString(s.Groups...)),
93+
},
94+
)
95+
96+
return subj, nil
97+
}
98+
99+
func (z Object) regoValue() ast.Value {
100+
userACL := ast.NewObject()
101+
for k, v := range z.ACLUserList {
102+
userACL.Insert(ast.StringTerm(k), ast.NewTerm(regoSlice(v)))
103+
}
104+
grpACL := ast.NewObject()
105+
for k, v := range z.ACLGroupList {
106+
grpACL.Insert(ast.StringTerm(k), ast.NewTerm(regoSlice(v)))
107+
}
108+
return ast.NewObject(
109+
[2]*ast.Term{
110+
ast.StringTerm("id"),
111+
ast.StringTerm(z.ID),
112+
},
113+
[2]*ast.Term{
114+
ast.StringTerm("owner"),
115+
ast.StringTerm(z.Owner),
116+
},
117+
[2]*ast.Term{
118+
ast.StringTerm("org_owner"),
119+
ast.StringTerm(z.OrgID),
120+
},
121+
[2]*ast.Term{
122+
ast.StringTerm("type"),
123+
ast.StringTerm(z.Type),
124+
},
125+
[2]*ast.Term{
126+
ast.StringTerm("acl_user_list"),
127+
ast.NewTerm(userACL),
128+
},
129+
[2]*ast.Term{
130+
ast.StringTerm("acl_group_list"),
131+
ast.NewTerm(grpACL),
132+
},
133+
)
134+
}
135+
136+
func (role Role) regoValue() ast.Value {
137+
orgMap := ast.NewObject()
138+
for k, p := range role.Org {
139+
orgMap.Insert(ast.StringTerm(k), ast.NewTerm(regoSlice(p)))
140+
}
141+
return ast.NewObject(
142+
[2]*ast.Term{
143+
ast.StringTerm("site"),
144+
ast.NewTerm(regoSlice(role.Site)),
145+
},
146+
[2]*ast.Term{
147+
ast.StringTerm("org"),
148+
ast.NewTerm(orgMap),
149+
},
150+
[2]*ast.Term{
151+
ast.StringTerm("user"),
152+
ast.NewTerm(regoSlice(role.User)),
153+
},
154+
)
155+
}
156+
157+
func (s Scope) regoValue() ast.Value {
158+
r, ok := s.Role.regoValue().(ast.Object)
159+
if !ok {
160+
panic("developer error: role is not an object")
161+
}
162+
r.Insert(
163+
ast.StringTerm("allow_list"),
164+
ast.NewTerm(regoSliceString(s.AllowIDList...)),
165+
)
166+
return r
167+
}
168+
169+
func (perm Permission) regoValue() ast.Value {
170+
return ast.NewObject(
171+
[2]*ast.Term{
172+
ast.StringTerm("negate"),
173+
ast.BooleanTerm(perm.Negate),
174+
},
175+
[2]*ast.Term{
176+
ast.StringTerm("resource_type"),
177+
ast.StringTerm(perm.ResourceType),
178+
},
179+
[2]*ast.Term{
180+
ast.StringTerm("action"),
181+
ast.StringTerm(string(perm.Action)),
182+
},
183+
)
184+
}
185+
186+
func (act Action) regoValue() ast.Value {
187+
return ast.StringTerm(string(act)).Value
188+
}
189+
190+
type regoValue interface {
191+
regoValue() ast.Value
192+
}
193+
194+
// regoSlice returns the ast.Array representation of the slice.
195+
// The slice must contain only types that implement the regoValue interface.
196+
func regoSlice[T regoValue](slice []T) *ast.Array {
197+
terms := make([]*ast.Term, len(slice))
198+
for i, v := range slice {
199+
terms[i] = ast.NewTerm(v.regoValue())
200+
}
201+
return ast.NewArray(terms...)
202+
}
203+
204+
func regoSliceString(slice ...string) *ast.Array {
205+
terms := make([]*ast.Term, len(slice))
206+
for i, v := range slice {
207+
terms[i] = ast.StringTerm(v)
208+
}
209+
return ast.NewArray(terms...)
210+
}

0 commit comments

Comments
 (0)