From c0ce59a1ffbb2d2350550cc9dfec2d4fd54eafe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20D=C3=B6ll?= Date: Wed, 12 Jun 2024 19:48:21 +0000 Subject: [PATCH] refactor: only use resolvers --- .devcontainer/Dockerfile | 7 ++ authenticator.go | 6 +- authz.go | 112 +++++++++++++--------------- examples/main.go | 12 ++- tbac.go => tbrac/tbrac.go | 13 ++-- tbac_test.go => tbrac/tbrac_test.go | 2 +- 6 files changed, 76 insertions(+), 76 deletions(-) rename tbac.go => tbrac/tbrac.go (96%) rename tbac_test.go => tbrac/tbrac_test.go (98%) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ac73d90..7e2ef47 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -8,6 +8,13 @@ FROM mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT} ADD zscaler.pem /usr/local/share/ca-certificates/zscaler.crt RUN sudo chmod 644 /usr/local/share/ca-certificates/zscaler.crt && sudo update-ca-certificates +# zScaler certificate, for all busy engineers +ADD zscaler.pem /usr/local/share/ca-certificates/zscaler.crt +RUN sudo chmod 644 /usr/local/share/ca-certificates/zscaler.crt && sudo update-ca-certificates + +# Set the environment variable NODE_EXTRA_CA_CERTS to the zScaler certificate +ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/zscaler.crt + # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # && apt-get -y install protobuf-compiler \ No newline at end of file diff --git a/authenticator.go b/authenticator.go index 4a7b8fa..e747f2a 100644 --- a/authenticator.go +++ b/authenticator.go @@ -29,15 +29,15 @@ type JWSValidator interface { // NewAuthenticator ... func NewAuthenticator(c AuthzChecker, v JWSValidator) openapi3filter.AuthenticationFunc { return func(ctx context.Context, input *openapi3filter.AuthenticationInput) error { - return Authenticate(ctx, c, v, input) + return Authenticated(ctx, c, v, input) } } // ErrForbidden ... var ErrForbidden = errors.New("forbidden") -// Authenticate ... -func Authenticate(ctx context.Context, checker AuthzChecker, validate JWSValidator, input *openapi3filter.AuthenticationInput) error { +// Authenticated ... +func Authenticated(ctx context.Context, checker AuthzChecker, validate JWSValidator, input *openapi3filter.AuthenticationInput) error { if input.SecuritySchemeName != "BearerAuth" { return fmt.Errorf("security scheme %s != 'BearerAuth'", input.SecuritySchemeName) } diff --git a/authz.go b/authz.go index 7c65085..26c83bd 100644 --- a/authz.go +++ b/authz.go @@ -35,18 +35,21 @@ func (a AuthzAction) String() string { } const ( - AuthzNoPrincipial = "" - AuthzNoObject = "" - AuthzNoAction = "" + AuthzNoPrincipial AuthzPrincipal = "" + AuthzNoObject AuthzObject = "" + AuthzNoAction AuthzAction = "" ) -// AuthzActionDefaults are the default actions. -const ( - Read AuthzAction = "read" - Write AuthzAction = "write" - Admin AuthzAction = "admin" - SuperAdmin AuthzAction = "superadmin" -) +// AuthzParams is the struct that holds the principal, object and action from the context. +// There needs to be a :principal, :object and :action in the context. +type AuthzParams struct { + // Principal is the subject. + Principal AuthzPrincipal `json:"principal" params:"principal" query:"principal" form:"principal"` + // Object is the object. + Object AuthzObject `json:"object" params:"object" query:"object" form:"object"` + // Action is the action. + Action AuthzAction `json:"action" params:"action" query:"action" form:"action"` +} // AuthzChecker is the interface that wraps the Allowed method. type AuthzChecker interface { @@ -99,6 +102,15 @@ type Config struct { // Checker is implementing the AuthzChecker interface. Checker AuthzChecker + // ObjectResolver is the object resolver. + ObjectResolver AuthzObjectResolver + + // ActionResolver is the action resolver. + ActionResolver AuthzActionResolver + + // PrincipalResolver is the principal resolver. + PrincipalResolver AuthzPrincipalResolver + // ErrorHandler is executed when an error is returned from fiber.Handler. // // Optional. Default: DefaultErrorHandler @@ -107,8 +119,11 @@ type Config struct { // ConfigDefault is the default config. var ConfigDefault = Config{ - ErrorHandler: defaultErrorHandler, - Checker: NewNoop(), + ErrorHandler: defaultErrorHandler, + ObjectResolver: NewNoopObjectResolver(), + PrincipalResolver: NewNoopPrincipalResolver(), + ActionResolver: NewNoopActionResolver(), + Checker: NewNoop(), } // default ErrorHandler that process return error from fiber.Handler @@ -134,48 +149,33 @@ type AuthzActionResolver interface { Resolve(c *fiber.Ctx) (AuthzAction, error) } -// SetAuthzHandler is a middleware that sets the principal and user in the context. -// This function can map any thing. -func SetAuthzHandler(object AuthzObjectResolver, action AuthzActionResolver, principal AuthzPrincipalResolver) func(c *fiber.Ctx) error { +// Authenticate is a middleware that sets the principal and user in the context. +func Authenticate(handler fiber.Handler, config ...Config) func(c *fiber.Ctx) error { + cfg := configDefault(config...) + return func(c *fiber.Ctx) error { - object, err := object.Resolve(c) - if err != nil { - return err + if cfg.Next != nil && cfg.Next(c) { + return c.Next() } - principal, err := principal.Resolve(c) + object, err := cfg.ObjectResolver.Resolve(c) if err != nil { return err } - action, err := action.Resolve(c) + principal, err := cfg.PrincipalResolver.Resolve(c) if err != nil { return err } - return ContextWithAuthz(c, principal, object, action).Next() - } -} - -// NewTBACHandler there is a new fiber.Handler that checks if the principal can perform the action on the object. -func NewTBACHandler(handler fiber.Handler, action AuthzAction, param string, config ...Config) fiber.Handler { - cfg := configDefault(config...) - - return func(c *fiber.Ctx) error { - if cfg.Next != nil && cfg.Next(c) { - return c.Next() - } - - team := AuthzObject(c.Params(param, "")) - - principal, _, _, err := AuthzFromContext(c) + action, err := cfg.ActionResolver.Resolve(c) if err != nil { - return defaultErrorHandler(c, err) + return err } - allowed, err := cfg.Checker.Allowed(c.Context(), principal, team, action) + allowed, err := cfg.Checker.Allowed(c.Context(), principal, object, action) if err != nil { - return defaultErrorHandler(c, err) + return cfg.ErrorHandler(c, err) } if !allowed { @@ -202,12 +202,12 @@ func NewCheckerHandler(config ...Config) fiber.Handler { }{} if err := c.BodyParser(&payload); err != nil { - return defaultErrorHandler(c, err) + return cfg.ErrorHandler(c, err) } allowed, err := cfg.Checker.Allowed(c.Context(), payload.Principal, payload.Object, payload.Permission) if err != nil { - return defaultErrorHandler(c, err) + return cfg.ErrorHandler(c, err) } if allowed { @@ -218,24 +218,6 @@ func NewCheckerHandler(config ...Config) fiber.Handler { } } -// ContextWithAuthz returns a new context with the principal, object and action set. -func ContextWithAuthz(ctx *fiber.Ctx, principal AuthzPrincipal, object AuthzObject, action AuthzAction) *fiber.Ctx { - ctx.Locals(authzPrincipial, principal) - ctx.Locals(authzObject, object) - ctx.Locals(authzAction, action) - - return ctx -} - -// AuthzFromContext return the principal, object and action from the context. -func AuthzFromContext(ctx *fiber.Ctx) (AuthzPrincipal, AuthzObject, AuthzAction, error) { - principal := ctx.Locals(authzPrincipial) - object := ctx.Locals(authzObject) - action := ctx.Locals(authzAction) - - return principal.(AuthzPrincipal), object.(AuthzObject), action.(AuthzAction), nil -} - // Helper function to set default values func configDefault(config ...Config) Config { if len(config) < 1 { @@ -253,6 +235,18 @@ func configDefault(config ...Config) Config { cfg.ErrorHandler = ConfigDefault.ErrorHandler } + if cfg.ObjectResolver == nil { + cfg.ObjectResolver = ConfigDefault.ObjectResolver + } + + if cfg.PrincipalResolver == nil { + cfg.PrincipalResolver = ConfigDefault.PrincipalResolver + } + + if cfg.ActionResolver == nil { + cfg.ActionResolver = ConfigDefault.ActionResolver + } + return cfg } diff --git a/examples/main.go b/examples/main.go index 2807a83..8c23c57 100644 --- a/examples/main.go +++ b/examples/main.go @@ -8,6 +8,7 @@ import ( "sort" authz "github.com/zeiss/fiber-authz" + "github.com/zeiss/fiber-authz/tbrac" "github.com/gofiber/fiber/v2" ll "github.com/gofiber/fiber/v2/middleware/logger" @@ -150,7 +151,7 @@ func run(_ context.Context) error { providers.RegisterProvider(github.New(os.Getenv("GITHUB_KEY"), os.Getenv("GITHUB_SECRET"), "http://localhost:3000/auth/github/callback")) - err = authz.RunMigrations(conn) + err = tbrac.RunMigrations(conn) if err != nil { return err } @@ -174,11 +175,6 @@ func run(_ context.Context) error { } app.Use(goth.NewProtectMiddleware(gothConfig)) - app.Use(authz.SetAuthzHandler(authz.NewNoopObjectResolver(), authz.NewNoopActionResolver(), authz.NewGothAuthzPrincipalResolver())) - - config := authz.Config{ - Checker: authz.NewTBAC(conn), - } indexHandler := func(c *fiber.Ctx) error { session, err := goth.SessionFromContext(c) @@ -194,7 +190,9 @@ func run(_ context.Context) error { return t.Execute(c.Response().BodyWriter(), providerIndex) }) - app.Get("/:team", authz.NewTBACHandler(indexHandler, authz.AuthzAction("admin"), "team", config)) + team := app.Get("/:team", authz.Authenticate(indexHandler)) + team.Use() + app.Get("/login/:provider", goth.NewBeginAuthHandler(gothConfig)) app.Get("/auth/:provider/callback", goth.NewCompleteAuthHandler(gothConfig)) app.Get("/logout", goth.NewLogoutHandler(gothConfig)) diff --git a/tbac.go b/tbrac/tbrac.go similarity index 96% rename from tbac.go rename to tbrac/tbrac.go index a8493fd..4d5f282 100644 --- a/tbac.go +++ b/tbrac/tbrac.go @@ -1,4 +1,4 @@ -package authz +package tbrac import ( "context" @@ -7,6 +7,7 @@ import ( "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" "github.com/google/uuid" + authz "github.com/zeiss/fiber-authz" "github.com/zeiss/fiber-goth/adapters" "gorm.io/gorm" ) @@ -203,8 +204,8 @@ type APIKeyRole struct { } var ( - _ AuthzChecker = (*tbac)(nil) - _ adapters.Adapter = (*tbac)(nil) + _ authz.AuthzChecker = (*tbac)(nil) + _ adapters.Adapter = (*tbac)(nil) ) type tbac struct { @@ -221,7 +222,7 @@ func NewTBAC(db *gorm.DB) *tbac { } // Allowed is a method that returns true if the principal is allowed to perform the action on the user. -func (t *tbac) Allowed(ctx context.Context, principal AuthzPrincipal, object AuthzObject, action AuthzAction) (bool, error) { +func (t *tbac) Allowed(ctx context.Context, principal authz.AuthzPrincipal, object authz.AuthzObject, action authz.AuthzAction) (bool, error) { var allowed int64 team := t.db.WithContext(ctx).Model(&Team{}).Select("id").Where("slug = ?", object) @@ -239,8 +240,8 @@ func (t *tbac) Allowed(ctx context.Context, principal AuthzPrincipal, object Aut } // Resolve ... -func (t *tbac) Resolve(c *fiber.Ctx) (AuthzObject, error) { - return AuthzObject(c.Params("team")), nil +func (t *tbac) Resolve(c *fiber.Ctx) (authz.AuthzObject, error) { + return authz.AuthzObject(c.Params("team")), nil } // CreateUser ... diff --git a/tbac_test.go b/tbrac/tbrac_test.go similarity index 98% rename from tbac_test.go rename to tbrac/tbrac_test.go index f50be34..805db6a 100644 --- a/tbac_test.go +++ b/tbrac/tbrac_test.go @@ -1,4 +1,4 @@ -package authz +package tbrac import ( "testing"