Skip to content

Commit fd9231d

Browse files
committed
Merge branch 'main' into abhineetjain/delete-session-token-api
2 parents 2ad0fbc + 9929189 commit fd9231d

File tree

6 files changed

+252
-9
lines changed

6 files changed

+252
-9
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ INSTALL_DIR=$(shell go env GOPATH)/bin
44
GOOS=$(shell go env GOOS)
55
GOARCH=$(shell go env GOARCH)
66

7-
bin: $(shell find . -not -path './vendor/*' -type f -name '*.go') go.mod go.sum
7+
bin: $(shell find . -not -path './vendor/*' -type f -name '*.go') go.mod go.sum $(shell find ./examples/templates)
88
@echo "== This builds binaries for command-line usage."
99
@echo "== Use \"make build\" to embed the site."
1010
goreleaser build --snapshot --rm-dist --single-target
@@ -24,7 +24,7 @@ dev:
2424
./scripts/develop.sh
2525
.PHONY: dev
2626

27-
dist/artifacts.json: site/out/index.html $(shell find . -not -path './vendor/*' -type f -name '*.go') go.mod go.sum
27+
dist/artifacts.json: site/out/index.html $(shell find . -not -path './vendor/*' -type f -name '*.go') go.mod go.sum $(shell find ./examples/templates)
2828
goreleaser release --snapshot --rm-dist --skip-sign
2929

3030
fmt/prettier:

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ Discord"](https://img.shields.io/badge/join-us%20on%20Discord-gray.svg?longCache
88
Follow](https://img.shields.io/twitter/follow/CoderHQ?label=%40CoderHQ&style=social)](https://twitter.com/coderhq)
99
[![codecov](https://codecov.io/gh/coder/coder/branch/main/graph/badge.svg?token=TNLW3OAP6G)](https://codecov.io/gh/coder/coder)
1010

11-
Coder turns your cloud into a fleet of remote development servers.
11+
Coder creates remote development machines so you can develop your code from anywhere.
12+
13+
**Coder is in an alpha state.** But, any serious bugs are P1 for us so please report them.
1214

1315
<p align="center">
1416
<img src="./docs/images/hero-image.png">

agent/agent.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
359359
if err != nil {
360360
return nil, xerrors.Errorf("getting os executable: %w", err)
361361
}
362+
cmd.Env = append(cmd.Env, fmt.Sprintf("USER=%s", username))
362363
cmd.Env = append(cmd.Env, fmt.Sprintf(`PATH=%s%c%s`, os.Getenv("PATH"), filepath.ListSeparator, filepath.Dir(executablePath)))
363364
// Git on Windows resolves with UNIX-style paths.
364365
// If using backslashes, it's unable to find the executable.

coderd/httpapi/httpapi.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func Read(rw http.ResponseWriter, r *http.Request, value interface{}) bool {
104104
for _, validationError := range validationErrors {
105105
apiErrors = append(apiErrors, Error{
106106
Field: validationError.Field(),
107-
Detail: validationError.Tag(),
107+
Detail: fmt.Sprintf("Validation failed for tag %q with value: \"%v\"", validationError.Tag(), validationError.Value()),
108108
})
109109
}
110110
Write(rw, http.StatusBadRequest, Response{

coderd/httpapi/httpapi_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func TestRead(t *testing.T) {
5858

5959
var validate toValidate
6060
require.True(t, httpapi.Read(rw, r, &validate))
61-
require.Equal(t, validate.Value, "hi")
61+
require.Equal(t, "hi", validate.Value)
6262
})
6363

6464
t.Run("ValidateFailure", func(t *testing.T) {
@@ -75,8 +75,8 @@ func TestRead(t *testing.T) {
7575
err := json.NewDecoder(rw.Body).Decode(&v)
7676
require.NoError(t, err)
7777
require.Len(t, v.Errors, 1)
78-
require.Equal(t, v.Errors[0].Field, "value")
79-
require.Equal(t, v.Errors[0].Detail, "required")
78+
require.Equal(t, "value", v.Errors[0].Field)
79+
require.Equal(t, "Validation failed for tag \"required\" with value: \"\"", v.Errors[0].Detail)
8080
})
8181
}
8282

@@ -140,7 +140,7 @@ func TestReadUsername(t *testing.T) {
140140
r := httptest.NewRequest("POST", "/", bytes.NewBuffer(data))
141141

142142
var validate toValidate
143-
require.Equal(t, httpapi.Read(rw, r, &validate), testCase.Valid)
143+
require.Equal(t, testCase.Valid, httpapi.Read(rw, r, &validate))
144144
})
145145
}
146146
}

coderd/rbac/builtin_test.go

Lines changed: 241 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,256 @@
11
package rbac_test
22

33
import (
4+
"context"
45
"fmt"
56
"testing"
67

78
"github.com/google/uuid"
8-
9+
"github.com/stretchr/testify/assert"
910
"github.com/stretchr/testify/require"
1011

1112
"github.com/coder/coder/coderd/rbac"
1213
)
1314

15+
type authSubject struct {
16+
// Name is helpful for test assertions
17+
Name string
18+
UserID string
19+
Roles []string
20+
}
21+
22+
func TestRolePermissions(t *testing.T) {
23+
t.Parallel()
24+
25+
auth, err := rbac.NewAuthorizer()
26+
require.NoError(t, err, "new rego authorizer")
27+
28+
// currentUser is anything that references "me", "mine", or "my".
29+
currentUser := uuid.New()
30+
adminID := uuid.New()
31+
orgID := uuid.New()
32+
otherOrg := uuid.New()
33+
34+
// Subjects to user
35+
memberMe := authSubject{Name: "member_me", UserID: currentUser.String(), Roles: []string{rbac.RoleMember()}}
36+
orgMemberMe := authSubject{Name: "org_member_me", UserID: currentUser.String(), Roles: []string{rbac.RoleMember(), rbac.RoleOrgMember(orgID)}}
37+
38+
admin := authSubject{Name: "admin", UserID: adminID.String(), Roles: []string{rbac.RoleMember(), rbac.RoleAdmin()}}
39+
orgAdmin := authSubject{Name: "org_admin", UserID: adminID.String(), Roles: []string{rbac.RoleMember(), rbac.RoleOrgMember(orgID), rbac.RoleOrgAdmin(orgID)}}
40+
41+
otherOrgMember := authSubject{Name: "org_member_other", UserID: uuid.NewString(), Roles: []string{rbac.RoleMember(), rbac.RoleOrgMember(otherOrg)}}
42+
otherOrgAdmin := authSubject{Name: "org_admin_other", UserID: uuid.NewString(), Roles: []string{rbac.RoleMember(), rbac.RoleOrgMember(otherOrg), rbac.RoleOrgAdmin(otherOrg)}}
43+
44+
// requiredSubjects are required to be asserted in each test case. This is
45+
// to make sure one is not forgotten.
46+
requiredSubjects := []authSubject{memberMe, admin, orgMemberMe, orgAdmin, otherOrgAdmin, otherOrgMember}
47+
48+
testCases := []struct {
49+
// Name the test case to better locate the failing test case.
50+
Name string
51+
Resource rbac.Object
52+
Actions []rbac.Action
53+
// AuthorizeMap must cover all subjects in 'requiredSubjects'.
54+
// This map will run an Authorize() check with the resource, action,
55+
// and subjects. The subjects are split into 2 categories, "true" and
56+
// "false".
57+
// true: Subjects who Authorize should return no error
58+
// false: Subjects who Authorize should return forbidden.
59+
AuthorizeMap map[bool][]authSubject
60+
}{
61+
{
62+
Name: "MyUser",
63+
Actions: []rbac.Action{rbac.ActionRead},
64+
Resource: rbac.ResourceUser.WithID(currentUser.String()),
65+
AuthorizeMap: map[bool][]authSubject{
66+
true: {admin, memberMe, orgMemberMe, orgAdmin, otherOrgMember, otherOrgAdmin},
67+
false: {},
68+
},
69+
},
70+
{
71+
Name: "AUser",
72+
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
73+
Resource: rbac.ResourceUser,
74+
AuthorizeMap: map[bool][]authSubject{
75+
true: {admin},
76+
false: {memberMe, orgMemberMe, orgAdmin, otherOrgMember, otherOrgAdmin},
77+
},
78+
},
79+
{
80+
Name: "MyWorkspaceInOrg",
81+
// When creating the WithID won't be set, but it does not change the result.
82+
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
83+
Resource: rbac.ResourceWorkspace.InOrg(orgID).WithOwner(currentUser.String()).WithID(uuid.NewString()),
84+
AuthorizeMap: map[bool][]authSubject{
85+
true: {admin, orgMemberMe, orgAdmin},
86+
false: {memberMe, otherOrgAdmin, otherOrgMember},
87+
},
88+
},
89+
{
90+
Name: "Templates",
91+
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
92+
Resource: rbac.ResourceTemplate.InOrg(orgID).WithID(uuid.NewString()),
93+
AuthorizeMap: map[bool][]authSubject{
94+
true: {admin, orgAdmin},
95+
false: {memberMe, orgMemberMe, otherOrgAdmin, otherOrgMember},
96+
},
97+
},
98+
{
99+
Name: "ReadTemplates",
100+
Actions: []rbac.Action{rbac.ActionRead},
101+
Resource: rbac.ResourceTemplate.InOrg(orgID).WithID(uuid.NewString()),
102+
AuthorizeMap: map[bool][]authSubject{
103+
true: {admin, orgMemberMe, orgAdmin},
104+
false: {memberMe, otherOrgAdmin, otherOrgMember},
105+
},
106+
},
107+
{
108+
Name: "Files",
109+
Actions: []rbac.Action{rbac.ActionCreate},
110+
Resource: rbac.ResourceFile,
111+
AuthorizeMap: map[bool][]authSubject{
112+
true: {admin},
113+
false: {orgMemberMe, orgAdmin, memberMe, otherOrgAdmin, otherOrgMember},
114+
},
115+
},
116+
{
117+
Name: "MyFile",
118+
Actions: []rbac.Action{rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
119+
Resource: rbac.ResourceFile.WithID(uuid.NewString()).WithOwner(currentUser.String()),
120+
AuthorizeMap: map[bool][]authSubject{
121+
true: {admin, memberMe, orgMemberMe},
122+
false: {orgAdmin, otherOrgAdmin, otherOrgMember},
123+
},
124+
},
125+
{
126+
Name: "CreateOrganizations",
127+
Actions: []rbac.Action{rbac.ActionCreate},
128+
Resource: rbac.ResourceOrganization,
129+
AuthorizeMap: map[bool][]authSubject{
130+
true: {admin},
131+
false: {orgAdmin, otherOrgAdmin, otherOrgMember, memberMe, orgMemberMe},
132+
},
133+
},
134+
{
135+
Name: "Organizations",
136+
Actions: []rbac.Action{rbac.ActionUpdate, rbac.ActionDelete},
137+
Resource: rbac.ResourceOrganization.InOrg(orgID).WithID(orgID.String()),
138+
AuthorizeMap: map[bool][]authSubject{
139+
true: {admin, orgAdmin},
140+
false: {otherOrgAdmin, otherOrgMember, memberMe, orgMemberMe},
141+
},
142+
},
143+
{
144+
Name: "ReadOrganizations",
145+
Actions: []rbac.Action{rbac.ActionRead},
146+
Resource: rbac.ResourceOrganization.InOrg(orgID).WithID(orgID.String()),
147+
AuthorizeMap: map[bool][]authSubject{
148+
true: {admin, orgAdmin, orgMemberMe},
149+
false: {otherOrgAdmin, otherOrgMember, memberMe},
150+
},
151+
},
152+
{
153+
Name: "RoleAssignment",
154+
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
155+
Resource: rbac.ResourceRoleAssignment,
156+
AuthorizeMap: map[bool][]authSubject{
157+
true: {admin},
158+
false: {orgAdmin, orgMemberMe, otherOrgAdmin, otherOrgMember, memberMe},
159+
},
160+
},
161+
{
162+
Name: "ReadRoleAssignment",
163+
Actions: []rbac.Action{rbac.ActionRead},
164+
Resource: rbac.ResourceRoleAssignment,
165+
AuthorizeMap: map[bool][]authSubject{
166+
true: {admin, orgAdmin, orgMemberMe, otherOrgAdmin, otherOrgMember, memberMe},
167+
false: {},
168+
},
169+
},
170+
{
171+
Name: "OrgRoleAssignment",
172+
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
173+
Resource: rbac.ResourceOrgRoleAssignment.InOrg(orgID),
174+
AuthorizeMap: map[bool][]authSubject{
175+
true: {admin, orgAdmin},
176+
false: {orgMemberMe, otherOrgAdmin, otherOrgMember, memberMe},
177+
},
178+
},
179+
{
180+
Name: "ReadOrgRoleAssignment",
181+
Actions: []rbac.Action{rbac.ActionRead},
182+
Resource: rbac.ResourceOrgRoleAssignment.InOrg(orgID),
183+
AuthorizeMap: map[bool][]authSubject{
184+
true: {admin, orgAdmin, orgMemberMe},
185+
false: {otherOrgAdmin, otherOrgMember, memberMe},
186+
},
187+
},
188+
{
189+
Name: "APIKey",
190+
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
191+
Resource: rbac.ResourceAPIKey.WithOwner(currentUser.String()).WithID(uuid.NewString()),
192+
AuthorizeMap: map[bool][]authSubject{
193+
true: {admin, orgMemberMe, memberMe},
194+
false: {orgAdmin, otherOrgAdmin, otherOrgMember},
195+
},
196+
},
197+
{
198+
Name: "UserData",
199+
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
200+
Resource: rbac.ResourceUserData.WithOwner(currentUser.String()).WithID(currentUser.String()),
201+
AuthorizeMap: map[bool][]authSubject{
202+
true: {admin, orgMemberMe, memberMe},
203+
false: {orgAdmin, otherOrgAdmin, otherOrgMember},
204+
},
205+
},
206+
{
207+
Name: "ManageOrgMember",
208+
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
209+
Resource: rbac.ResourceOrganizationMember.InOrg(orgID).WithID(uuid.NewString()),
210+
AuthorizeMap: map[bool][]authSubject{
211+
true: {admin, orgAdmin},
212+
false: {orgMemberMe, memberMe, otherOrgAdmin, otherOrgMember},
213+
},
214+
},
215+
{
216+
Name: "ReadOrgMember",
217+
Actions: []rbac.Action{rbac.ActionRead},
218+
Resource: rbac.ResourceOrganizationMember.InOrg(orgID).WithID(uuid.NewString()),
219+
AuthorizeMap: map[bool][]authSubject{
220+
true: {admin, orgAdmin, orgMemberMe},
221+
false: {memberMe, otherOrgAdmin, otherOrgMember},
222+
},
223+
},
224+
}
225+
226+
for _, c := range testCases {
227+
c := c
228+
t.Run(c.Name, func(t *testing.T) {
229+
t.Parallel()
230+
remainingSubjs := make(map[string]struct{})
231+
for _, subj := range requiredSubjects {
232+
remainingSubjs[subj.Name] = struct{}{}
233+
}
234+
235+
for _, action := range c.Actions {
236+
for result, subjs := range c.AuthorizeMap {
237+
for _, subj := range subjs {
238+
delete(remainingSubjs, subj.Name)
239+
msg := fmt.Sprintf("%s as %q doing %q on %q", c.Name, subj.Name, action, c.Resource.Type)
240+
err := auth.ByRoleName(context.Background(), subj.UserID, subj.Roles, action, c.Resource)
241+
if result {
242+
assert.NoError(t, err, fmt.Sprintf("Should pass: %s", msg))
243+
} else {
244+
assert.ErrorContains(t, err, "forbidden", fmt.Sprintf("Should fail: %s", msg))
245+
}
246+
}
247+
}
248+
}
249+
require.Empty(t, remainingSubjs, "test should cover all subjects")
250+
})
251+
}
252+
}
253+
14254
func TestIsOrgRole(t *testing.T) {
15255
t.Parallel()
16256
randomUUID := uuid.New()

0 commit comments

Comments
 (0)