44 "fmt"
55 "net/http"
66
7+ "github.com/google/uuid"
8+
79 "golang.org/x/xerrors"
810
911 "cdr.dev/slog"
@@ -18,7 +20,7 @@ import (
1820// This is faster than calling Authorize() on each object.
1921func AuthorizeFilter [O rbac.Objecter ](h * HTTPAuthorizer , r * http.Request , action rbac.Action , objects []O ) ([]O , error ) {
2022 roles := httpmw .UserAuthorization (r )
21- objects , err := rbac .Filter (r .Context (), h .Authorizer , roles .ID .String (), roles .Roles , roles .Scope .ToRBAC (), action , objects )
23+ objects , err := rbac .Filter (r .Context (), h .Authorizer , roles .ID .String (), roles .Roles , roles .Scope .ToRBAC (), roles . Groups , action , objects )
2224 if err != nil {
2325 // Log the error as Filter should not be erroring.
2426 h .Logger .Error (r .Context (), "filter failed" ,
@@ -63,7 +65,7 @@ func (api *API) Authorize(r *http.Request, action rbac.Action, object rbac.Objec
6365// }
6466func (h * HTTPAuthorizer ) Authorize (r * http.Request , action rbac.Action , object rbac.Objecter ) bool {
6567 roles := httpmw .UserAuthorization (r )
66- err := h .Authorizer .ByRoleName (r .Context (), roles .ID .String (), roles .Roles , roles .Scope .ToRBAC (), action , object .RBACObject ())
68+ err := h .Authorizer .ByRoleName (r .Context (), roles .ID .String (), roles .Roles , roles .Scope .ToRBAC (), roles . Groups , action , object .RBACObject ())
6769 if err != nil {
6870 // Log the errors for debugging
6971 internalError := new (rbac.UnauthorizedError )
@@ -95,7 +97,7 @@ func (h *HTTPAuthorizer) Authorize(r *http.Request, action rbac.Action, object r
9597// Note the authorization is only for the given action and object type.
9698func (h * HTTPAuthorizer ) AuthorizeSQLFilter (r * http.Request , action rbac.Action , objectType string ) (rbac.AuthorizeFilter , error ) {
9799 roles := httpmw .UserAuthorization (r )
98- prepared , err := h .Authorizer .PrepareByRoleName (r .Context (), roles .ID .String (), roles .Roles , roles .Scope .ToRBAC (), action , objectType )
100+ prepared , err := h .Authorizer .PrepareByRoleName (r .Context (), roles .ID .String (), roles .Roles , roles .Scope .ToRBAC (), roles . Groups , action , objectType )
99101 if err != nil {
100102 return nil , xerrors .Errorf ("prepare filter: %w" , err )
101103 }
@@ -127,6 +129,28 @@ func (api *API) checkAuthorization(rw http.ResponseWriter, r *http.Request) {
127129 )
128130
129131 response := make (codersdk.AuthorizationResponse )
132+ // Prevent using too many resources by ID. This prevents database abuse
133+ // from this endpoint. This also prevents misuse of this endpoint, as
134+ // resource_id should be used for single objects, not for a list of them.
135+ var (
136+ idFetch int
137+ maxFetch = 10
138+ )
139+ for _ , v := range params .Checks {
140+ if v .Object .ResourceID != "" {
141+ idFetch ++
142+ }
143+ }
144+ if idFetch > maxFetch {
145+ httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
146+ Message : fmt .Sprintf (
147+ "Endpoint only supports using \" resource_id\" field %d times, found %d usages. Remove %d objects with this field set." ,
148+ maxFetch , idFetch , idFetch - maxFetch ,
149+ ),
150+ })
151+ return
152+ }
153+
130154 for k , v := range params .Checks {
131155 if v .Object .ResourceType == "" {
132156 httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
@@ -135,15 +159,60 @@ func (api *API) checkAuthorization(rw http.ResponseWriter, r *http.Request) {
135159 return
136160 }
137161
138- if v .Object .OwnerID == "me" {
139- v .Object .OwnerID = auth .ID .String ()
162+ obj := rbac.Object {
163+ Owner : v .Object .OwnerID ,
164+ OrgID : v .Object .OrganizationID ,
165+ Type : v .Object .ResourceType ,
140166 }
141- err := api .Authorizer .ByRoleName (r .Context (), auth .ID .String (), auth .Roles , auth .Scope .ToRBAC (), rbac .Action (v .Action ),
142- rbac.Object {
143- Owner : v .Object .OwnerID ,
144- OrgID : v .Object .OrganizationID ,
145- Type : v .Object .ResourceType ,
146- })
167+ if obj .Owner == "me" {
168+ obj .Owner = auth .ID .String ()
169+ }
170+
171+ // If a resource ID is specified, fetch that specific resource.
172+ if v .Object .ResourceID != "" {
173+ id , err := uuid .Parse (v .Object .ResourceID )
174+ if err != nil {
175+ httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
176+ Message : fmt .Sprintf ("Object %q id is not a valid uuid." , v .Object .ResourceID ),
177+ Validations : []codersdk.ValidationError {{Field : "resource_id" , Detail : err .Error ()}},
178+ })
179+ return
180+ }
181+
182+ var dbObj rbac.Objecter
183+ var dbErr error
184+ // Only support referencing some resources by ID.
185+ switch v .Object .ResourceType {
186+ case rbac .ResourceWorkspaceExecution .Type :
187+ wrkSpace , err := api .Database .GetWorkspaceByID (ctx , id )
188+ if err == nil {
189+ dbObj = wrkSpace .ExecutionRBAC ()
190+ }
191+ dbErr = err
192+ case rbac .ResourceWorkspace .Type :
193+ dbObj , dbErr = api .Database .GetWorkspaceByID (ctx , id )
194+ case rbac .ResourceTemplate .Type :
195+ dbObj , dbErr = api .Database .GetTemplateByID (ctx , id )
196+ case rbac .ResourceUser .Type :
197+ dbObj , dbErr = api .Database .GetUserByID (ctx , id )
198+ case rbac .ResourceGroup .Type :
199+ dbObj , dbErr = api .Database .GetGroupByID (ctx , id )
200+ default :
201+ httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
202+ Message : fmt .Sprintf ("Object type %q does not support \" resource_id\" field." , v .Object .ResourceType ),
203+ Validations : []codersdk.ValidationError {{Field : "resource_type" , Detail : err .Error ()}},
204+ })
205+ return
206+ }
207+ if dbErr != nil {
208+ // 404 or unauthorized is false
209+ response [k ] = false
210+ continue
211+ }
212+ obj = dbObj .RBACObject ()
213+ }
214+
215+ err := api .Authorizer .ByRoleName (r .Context (), auth .ID .String (), auth .Roles , auth .Scope .ToRBAC (), auth .Groups , rbac .Action (v .Action ), obj )
147216 response [k ] = err == nil
148217 }
149218
0 commit comments