Skip to content

Commit 8a8ce06

Browse files
committed
Merge remote-tracking branch 'origin/authzquerier_layer' into authzquerier_layer
2 parents 6bb2e1c + 357b05d commit 8a8ce06

File tree

4 files changed

+153
-243
lines changed

4 files changed

+153
-243
lines changed

coderd/authzquery/authz_test.go

Lines changed: 1 addition & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,6 @@ import (
44
"context"
55
"reflect"
66
"testing"
7-
"time"
8-
9-
"github.com/moby/moby/pkg/namesgenerator"
10-
11-
"github.com/coder/coder/testutil"
12-
13-
"github.com/stretchr/testify/require"
14-
15-
"github.com/coder/coder/coderd/database"
167

178
"github.com/google/uuid"
189

@@ -50,162 +41,8 @@ func TestAuthzQueryRecursive(t *testing.T) {
5041
}
5142
// Log the name of the last method, so if there is a panic, it is
5243
// easy to know which method failed.
53-
t.Log(method.Name)
44+
//t.Log(method.Name)
5445
// Call the function. Any infinite recursion will stack overflow.
5546
reflect.ValueOf(q).Method(i).Call(ins)
5647
}
5748
}
58-
59-
type authorizeTest struct {
60-
Data func(t *testing.T, tc *authorizeTest) map[string]interface{}
61-
// Test is all the calls to the AuthzStore
62-
Test func(ctx context.Context, t *testing.T, tc *authorizeTest, q authzquery.AuthzStore)
63-
// Assert is the objects and the expected RBAC calls.
64-
// If 2 reads are expected on the same object, pass in 2 rbac.Reads.
65-
Asserts map[string][]rbac.Action
66-
67-
names map[string]uuid.UUID
68-
}
69-
70-
func (tc *authorizeTest) Lookup(name string) uuid.UUID {
71-
if tc.names == nil {
72-
tc.names = make(map[string]uuid.UUID)
73-
}
74-
if id, ok := tc.names[name]; ok {
75-
return id
76-
}
77-
id := uuid.New()
78-
tc.names[name] = id
79-
return id
80-
}
81-
82-
func testAuthorizeFunction(t *testing.T, testCase *authorizeTest) {
83-
t.Helper()
84-
85-
// The actor does not really matter since all authz calls will succeed.
86-
actor := rbac.Subject{
87-
ID: uuid.New().String(),
88-
Roles: rbac.RoleNames{},
89-
Groups: []string{},
90-
Scope: rbac.ScopeAll,
91-
}
92-
93-
// Always use a fake database.
94-
db := databasefake.New()
95-
96-
// Record all authorization calls. This will allow all authorization calls
97-
// to succeed.
98-
rec := &coderdtest.RecordingAuthorizer{}
99-
q := authzquery.NewAuthzQuerier(db, rec)
100-
101-
// Setup Context
102-
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
103-
ctx = authzquery.WithAuthorizeContext(ctx, actor)
104-
t.Cleanup(cancel)
105-
106-
// Seed all data into the database that is required for the test.
107-
data := setupTestData(t, testCase, db, ctx)
108-
109-
// Run the test.
110-
testCase.Test(ctx, t, testCase, q)
111-
112-
// Asset RBAC calls.
113-
pairs := make([]coderdtest.ActionObjectPair, 0)
114-
for objectName, asserts := range testCase.Asserts {
115-
object := data[objectName]
116-
for _, assert := range asserts {
117-
canRBAC, ok := object.(rbac.Objecter)
118-
require.True(t, ok, "object %q does not implement rbac.Objecter", objectName)
119-
pairs = append(pairs, rec.Pair(assert, canRBAC.RBACObject()))
120-
}
121-
}
122-
rec.UnorderedAssertActor(t, actor, pairs...)
123-
require.NoError(t, rec.AllAsserted(), "all authz checks asserted")
124-
}
125-
126-
func setupTestData(t *testing.T, testCase *authorizeTest, db database.Store, ctx context.Context) map[string]interface{} {
127-
// Setup the test data.
128-
orgID := uuid.New()
129-
data := testCase.Data(t, testCase)
130-
for name, v := range data {
131-
switch orig := v.(type) {
132-
case database.Template:
133-
template, err := db.InsertTemplate(ctx, database.InsertTemplateParams{
134-
ID: testCase.Lookup(name),
135-
CreatedAt: time.Now(),
136-
UpdatedAt: time.Now(),
137-
OrganizationID: takeFirst(orig.OrganizationID, orgID),
138-
Name: takeFirst(orig.Name, namesgenerator.GetRandomName(1)),
139-
Provisioner: takeFirst(orig.Provisioner, database.ProvisionerTypeEcho),
140-
ActiveVersionID: takeFirst(orig.ActiveVersionID, uuid.New()),
141-
Description: takeFirst(orig.Description, namesgenerator.GetRandomName(1)),
142-
DefaultTTL: takeFirst(orig.DefaultTTL, 3600),
143-
CreatedBy: takeFirst(orig.CreatedBy, uuid.New()),
144-
Icon: takeFirst(orig.Icon, namesgenerator.GetRandomName(1)),
145-
UserACL: orig.UserACL,
146-
GroupACL: orig.GroupACL,
147-
DisplayName: takeFirst(orig.DisplayName, namesgenerator.GetRandomName(1)),
148-
AllowUserCancelWorkspaceJobs: takeFirst(orig.AllowUserCancelWorkspaceJobs, true),
149-
})
150-
require.NoError(t, err, "insert template")
151-
152-
data[name] = template
153-
case database.Workspace:
154-
workspace, err := db.InsertWorkspace(ctx, database.InsertWorkspaceParams{
155-
ID: testCase.Lookup(name),
156-
CreatedAt: time.Now(),
157-
UpdatedAt: time.Now(),
158-
OrganizationID: takeFirst(orig.OrganizationID, orgID),
159-
TemplateID: takeFirst(orig.TemplateID, uuid.New()),
160-
Name: takeFirst(orig.Name, namesgenerator.GetRandomName(1)),
161-
AutostartSchedule: orig.AutostartSchedule,
162-
Ttl: orig.Ttl,
163-
})
164-
require.NoError(t, err, "insert workspace")
165-
166-
data[name] = workspace
167-
case database.WorkspaceBuild:
168-
build, err := db.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{
169-
ID: testCase.Lookup(name),
170-
CreatedAt: time.Now(),
171-
UpdatedAt: time.Now(),
172-
WorkspaceID: takeFirst(orig.WorkspaceID, uuid.New()),
173-
TemplateVersionID: takeFirst(orig.TemplateVersionID, uuid.New()),
174-
BuildNumber: takeFirst(orig.BuildNumber, 0),
175-
Transition: takeFirst(orig.Transition, database.WorkspaceTransitionStart),
176-
InitiatorID: takeFirst(orig.InitiatorID, uuid.New()),
177-
JobID: takeFirst(orig.InitiatorID, uuid.New()),
178-
ProvisionerState: []byte{},
179-
Deadline: time.Now(),
180-
Reason: takeFirst(orig.Reason, database.BuildReasonInitiator),
181-
})
182-
require.NoError(t, err, "insert workspace build")
183-
184-
data[name] = build
185-
case database.User:
186-
user, err := db.InsertUser(ctx, database.InsertUserParams{
187-
ID: testCase.Lookup(name),
188-
Email: takeFirst(orig.Email, namesgenerator.GetRandomName(1)),
189-
Username: takeFirst(orig.Username, namesgenerator.GetRandomName(1)),
190-
HashedPassword: []byte{},
191-
CreatedAt: time.Now(),
192-
UpdatedAt: time.Now(),
193-
RBACRoles: []string{},
194-
LoginType: takeFirst(orig.LoginType, database.LoginTypePassword),
195-
})
196-
require.NoError(t, err, "insert user")
197-
198-
data[name] = user
199-
}
200-
}
201-
return data
202-
}
203-
204-
// takeFirst will take the first non empty value.
205-
func takeFirst[Value comparable](def Value, next Value) Value {
206-
var empty Value
207-
if def == empty {
208-
return next
209-
}
210-
return def
211-
}

coderd/authzquery/methods_test.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package authzquery_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"reflect"
7+
"strings"
8+
"testing"
9+
10+
"github.com/google/uuid"
11+
12+
"github.com/stretchr/testify/require"
13+
14+
"github.com/coder/coder/coderd/authzquery"
15+
"github.com/coder/coder/coderd/coderdtest"
16+
"github.com/coder/coder/coderd/database/databasefake"
17+
"github.com/stretchr/testify/suite"
18+
19+
"github.com/coder/coder/coderd/database"
20+
"github.com/coder/coder/coderd/rbac"
21+
)
22+
23+
// Define the suite, and absorb the built-in basic suite
24+
// functionality from testify - including a T() method which
25+
// returns the current testing context
26+
type MethodTestSuite struct {
27+
suite.Suite
28+
}
29+
30+
func (suite *MethodTestSuite) SetupTest() {
31+
}
32+
33+
func (suite *MethodTestSuite) TearDownTest() {
34+
}
35+
36+
// In order for 'go test' to run this suite, we need to create
37+
// a normal test function and pass our suite to suite.Run
38+
func TestMethodTestSuite(t *testing.T) {
39+
suite.Run(t, new(MethodTestSuite))
40+
}
41+
42+
type MethodCase struct {
43+
Inputs []reflect.Value
44+
Assertions []AssertRBAC
45+
}
46+
47+
type AssertRBAC struct {
48+
Object rbac.Object
49+
Actions []rbac.Action
50+
}
51+
52+
func (suite *MethodTestSuite) RunMethodTest(t *testing.T, testCaseF func(t *testing.T, db database.Store) MethodCase) {
53+
testName := suite.T().Name()
54+
names := strings.Split(testName, "/")
55+
methodName := names[len(names)-1]
56+
57+
db := databasefake.New()
58+
rec := &coderdtest.RecordingAuthorizer{
59+
Wrapped: &coderdtest.FakeAuthorizer{},
60+
}
61+
az := authzquery.NewAuthzQuerier(db, rec)
62+
actor := rbac.Subject{
63+
ID: uuid.NewString(),
64+
Roles: rbac.RoleNames{},
65+
Groups: []string{},
66+
Scope: rbac.ScopeAll,
67+
}
68+
ctx := authzquery.WithAuthorizeContext(context.Background(), actor)
69+
70+
testCase := testCaseF(t, db)
71+
72+
// Find the method with the name of the test.
73+
found := false
74+
azt := reflect.TypeOf(az)
75+
MethodLoop:
76+
for i := 0; i < azt.NumMethod(); i++ {
77+
method := azt.Method(i)
78+
if method.Name == methodName {
79+
resp := reflect.ValueOf(az).Method(i).Call(append([]reflect.Value{reflect.ValueOf(ctx)}, testCase.Inputs...))
80+
var _ = resp
81+
found = true
82+
break MethodLoop
83+
}
84+
}
85+
86+
require.True(t, found, "method %q does not exist", testName)
87+
}
88+
89+
func methodInputs(inputs ...any) []reflect.Value {
90+
out := make([]reflect.Value, 0)
91+
for _, input := range inputs {
92+
input := input
93+
out = append(out, reflect.ValueOf(input))
94+
}
95+
return out
96+
}
97+
98+
func asserts(inputs ...any) []AssertRBAC {
99+
if len(inputs)%2 != 0 {
100+
panic(fmt.Sprintf("Must be an even length number of args, found %d", len(inputs)))
101+
}
102+
103+
out := make([]AssertRBAC, 0)
104+
for i := 0; i < len(inputs); i += 2 {
105+
obj, ok := inputs[i].(rbac.Objecter)
106+
if !ok {
107+
panic(fmt.Sprintf("object type '%T' not a supported key", obj))
108+
}
109+
110+
var actions []rbac.Action
111+
actions, ok = inputs[i+1].([]rbac.Action)
112+
if !ok {
113+
action, ok := inputs[i+1].(rbac.Action)
114+
if !ok {
115+
// Could be the string type.
116+
actionAsString, ok := inputs[i+1].(string)
117+
if !ok {
118+
panic(fmt.Sprintf("action type '%T' not a supported action", obj))
119+
}
120+
action = rbac.Action(actionAsString)
121+
}
122+
actions = []rbac.Action{action}
123+
}
124+
125+
out = append(out, AssertRBAC{
126+
Object: rbac.Object{},
127+
Actions: actions,
128+
})
129+
}
130+
return out
131+
}

coderd/authzquery/workspace.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,20 @@ func (q *AuthzQuerier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.U
201201
return authorizedFetch(q.authorizer, q.database.GetWorkspaceByAgentID)(ctx, agentID)
202202
}
203203

204+
// GetWorkspaceByID
205+
// Gen: Workspace
206+
// Args: Workspace.ID
207+
// Assert: Workspace.read
204208
func (q *AuthzQuerier) GetWorkspaceByID(ctx context.Context, id uuid.UUID) (database.Workspace, error) {
205209
return authorizedFetch(q.authorizer, q.database.GetWorkspaceByID)(ctx, id)
206210
}
207211

212+
//OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
213+
//Deleted bool `db:"deleted" json:"deleted"`
214+
//Name string `db:"name" json:"name"`
215+
216+
// GetWorkspaceByOwnerIDAndName
217+
// Gen: Workspace
208218
func (q *AuthzQuerier) GetWorkspaceByOwnerIDAndName(ctx context.Context, arg database.GetWorkspaceByOwnerIDAndNameParams) (database.Workspace, error) {
209219
return authorizedFetch(q.authorizer, q.database.GetWorkspaceByOwnerIDAndName)(ctx, arg)
210220
}

0 commit comments

Comments
 (0)