From 65b1657b30a1b1cf5ba7e926e2c060d3dabfe1cd Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 16 Aug 2023 13:39:54 +0000 Subject: [PATCH] auth: add AuthContext.Access Same logic as server.Access, but lower-level. Useful to check for a permission not covered by the GraphQL schema @access directives (such as builds.sr.ht secrets). --- auth/middleware.go | 28 ++++++++++++++++++++++++++++ server/directives.go | 27 +++------------------------ 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/auth/middleware.go b/auth/middleware.go index c1f6c493c69da1f4a526ec2ddaa43315a8f68eab..2771923843b0f35681b0e62bd4683f18abb95c80 100644 --- a/auth/middleware.go +++ b/auth/middleware.go @@ -84,6 +84,34 @@ type AuthContext struct { TokenHash [64]byte } +func (authctx *AuthContext) Access(scope, kind string) error { + switch authctx.AuthMethod { + case AUTH_INTERNAL, AUTH_COOKIE: + return nil + case AUTH_OAUTH_LEGACY: + if kind == RO { + // Only legacy tokens with "*" scopes ever get this far + return nil + } else { + return fmt.Errorf("Read/write access denied for legacy OAuth token") + } + case AUTH_WEBHOOK: + if kind != RO { + return fmt.Errorf("Access to read/write resolver denied for webhook") + } + fallthrough + case AUTH_OAUTH2: + if !authctx.Grants.Has(scope, kind) { + return fmt.Errorf("Access denied, missing %v:%v grant", scope, kind) + } + return nil + case AUTH_ANON_INTERNAL: + return fmt.Errorf("Access denied for internal anonymous auth") + default: + panic(fmt.Errorf("Unknown auth method %q for access check", authctx.AuthMethod)) + } +} + func authError(w http.ResponseWriter, reason string, code int) { gqlerr := gqlerror.Errorf("Authentication error: %s", reason) b, err := json.Marshal(struct { diff --git a/server/directives.go b/server/directives.go index 6740674e3074bfd64629d7cb8073cf1029b001dd..b0a968ee397ca212026c8a17d05ba89e13560996 100644 --- a/server/directives.go +++ b/server/directives.go @@ -48,31 +48,10 @@ func Private(ctx context.Context, obj interface{}, func Access(ctx context.Context, obj interface{}, next graphql.Resolver, scope string, kind string) (interface{}, error) { - authctx := auth.ForContext(ctx) - switch authctx.AuthMethod { - case auth.AUTH_INTERNAL, auth.AUTH_COOKIE: - return next(ctx) - case auth.AUTH_OAUTH_LEGACY: - if kind == auth.RO { - // Only legacy tokens with "*" scopes ever get this far - return next(ctx) - } - case auth.AUTH_WEBHOOK: - if kind != auth.RO { - return nil, fmt.Errorf("Access to read/write resolver denied for webhook") - } - fallthrough - case auth.AUTH_OAUTH2: - if !authctx.Grants.Has(scope, kind) { - return nil, fmt.Errorf("Access denied, missing %v:%v grant", scope, kind) - } - return next(ctx) - case auth.AUTH_ANON_INTERNAL: - return nil, fmt.Errorf("Access denied for internal anonymous auth") - default: - panic(fmt.Errorf("Unknown auth method for access check")) + if err := auth.ForContext(ctx).Access(scope, kind); err != nil { + return nil, err } - return nil, fmt.Errorf("Access denied for invalid auth method") + return next(ctx) }