diff --git a/authorize/check_response.go b/authorize/check_response.go index d02cbfd4d..1d2bb3ab9 100644 --- a/authorize/check_response.go +++ b/authorize/check_response.go @@ -13,6 +13,7 @@ import ( envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" "github.com/golang/protobuf/ptypes/wrappers" + "github.com/tniswong/go.rfcx/rfc7231" "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/grpc/codes" @@ -104,7 +105,7 @@ func (a *Authorize) deniedResponse( }, nil } -func (a *Authorize) redirectResponse(ctx context.Context, in *envoy_service_auth_v3.CheckRequest) (*envoy_service_auth_v3.CheckResponse, error) { +func (a *Authorize) requireLoginResponse(ctx context.Context, in *envoy_service_auth_v3.CheckRequest) (*envoy_service_auth_v3.CheckResponse, error) { opts := a.currentOptions.Load() state := a.state.Load() authenticateURL, err := opts.GetAuthenticateURL() @@ -112,6 +113,10 @@ func (a *Authorize) redirectResponse(ctx context.Context, in *envoy_service_auth return nil, err } + if !shouldRedirect(in) { + return a.deniedResponse(ctx, in, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized), nil) + } + signinURL := authenticateURL.ResolveReference(&url.URL{ Path: "/.pomerium/sign_in", }) @@ -180,3 +185,22 @@ func (a *Authorize) userInfoEndpointURL(in *envoy_service_auth_v3.CheckRequest) return urlutil.NewSignedURL(a.state.Load().sharedKey, debugEndpoint).Sign(), nil } + +func shouldRedirect(in *envoy_service_auth_v3.CheckRequest) bool { + requestHeaders := in.GetAttributes().GetRequest().GetHttp().GetHeaders() + if requestHeaders == nil { + return true + } + + a, err := rfc7231.ParseAccept(requestHeaders["accept"]) + if err != nil { + return true + } + + mediaType, ok := a.MostAcceptable([]string{"text/html", "application/json", "text/plain"}) + if !ok { + return true + } + + return mediaType == "text/html" +} diff --git a/authorize/check_response_test.go b/authorize/check_response_test.go index 865d2ab67..9feee5bdd 100644 --- a/authorize/check_response_test.go +++ b/authorize/check_response_test.go @@ -179,3 +179,48 @@ func mustParseWeightedURLs(t *testing.T, urls ...string) []config.WeightedURL { require.NoError(t, err) return wu } + +func TestRequireLogin(t *testing.T) { + opt := config.NewDefaultOptions() + opt.AuthenticateURLString = "https://authenticate.example.com" + opt.DataBrokerURLString = "https://databroker.example.com" + opt.SharedKey = "E8wWIMnihUx+AUfRegAQDNs8eRb3UrB5G3zlJW9XJDM=" + a, err := New(&config.Config{Options: opt}) + require.NoError(t, err) + + t.Run("accept empty", func(t *testing.T) { + res, err := a.requireLoginResponse(context.Background(), &envoy_service_auth_v3.CheckRequest{}) + require.NoError(t, err) + assert.Equal(t, http.StatusFound, int(res.GetDeniedResponse().GetStatus().GetCode())) + }) + t.Run("accept html", func(t *testing.T) { + res, err := a.requireLoginResponse(context.Background(), &envoy_service_auth_v3.CheckRequest{ + Attributes: &envoy_service_auth_v3.AttributeContext{ + Request: &envoy_service_auth_v3.AttributeContext_Request{ + Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{ + Headers: map[string]string{ + "accept": "*/*", + }, + }, + }, + }, + }) + require.NoError(t, err) + assert.Equal(t, http.StatusFound, int(res.GetDeniedResponse().GetStatus().GetCode())) + }) + t.Run("accept json", func(t *testing.T) { + res, err := a.requireLoginResponse(context.Background(), &envoy_service_auth_v3.CheckRequest{ + Attributes: &envoy_service_auth_v3.AttributeContext{ + Request: &envoy_service_auth_v3.AttributeContext_Request{ + Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{ + Headers: map[string]string{ + "accept": "application/json", + }, + }, + }, + }, + }) + require.NoError(t, err) + assert.Equal(t, http.StatusUnauthorized, int(res.GetDeniedResponse().GetStatus().GetCode())) + }) +} diff --git a/authorize/grpc.go b/authorize/grpc.go index f113e6efe..3444012a5 100644 --- a/authorize/grpc.go +++ b/authorize/grpc.go @@ -75,7 +75,7 @@ func (a *Authorize) Check(ctx context.Context, in *envoy_service_auth_v3.CheckRe if isForwardAuth && hreq.URL.Path == "/verify" { return a.deniedResponse(ctx, in, http.StatusUnauthorized, "Unauthenticated", nil) } - return a.redirectResponse(ctx, in) + return a.requireLoginResponse(ctx, in) } return a.deniedResponse(ctx, in, int32(reply.Status), reply.Message, nil) } diff --git a/go.mod b/go.mod index 12382eaf2..c16048506 100644 --- a/go.mod +++ b/go.mod @@ -57,6 +57,7 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.7.0 + github.com/tniswong/go.rfcx v0.0.0-20181019234604-07783c52761f github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da go.opencensus.io v0.23.0 diff --git a/go.sum b/go.sum index 9752ea37e..dec7eedec 100644 --- a/go.sum +++ b/go.sum @@ -622,6 +622,8 @@ github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tniswong/go.rfcx v0.0.0-20181019234604-07783c52761f h1:C43EMGXFtvYf/zunHR6ivZV7Z6ytg73t0GXwYyicXMQ= +github.com/tniswong/go.rfcx v0.0.0-20181019234604-07783c52761f/go.mod h1:N+sR0vLSCTtI6o06PMWsjMB4TVqqDttKNq4iC9wvxVY= github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y= github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE= github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U=