@@ -34,42 +34,11 @@ import (
34
34
const (
35
35
// This needs to be a super unique query parameter because we don't want to
36
36
// conflict with query parameters that users may use.
37
- // TODO: this will make dogfooding harder so come up with a more unique
38
- // solution
39
37
//nolint:gosec
40
38
subdomainProxyAPIKeyParam = "coder_application_connect_api_key_35e783"
41
39
redirectURIQueryParam = "redirect_uri"
42
40
)
43
41
44
- type AppAuthorizer interface {
45
- // Authorize returns true if the request is authorized to access an app at
46
- // share level `AppSharingLevel` in `workspace`. An error is only returned if
47
- // there is a processing error. "Unauthorized" errors should not be
48
- // returned.
49
- //
50
- // It must be able to handle optional user authorization. Use
51
- // `httpmw.*Optional` methods.
52
- Authorize (r * http.Request , db database.Store , AppSharingLevel database.AppSharingLevel , workspace database.Workspace ) (bool , error )
53
- }
54
-
55
- type AGPLAppAuthorizer struct {
56
- RBAC rbac.Authorizer
57
- }
58
-
59
- var _ AppAuthorizer = & AGPLAppAuthorizer {}
60
-
61
- // Authorize provides an AGPL implementation of AppAuthorizer. It does not
62
- // support app sharing levels as they are an enterprise feature.
63
- func (a AGPLAppAuthorizer ) Authorize (r * http.Request , _ database.Store , _ database.AppSharingLevel , workspace database.Workspace ) (bool , error ) {
64
- roles , ok := httpmw .UserAuthorizationOptional (r )
65
- if ! ok {
66
- return false , nil
67
- }
68
-
69
- err := a .RBAC .ByRoleName (r .Context (), roles .ID .String (), roles .Roles , roles .Scope .ToRBAC (), rbac .ActionCreate , workspace .ApplicationConnectRBAC ())
70
- return err == nil , nil
71
- }
72
-
73
42
func (api * API ) appHost (rw http.ResponseWriter , r * http.Request ) {
74
43
httpapi .Write (r .Context (), rw , http .StatusOK , codersdk.GetAppHostResponse {
75
44
Host : api .AppHostname ,
@@ -326,12 +295,69 @@ func (api *API) lookupWorkspaceApp(rw http.ResponseWriter, r *http.Request, agen
326
295
return app , true
327
296
}
328
297
298
+ func (api * API ) authorizeWorkspaceApp (r * http.Request , sharingLevel database.AppSharingLevel , workspace database.Workspace ) (bool , error ) {
299
+ ctx := r .Context ()
300
+
301
+ // Short circuit if not authenticated.
302
+ roles , ok := httpmw .UserAuthorizationOptional (r )
303
+ if ! ok {
304
+ // The user is not authenticated, so they can only access the app if it
305
+ // is public.
306
+ return sharingLevel == database .AppSharingLevelPublic , nil
307
+ }
308
+
309
+ // Do a standard RBAC check. This accounts for share level "owner" and any
310
+ // other RBAC rules that may be in place.
311
+ //
312
+ // Regardless of share level or whether it's enabled or not, the owner of
313
+ // the workspace can always access applications (as long as their key's
314
+ // scope allows it).
315
+ err := api .Authorizer .ByRoleName (ctx , roles .ID .String (), roles .Roles , roles .Scope .ToRBAC (), rbac .ActionCreate , workspace .ApplicationConnectRBAC ())
316
+ if err == nil {
317
+ return true , nil
318
+ }
319
+
320
+ switch sharingLevel {
321
+ case database .AppSharingLevelOwner :
322
+ // We essentially already did this above.
323
+ case database .AppSharingLevelTemplate :
324
+ // Check if the user has access to the same template as the workspace.
325
+ template , err := api .Database .GetTemplateByID (ctx , workspace .TemplateID )
326
+ if err != nil {
327
+ return false , xerrors .Errorf ("get template %q: %w" , workspace .TemplateID , err )
328
+ }
329
+
330
+ err = api .Authorizer .ByRoleName (ctx , roles .ID .String (), roles .Roles , roles .Scope .ToRBAC (), rbac .ActionRead , template .RBACObject ())
331
+ if err == nil {
332
+ return true , nil
333
+ }
334
+ case database .AppSharingLevelAuthenticated :
335
+ // The user is authenticated at this point, but we need to make sure
336
+ // that they have ApplicationConnect permissions to their own
337
+ // workspaces. This ensures that the key's scope has permission to
338
+ // connect to workspace apps.
339
+ object := rbac .ResourceWorkspaceApplicationConnect .WithOwner (roles .ID .String ())
340
+ err := api .Authorizer .ByRoleName (ctx , roles .ID .String (), roles .Roles , roles .Scope .ToRBAC (), rbac .ActionCreate , object )
341
+ if err == nil {
342
+ return true , nil
343
+ }
344
+ case database .AppSharingLevelPublic :
345
+ // We don't really care about scopes and stuff if it's public anyways.
346
+ // Someone with a restricted-scope API key could just not submit the
347
+ // API key cookie in the request and access the page.
348
+ return true , nil
349
+ }
350
+
351
+ // No checks were successful.
352
+ return false , nil
353
+ }
354
+
329
355
// fetchWorkspaceApplicationAuth authorizes the user using api.AppAuthorizer
330
356
// for a given app share level in the given workspace. The user's authorization
331
357
// status is returned. If a server error occurs, a HTML error page is rendered
332
358
// and false is returned so the caller can return early.
333
- func (api * API ) fetchWorkspaceApplicationAuth (rw http.ResponseWriter , r * http.Request , workspace database.Workspace , AppSharingLevel database.AppSharingLevel ) (authed bool , ok bool ) {
334
- ok , err := ( * api .AppAuthorizer . Load ()). Authorize ( r , api . Database , AppSharingLevel , workspace )
359
+ func (api * API ) fetchWorkspaceApplicationAuth (rw http.ResponseWriter , r * http.Request , workspace database.Workspace , appSharingLevel database.AppSharingLevel ) (authed bool , ok bool ) {
360
+ ok , err := api .authorizeWorkspaceApp ( r , appSharingLevel , workspace )
335
361
if err != nil {
336
362
api .Logger .Error (r .Context (), "authorize workspace app" , slog .Error (err ))
337
363
site .RenderStaticErrorPage (rw , r , site.ErrorPageData {
@@ -351,8 +377,8 @@ func (api *API) fetchWorkspaceApplicationAuth(rw http.ResponseWriter, r *http.Re
351
377
// for a given app share level in the given workspace. If the user is not
352
378
// authorized or a server error occurs, a discrete HTML error page is rendered
353
379
// and false is returned so the caller can return early.
354
- func (api * API ) checkWorkspaceApplicationAuth (rw http.ResponseWriter , r * http.Request , workspace database.Workspace , AppSharingLevel database.AppSharingLevel ) bool {
355
- authed , ok := api .fetchWorkspaceApplicationAuth (rw , r , workspace , AppSharingLevel )
380
+ func (api * API ) checkWorkspaceApplicationAuth (rw http.ResponseWriter , r * http.Request , workspace database.Workspace , appSharingLevel database.AppSharingLevel ) bool {
381
+ authed , ok := api .fetchWorkspaceApplicationAuth (rw , r , workspace , appSharingLevel )
356
382
if ! ok {
357
383
return false
358
384
}
0 commit comments