Skip to content

Commit 5f8d0e5

Browse files
authored
feat: Add RBAC to /files endpoints (#1664)
* feat: Add RBAC to /files endpoints
1 parent f763472 commit 5f8d0e5

File tree

4 files changed

+25
-6
lines changed

4 files changed

+25
-6
lines changed

coderd/coderd.go

Lines changed: 1 addition & 0 deletions
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

Lines changed: 9 additions & 2 deletions
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

Lines changed: 14 additions & 3 deletions
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

Lines changed: 1 addition & 1 deletion
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy