Skip to content

Commit

Permalink
feat!: Templating support in redirect error handler mechanism (#395)
Browse files Browse the repository at this point in the history
  • Loading branch information
dadrus authored Dec 26, 2022
1 parent 4ca9a9d commit 7a0eff3
Show file tree
Hide file tree
Showing 32 changed files with 323 additions and 222 deletions.
32 changes: 18 additions & 14 deletions cmd/serve/decision.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,8 @@ func NewDecisionCommand() *cobra.Command {
Use: "decision",
Short: "Starts heimdall in Decision operation mode",
Example: "heimdall serve decision",
Run: func(cmd *cobra.Command, args []string) {
configPath, _ := cmd.Flags().GetString("config")
envPrefix, _ := cmd.Flags().GetString("env-config-prefix")

app := fx.New(
fx.NopLogger,
fx.Supply(
config.ConfigurationPath(configPath),
config.EnvVarPrefix(envPrefix)),
internal.Module,
decision.Module,
)

err := app.Err()
Run: func(cmd *cobra.Command, _ []string) {
app, err := createDecisionApp(cmd)
if err != nil {
cmd.PrintErrf("Failed to initialize decision service: %v", err)
panic(err)
Expand All @@ -38,3 +26,19 @@ func NewDecisionCommand() *cobra.Command {
},
}
}

func createDecisionApp(cmd *cobra.Command) (*fx.App, error) {
configPath, _ := cmd.Flags().GetString("config")
envPrefix, _ := cmd.Flags().GetString("env-config-prefix")

app := fx.New(
fx.NopLogger,
fx.Supply(
config.ConfigurationPath(configPath),
config.EnvVarPrefix(envPrefix)),
internal.Module,
decision.Module,
)

return app, app.Err()
}
27 changes: 27 additions & 0 deletions cmd/serve/decision_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package serve

import (
"strconv"
"testing"

"github.com/spf13/cobra"
"github.com/stretchr/testify/require"

"github.com/dadrus/heimdall/internal/x/testsupport"
)

func TestCreateDecisionApp(t *testing.T) {
// this test verifies that all dependencies are resolved
// and nothing has been forgotten
port1, err := testsupport.GetFreePort()
require.NoError(t, err)

port2, err := testsupport.GetFreePort()
require.NoError(t, err)

t.Setenv("SERVE_DECISION_PORT", strconv.Itoa(port1))
t.Setenv("SERVE_MANAGEMENT_PORT", strconv.Itoa(port2))

_, err = createDecisionApp(&cobra.Command{})
require.NoError(t, err)
}
32 changes: 18 additions & 14 deletions cmd/serve/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,8 @@ func NewProxyCommand() *cobra.Command {
Use: "proxy",
Short: "Starts heimdall in Reverse Proxy operation mode",
Example: "heimdall serve proxy",
Run: func(cmd *cobra.Command, args []string) {
configPath, _ := cmd.Flags().GetString("config")
envPrefix, _ := cmd.Flags().GetString("env-config-prefix")

app := fx.New(
fx.NopLogger,
fx.Supply(
config.ConfigurationPath(configPath),
config.EnvVarPrefix(envPrefix)),
internal.Module,
proxy.Module,
)

err := app.Err()
Run: func(cmd *cobra.Command, _ []string) {
app, err := createProxyApp(cmd)
if err != nil {
cmd.PrintErrf("Failed to initialize proxy service: %v", err)
panic(err)
Expand All @@ -38,3 +26,19 @@ func NewProxyCommand() *cobra.Command {
},
}
}

func createProxyApp(cmd *cobra.Command) (*fx.App, error) {
configPath, _ := cmd.Flags().GetString("config")
envPrefix, _ := cmd.Flags().GetString("env-config-prefix")

app := fx.New(
fx.NopLogger,
fx.Supply(
config.ConfigurationPath(configPath),
config.EnvVarPrefix(envPrefix)),
internal.Module,
proxy.Module,
)

return app, app.Err()
}
27 changes: 27 additions & 0 deletions cmd/serve/proxy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package serve

import (
"strconv"
"testing"

"github.com/spf13/cobra"
"github.com/stretchr/testify/require"

"github.com/dadrus/heimdall/internal/x/testsupport"
)

func TestCreateProxyApp(t *testing.T) {
// this test verifies that all dependencies are resolved
// and nothing has been forgotten
port1, err := testsupport.GetFreePort()
require.NoError(t, err)

port2, err := testsupport.GetFreePort()
require.NoError(t, err)

t.Setenv("SERVE_PROXY_PORT", strconv.Itoa(port1))
t.Setenv("SERVE_MANAGEMENT_PORT", strconv.Itoa(port2))

_, err = createProxyApp(&cobra.Command{})
require.NoError(t, err)
}
15 changes: 7 additions & 8 deletions docs/content/docs/configuration/reference/reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ serve:
allow_credentials: true
max_age: 1m
tls:
key: /path/to/key/file.pem
cert: /path/to/cert/file.pem
key_store: /path/to/key/store.pem
password: VerySecure!
trusted_proxies:
- 192.168.1.0/24
Expand All @@ -61,8 +61,9 @@ serve:
allow_credentials: true
max_age: 1m
tls:
key: /path/to/key/file.pem
cert: /path/to/cert/file.pem
key_store: /path/to/key/store.pem
password: VerySecure!
key_id: first_entry
min_version: TLS1.2
cipher_suites:
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
Expand Down Expand Up @@ -93,8 +94,7 @@ serve:
allow_credentials: true
max_age: 1m
tls:
key: /path/to/key/file.pem
cert: /path/to/cert/file.pem
key_store: /path/to/key/store.pem
min_version: TLS1.2
log:
Expand Down Expand Up @@ -269,8 +269,7 @@ rules:
- id: authenticate_with_kratos
type: redirect
config:
to: http://127.0.0.1:4433/self-service/login/browser
return_to_query_parameter: return_to
to: http://127.0.0.1:4433/self-service/login/browser?return_to={{ .Request.URL | urlenc }}
when:
- error:
- type: authentication_error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,9 @@ Configuration using the `config` property is mandatory. Following properties are

* *`to`*: _URL_ (mandatory, not overridable)
+
The url to redirect the client to. If no `return_to_query_parameter` is defined, the value of the HTTP `Location` hader is set to the configured value.

* *`return_to_query_parameter`*: _string_ (optional, not overridable)
The url to redirect the client to via the `Location` header. Can be templated and has access to the link:{{< relref "overview.adoc#_request" >}}[`Request`] object.
+
If you want to return the user back to the url, heimdall was handling when this error handler mechanism kicked in and your authentication system supports this by considering a specific query parameter, you can configure it here.
NOTE: When creating query parameters by making use of templates, don't forget to encode the values using the `urlenc` function. See also examples below.

* *`code`*: _int_ (optional, not overridable)
+
Expand All @@ -63,7 +61,7 @@ Conditions, which must hold true for this error handler to execute. The defined
The redirect error handler below is configured to kick in if either
* the first error condition evaluates to true. This condition is for web requests (HTTP `Accept` header contains `text/html`) if an `authentication_error` error occurred (an error raised by authenticators). In this case, it will redirect the client (for web requests, usually a browser) to `\http://127.0.0.1:4433/self-service/login/browser` and also add the `return_to` query parameter with the current url to the redirect url.
* the first error condition evaluates to true. This condition is for web requests (HTTP `Accept` header contains `text/html`) if an `authentication_error` error occurred (an error raised by authenticators). In this case, it will redirect the client (for web requests, usually a browser) to `\http://127.0.0.1:4433/self-service/login/browser` and also add the `return_to` query parameter set to the URL encoded value of the current url.
* Or, if the second condition evaluates to true. The only difference to the previous one is the error type, which is `authorization_error` in this case.
So, e.g. if Heimdall was handling the request for `\http://my-service.local/foo` and run into an error like described above, the value of the HTTP `Location` header will be set to `\http://127.0.0.1:4433/self-service/login/browser?return_to=http%3A%2F%2Fmy-service.local%2Ffoo`
Expand All @@ -73,8 +71,7 @@ So, e.g. if Heimdall was handling the request for `\http://my-service.local/foo`
id: authenticate_with_kratos
type: redirect
config:
to: http://127.0.0.1:4433/self-service/login/browser
return_to_query_parameter: return_to
to: http://127.0.0.1:4433/self-service/login/browser?return_to={{ .Request.URL | urlenc }}
when:
- error:
- type: authentication_error
Expand All @@ -95,8 +92,7 @@ In this case the error condition can actually be simplified, so the mechanism co
id: authenticate_with_kratos
type: redirect
config:
to: http://127.0.0.1:4433/self-service/login/browser
return_to_query_parameter: return_to
to: http://127.0.0.1:4433/self-service/login/browser?return_to={{ .Request.URL | urlenc }}
when:
- error:
- type: authentication_error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,7 @@ rules:
- id: authenticate_with_kratos
type: redirect
config:
to: http://127.0.0.1:4433/self-service/login/browser
return_to_query_parameter: return_to
to: http://127.0.0.1:4433/self-service/login/browser?return_to={{ .Request.URL | urlenc }}
when:
- error:
- type: authentication_error
Expand Down
3 changes: 1 addition & 2 deletions example_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,7 @@ rules:
- id: authenticate_with_kratos
type: redirect
config:
to: http://127.0.0.1:4433/self-service/login/browser
return_to_query_parameter: origin
to: http://127.0.0.1:4433/self-service/login/browser?origin={{ .Request.URL | urlenc }}
when:
- error:
- type: authentication_error
Expand Down
3 changes: 1 addition & 2 deletions internal/config/test_data/test_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,7 @@ rules:
- id: authenticate_with_kratos
type: redirect
config:
to: http://127.0.0.1:4433/self-service/login/browser
return_to_query_parameter: return_to
to: http://127.0.0.1:4433/self-service/login/browser?return_to={{ .Request.URL | urlenc }}
when:
- error:
- type: authentication_error
Expand Down
2 changes: 1 addition & 1 deletion internal/fiber/middleware/errorhandler/error_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (h *handler) handle(ctx *fiber.Ctx) error { //nolint:cyclop

errors.As(err, &redirectError)

return ctx.Redirect(redirectError.RedirectTo.String(), redirectError.Code)
return ctx.Redirect(redirectError.RedirectTo, redirectError.Code)
default:
logger := zerolog.Ctx(ctx.UserContext())
logger.Error().Err(err).Msg("Internal error occurred")
Expand Down
5 changes: 2 additions & 3 deletions internal/fiber/middleware/errorhandler/error_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"io"
"net/http"
"net/url"
"testing"

"github.com/gofiber/fiber/v2"
Expand Down Expand Up @@ -165,13 +164,13 @@ func TestHandlerHandle(t *testing.T) {
{
uc: "redirect error",
handler: New(),
err: &heimdall.RedirectError{RedirectTo: &url.URL{}, Code: http.StatusFound},
err: &heimdall.RedirectError{RedirectTo: "http://foo.local", Code: http.StatusFound},
expCode: http.StatusFound,
},
{
uc: "redirect error verbose",
handler: New(WithVerboseErrors(true)),
err: &heimdall.RedirectError{RedirectTo: &url.URL{}, Code: http.StatusFound},
err: &heimdall.RedirectError{RedirectTo: "http://foo.local", Code: http.StatusFound},
expCode: http.StatusFound,
},
{
Expand Down
9 changes: 2 additions & 7 deletions internal/handler/decision/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/dadrus/heimdall/internal/handler/requestcontext"
"github.com/dadrus/heimdall/internal/heimdall"
"github.com/dadrus/heimdall/internal/rules"
"github.com/dadrus/heimdall/internal/signer"
"github.com/dadrus/heimdall/internal/x"
"github.com/dadrus/heimdall/internal/x/errorchain"
)
Expand All @@ -27,20 +26,16 @@ type handlerArgs struct {
App *fiber.App `name:"decision"`
RulesRepository rules.Repository
Config *config.Configuration
Signer heimdall.JWTSigner
Logger zerolog.Logger
}

func newHandler(args handlerArgs) (*Handler, error) {
jwtSigner, err := signer.NewJWTSigner(&args.Config.Signer, args.Logger)
if err != nil {
return nil, err
}

acceptedCode := args.Config.Serve.Decision.Respond.With.Accepted.Code

handler := &Handler{
r: args.RulesRepository,
s: jwtSigner,
s: args.Signer,
code: x.IfThenElse(acceptedCode != 0, acceptedCode, fiber.StatusOK),
}

Expand Down
14 changes: 7 additions & 7 deletions internal/handler/management/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,30 @@ import (
"github.com/rs/zerolog"
"go.uber.org/fx"

"github.com/dadrus/heimdall/internal/keystore"
"github.com/dadrus/heimdall/internal/heimdall"
)

type Handler struct{}

type handlerArgs struct {
fx.In

App *fiber.App `name:"management"`
KeyStore keystore.KeyStore
Logger zerolog.Logger
App *fiber.App `name:"management"`
Signer heimdall.JWTSigner
Logger zerolog.Logger
}

func newHandler(args handlerArgs) (*Handler, error) {
handler := &Handler{}

handler.registerRoutes(args.App.Group("/"), args.Logger, args.KeyStore)
handler.registerRoutes(args.App.Group("/"), args.Logger, args.Signer)

return handler, nil
}

func (h *Handler) registerRoutes(router fiber.Router, logger zerolog.Logger, ks keystore.KeyStore) {
func (h *Handler) registerRoutes(router fiber.Router, logger zerolog.Logger, signer heimdall.JWTSigner) {
logger.Debug().Msg("Registering Management service routes")

router.Get(EndpointHealth, health)
router.Get(EndpointJWKS, etag.New(), jwks(ks))
router.Get(EndpointJWKS, etag.New(), jwks(signer))
}
Loading

0 comments on commit 7a0eff3

Please sign in to comment.