chore: merge authorization contexts (#12816)

* chore: merge authorization contexts

Instead of 2 auth contexts from apikey and dbauthz, merge them to
just use dbauthz. It is annoying to have two.

* fixup authorization reference
This commit is contained in:
Steven Masley
2024-03-29 10:14:27 -05:00
committed by GitHub
parent 8e2d026d99
commit eeb3d63be6
16 changed files with 68 additions and 99 deletions
+18 -33
View File
@@ -44,27 +44,15 @@ func APIKey(r *http.Request) database.APIKey {
return key
}
// User roles are the 'subject' field of Authorize()
type userAuthKey struct{}
type Authorization struct {
Actor rbac.Subject
// ActorName is required for logging and human friendly related identification.
// It is usually the "username" of the user, but it can be the name of the
// external workspace proxy or other service type actor.
ActorName string
}
// UserAuthorizationOptional may return the roles and scope used for
// authorization. Depends on the ExtractAPIKey handler.
func UserAuthorizationOptional(r *http.Request) (Authorization, bool) {
auth, ok := r.Context().Value(userAuthKey{}).(Authorization)
return auth, ok
func UserAuthorizationOptional(r *http.Request) (rbac.Subject, bool) {
return dbauthz.ActorFromContext(r.Context())
}
// UserAuthorization returns the roles and scope used for authorization. Depends
// on the ExtractAPIKey handler.
func UserAuthorization(r *http.Request) Authorization {
func UserAuthorization(r *http.Request) rbac.Subject {
auth, ok := UserAuthorizationOptional(r)
if !ok {
panic("developer error: ExtractAPIKey middleware not provided")
@@ -119,7 +107,7 @@ type ExtractAPIKeyConfig struct {
//
// This is originally implemented to send entitlement warning headers after
// a user is authenticated to prevent additional CLI invocations.
PostAuthAdditionalHeadersFunc func(a Authorization, header http.Header)
PostAuthAdditionalHeadersFunc func(a rbac.Subject, header http.Header)
}
// ExtractAPIKeyMW calls ExtractAPIKey with the given config on each request,
@@ -142,9 +130,8 @@ func ExtractAPIKeyMW(cfg ExtractAPIKeyConfig) func(http.Handler) http.Handler {
// Actor is the user's authorization context.
ctx := r.Context()
ctx = context.WithValue(ctx, apiKeyContextKey{}, key)
ctx = context.WithValue(ctx, userAuthKey{}, authz)
// Set the auth context for the authzquerier as well.
ctx = dbauthz.As(ctx, authz.Actor)
// Set the auth context for the user.
ctx = dbauthz.As(ctx, authz)
next.ServeHTTP(rw, r.WithContext(ctx))
})
@@ -209,12 +196,12 @@ func APIKeyFromRequest(ctx context.Context, db database.Store, sessionTokenFunc
// and authz object may be returned. False is returned if a response was written
// to the request and the caller should give up.
// nolint:revive
func ExtractAPIKey(rw http.ResponseWriter, r *http.Request, cfg ExtractAPIKeyConfig) (*database.APIKey, *Authorization, bool) {
func ExtractAPIKey(rw http.ResponseWriter, r *http.Request, cfg ExtractAPIKeyConfig) (*database.APIKey, *rbac.Subject, bool) {
ctx := r.Context()
// Write wraps writing a response to redirect if the handler
// specified it should. This redirect is used for user-facing pages
// like workspace applications.
write := func(code int, response codersdk.Response) (*database.APIKey, *Authorization, bool) {
write := func(code int, response codersdk.Response) (*database.APIKey, *rbac.Subject, bool) {
if cfg.RedirectToLogin {
RedirectToLogin(rw, r, nil, response.Message)
return nil, nil, false
@@ -229,7 +216,7 @@ func ExtractAPIKey(rw http.ResponseWriter, r *http.Request, cfg ExtractAPIKeyCon
//
// It should be used when the API key is not provided or is invalid,
// but not when there are other errors.
optionalWrite := func(code int, response codersdk.Response) (*database.APIKey, *Authorization, bool) {
optionalWrite := func(code int, response codersdk.Response) (*database.APIKey, *rbac.Subject, bool) {
if cfg.Optional {
return nil, nil, true
}
@@ -451,21 +438,19 @@ func ExtractAPIKey(rw http.ResponseWriter, r *http.Request, cfg ExtractAPIKeyCon
}
// Actor is the user's authorization context.
authz := Authorization{
ActorName: roles.Username,
Actor: rbac.Subject{
ID: key.UserID.String(),
Roles: rbac.RoleNames(roles.Roles),
Groups: roles.Groups,
Scope: rbac.ScopeName(key.Scope),
}.WithCachedASTValue(),
}
actor := rbac.Subject{
FriendlyName: roles.Username,
ID: key.UserID.String(),
Roles: rbac.RoleNames(roles.Roles),
Groups: roles.Groups,
Scope: rbac.ScopeName(key.Scope),
}.WithCachedASTValue()
if cfg.PostAuthAdditionalHeadersFunc != nil {
cfg.PostAuthAdditionalHeadersFunc(authz, rw.Header())
cfg.PostAuthAdditionalHeadersFunc(actor, rw.Header())
}
return key, &authz, true
return key, &actor, true
}
// APITokenFromRequest returns the api token from the request.
+2 -2
View File
@@ -127,8 +127,8 @@ func TestExtractUserRoles(t *testing.T) {
)
rtr.Get("/", func(_ http.ResponseWriter, r *http.Request) {
roles := httpmw.UserAuthorization(r)
require.Equal(t, user.ID.String(), roles.Actor.ID)
require.ElementsMatch(t, expRoles, roles.Actor.Roles.Names())
require.Equal(t, user.ID.String(), roles.ID)
require.ElementsMatch(t, expRoles, roles.Roles.Names())
})
req := httptest.NewRequest("GET", "/", nil)
-14
View File
@@ -5,8 +5,6 @@ import (
"crypto/subtle"
"net/http"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/httpapi"
@@ -68,18 +66,6 @@ func ExtractProvisionerDaemonAuthenticated(opts ExtractProvisionerAuthConfig, ps
ctx = context.WithValue(ctx, provisionerDaemonContextKey{}, true)
// nolint:gocritic // Authenticating as a provisioner daemon.
ctx = dbauthz.AsProvisionerd(ctx)
subj, ok := dbauthz.ActorFromContext(ctx)
if !ok {
// This should never happen
httpapi.InternalServerError(w, xerrors.New("developer error: ExtractProvisionerDaemonAuth missing rbac actor"))
}
// Use the same subject for the userAuthKey
ctx = context.WithValue(ctx, userAuthKey{}, Authorization{
Actor: subj,
ActorName: "provisioner_daemon",
})
next.ServeHTTP(w, r.WithContext(ctx))
})
}
+1 -1
View File
@@ -47,7 +47,7 @@ func RateLimit(count int, window time.Duration) func(http.Handler) http.Handler
// We avoid using rbac.Authorizer since rego is CPU-intensive
// and undermines the DoS-prevention goal of the rate limiter.
for _, role := range auth.Actor.SafeRoleNames() {
for _, role := range auth.SafeRoleNames() {
if role == rbac.RoleOwner() {
// HACK: use a random key each time to
// de facto disable rate limiting. The
-12
View File
@@ -141,18 +141,6 @@ func ExtractWorkspaceProxy(opts ExtractWorkspaceProxyConfig) func(http.Handler)
// they can still only access the routes that the middleware is
// mounted to.
ctx = dbauthz.AsSystemRestricted(ctx)
subj, ok := dbauthz.ActorFromContext(ctx)
if !ok {
// This should never happen
httpapi.InternalServerError(w, xerrors.New("developer error: ExtractWorkspaceProxy missing rbac actor"))
return
}
// Use the same subject for the userAuthKey
ctx = context.WithValue(ctx, userAuthKey{}, Authorization{
Actor: subj,
ActorName: "proxy_" + proxy.Name,
})
next.ServeHTTP(w, r.WithContext(ctx))
})
}