Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit aaf08d5

Browse files
Emyrkkylecarbs
authored andcommitted
feat: Add RBAC to /files endpoints (#1664)
* feat: Add RBAC to /files endpoints
1 parent 524fed2 commit aaf08d5

File tree

4 files changed

+25
-6
lines changed

4 files changed

+25
-6
lines changed

coderd/coderd.go

+1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ func newRouter(options *Options, a *api) chi.Router {
134134
r.Route("/files", func(r chi.Router) {
135135
r.Use(
136136
apiKeyMiddleware,
137+
authRolesMiddleware,
137138
// This number is arbitrary, but reading/writing
138139
// file content is expensive so it should be small.
139140
httpmw.RateLimitPerMinute(12),

coderd/coderd_test.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/coder/coder/buildinfo"
1717
"github.com/coder/coder/coderd/coderdtest"
1818
"github.com/coder/coder/coderd/rbac"
19+
"github.com/coder/coder/codersdk"
1920
)
2021

2122
func TestMain(m *testing.M) {
@@ -34,6 +35,7 @@ func TestBuildInfo(t *testing.T) {
3435
// TestAuthorizeAllEndpoints will check `authorize` is called on every endpoint registered.
3536
func TestAuthorizeAllEndpoints(t *testing.T) {
3637
t.Parallel()
38+
ctx := context.Background()
3739

3840
authorizer := &fakeAuthorizer{}
3941
srv, client, _ := coderdtest.NewWithServer(t, &coderdtest.Options{
@@ -50,6 +52,8 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
5052
template := coderdtest.CreateTemplate(t, client, admin.OrganizationID, version.ID)
5153
workspace := coderdtest.CreateWorkspace(t, client, admin.OrganizationID, template.ID)
5254
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
55+
file, err := client.Upload(ctx, codersdk.ContentTypeTar, make([]byte, 1024))
56+
require.NoError(t, err, "upload file")
5357

5458
// Always fail auth from this point forward
5559
authorizer.AlwaysReturn = rbac.ForbiddenWithInternal(xerrors.New("fake implementation"), nil, nil)
@@ -121,8 +125,6 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
121125

122126
"POST:/api/v2/users/{user}/organizations": {NoAuthorize: true},
123127

124-
"POST:/api/v2/files": {NoAuthorize: true},
125-
"GET:/api/v2/files/{hash}": {NoAuthorize: true},
126128
"GET:/api/v2/workspaces/{workspace}/watch": {NoAuthorize: true},
127129

128130
// These endpoints have more assertions. This is good, add more endpoints to assert if you can!
@@ -184,6 +186,10 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
184186
AssertObject: workspaceRBACObj,
185187
},
186188

189+
"POST:/api/v2/files": {AssertAction: rbac.ActionCreate, AssertObject: rbac.ResourceFile},
190+
"GET:/api/v2/files/{fileHash}": {AssertAction: rbac.ActionRead,
191+
AssertObject: rbac.ResourceFile.WithOwner(admin.UserID.String()).WithID(file.Hash)},
192+
187193
// These endpoints need payloads to get to the auth part. Payloads will be required
188194
"PUT:/api/v2/users/{user}/roles": {StatusCode: http.StatusBadRequest, NoAuthorize: true},
189195
"POST:/api/v2/workspaces/{workspace}/builds": {StatusCode: http.StatusBadRequest, NoAuthorize: true},
@@ -220,6 +226,7 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
220226
route = strings.ReplaceAll(route, "{workspacebuild}", workspace.LatestBuild.ID.String())
221227
route = strings.ReplaceAll(route, "{workspacename}", workspace.Name)
222228
route = strings.ReplaceAll(route, "{workspacebuildname}", workspace.LatestBuild.Name)
229+
route = strings.ReplaceAll(route, "{hash}", file.Hash)
223230

224231
resp, err := client.Request(context.Background(), method, route, nil)
225232
require.NoError(t, err, "do req")

coderd/files.go

+14-3
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,18 @@ import (
1414
"github.com/coder/coder/coderd/database"
1515
"github.com/coder/coder/coderd/httpapi"
1616
"github.com/coder/coder/coderd/httpmw"
17+
"github.com/coder/coder/coderd/rbac"
1718
"github.com/coder/coder/codersdk"
1819
)
1920

2021
func (api *api) postFile(rw http.ResponseWriter, r *http.Request) {
2122
apiKey := httpmw.APIKey(r)
23+
// This requires the site wide action to create files.
24+
// Once created, a user can read their own files uploaded
25+
if !api.Authorize(rw, r, rbac.ActionCreate, rbac.ResourceFile) {
26+
return
27+
}
28+
2229
contentType := r.Header.Get("Content-Type")
2330

2431
switch contentType {
@@ -77,9 +84,7 @@ func (api *api) fileByHash(rw http.ResponseWriter, r *http.Request) {
7784
}
7885
file, err := api.Database.GetFileByHash(r.Context(), hash)
7986
if errors.Is(err, sql.ErrNoRows) {
80-
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
81-
Message: "no file exists with that hash",
82-
})
87+
httpapi.Forbidden(rw)
8388
return
8489
}
8590
if err != nil {
@@ -88,6 +93,12 @@ func (api *api) fileByHash(rw http.ResponseWriter, r *http.Request) {
8893
})
8994
return
9095
}
96+
97+
if !api.Authorize(rw, r, rbac.ActionRead,
98+
rbac.ResourceFile.WithOwner(file.CreatedBy.String()).WithID(file.Hash)) {
99+
return
100+
}
101+
91102
rw.Header().Set("Content-Type", file.Mimetype)
92103
rw.WriteHeader(http.StatusOK)
93104
_, _ = rw.Write(file.Data)

coderd/files_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func TestDownload(t *testing.T) {
5050
_, _, err := client.Download(context.Background(), "something")
5151
var apiErr *codersdk.Error
5252
require.ErrorAs(t, err, &apiErr)
53-
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
53+
require.Equal(t, http.StatusForbidden, apiErr.StatusCode())
5454
})
5555

5656
t.Run("Insert", func(t *testing.T) {

0 commit comments

Comments
 (0)