authorize: add support for .pomerium and unauthenticated routes (#639)

* authorize: add support for .pomerium and unauthenticated routes
integration-tests: add test for forward auth dashboard urls

* proxy: fix ctx error test to return a 200 when authorize allows it
This commit is contained in:
Caleb Doxsey 2020-04-29 10:55:46 -06:00 committed by GitHub
parent e5c7c5b27e
commit b1d3bbaf56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 158 additions and 69 deletions

View file

@ -5,10 +5,15 @@ import data.shared_key
default allow = false default allow = false
# allow public
allow {
route := first_allowed_route(input.url)
route_policies[route].AllowPublicUnauthenticatedAccess == true
}
# allow by email # allow by email
allow { allow {
some route route := first_allowed_route(input.url)
allowed_route(input.url, route_policies[route])
token.payload.email = route_policies[route].allowed_users[_] token.payload.email = route_policies[route].allowed_users[_]
token.valid token.valid
count(deny)==0 count(deny)==0
@ -16,8 +21,7 @@ allow {
# allow group # allow group
allow { allow {
some route route := first_allowed_route(input.url)
allowed_route(input.url, route_policies[route])
some group some group
token.payload.groups[group] == route_policies[route].allowed_groups[_] token.payload.groups[group] == route_policies[route].allowed_groups[_]
token.valid token.valid
@ -26,8 +30,7 @@ allow {
# allow by impersonate email # allow by impersonate email
allow { allow {
some route route := first_allowed_route(input.url)
allowed_route(input.url, route_policies[route])
token.payload.impersonate_email = route_policies[route].allowed_users[_] token.payload.impersonate_email = route_policies[route].allowed_users[_]
token.valid token.valid
count(deny)==0 count(deny)==0
@ -35,8 +38,7 @@ allow {
# allow by impersonate group # allow by impersonate group
allow { allow {
some route route := first_allowed_route(input.url)
allowed_route(input.url, route_policies[route])
some group some group
token.payload.impersonate_groups[group] == route_policies[route].allowed_groups[_] token.payload.impersonate_groups[group] == route_policies[route].allowed_groups[_]
token.valid token.valid
@ -45,8 +47,7 @@ allow {
# allow by domain # allow by domain
allow { allow {
some route route := first_allowed_route(input.url)
allowed_route(input.url, route_policies[route])
some domain some domain
email_in_domain(token.payload.email, route_policies[route].allowed_domains[domain]) email_in_domain(token.payload.email, route_policies[route].allowed_domains[domain])
token.valid token.valid
@ -55,14 +56,24 @@ allow {
# allow by impersonate domain # allow by impersonate domain
allow { allow {
some route route := first_allowed_route(input.url)
allowed_route(input.url, route_policies[route])
some domain some domain
email_in_domain(token.payload.impersonate_email, route_policies[route].allowed_domains[domain]) email_in_domain(token.payload.impersonate_email, route_policies[route].allowed_domains[domain])
token.valid token.valid
count(deny)==0 count(deny)==0
} }
# allow pomerium urls
allow {
contains(input.url, "/.pomerium/")
not contains(input.url,"/.pomerium/admin")
}
# returns the first matching route
first_allowed_route(input_url) = route {
route := [route | some route ; allowed_route(input.url, route_policies[route])][0]
}
allowed_route(input_url, policy){ allowed_route(input_url, policy){
input_url_obj := parse_url(input_url) input_url_obj := parse_url(input_url)
allowed_route_source(input_url_obj, policy) allowed_route_source(input_url_obj, policy)

View file

@ -65,6 +65,51 @@ test_email_denied {
} }
} }
test_public_allowed {
allow with data.route_policies as [{
"source": "example.com",
"AllowPublicUnauthenticatedAccess": true
}] with input as {
"url": "http://example.com",
"host": "example.com"
}
}
test_public_denied {
not allow with data.route_policies as [
{
"source": "example.com",
"prefix": "/by-user",
"allowed_users": ["bob@example.com"]
},
{
"source": "example.com",
"AllowPublicUnauthenticatedAccess": true
}
] with input as {
"url": "http://example.com/by-user",
"host": "example.com"
}
}
test_pomerium_allowed {
allow with data.route_policies as [{
"source": "example.com",
"allowed_users": ["bob@example.com"]
}] with input as {
"url": "http://example.com/.pomerium/",
"host": "example.com"
}
}
test_pomerium_denied {
not allow with data.route_policies as [{
"source": "example.com",
"allowed_users": ["bob@example.com"]
}] with input as {
"url": "http://example.com/.pomerium/admin",
"host": "example.com"
}
}
test_parse_url { test_parse_url {
url := parse_url("http://example.com/some/path?qs") url := parse_url("http://example.com/some/path?qs")
url.scheme == "http" url.scheme == "http"

File diff suppressed because one or more lines are too long

View file

@ -8,6 +8,7 @@ import (
"github.com/pomerium/pomerium/authorize/evaluator" "github.com/pomerium/pomerium/authorize/evaluator"
"github.com/pomerium/pomerium/internal/grpc/authorize" "github.com/pomerium/pomerium/internal/grpc/authorize"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace" "github.com/pomerium/pomerium/internal/telemetry/trace"
) )
@ -25,7 +26,19 @@ func (a *Authorize) IsAuthorized(ctx context.Context, in *authorize.IsAuthorized
RemoteAddr: in.GetRequestRemoteAddr(), RemoteAddr: in.GetRequestRemoteAddr(),
URL: getFullURL(in.GetRequestUrl(), in.GetRequestHost()), URL: getFullURL(in.GetRequestUrl(), in.GetRequestHost()),
} }
return a.pe.IsAuthorized(ctx, req) reply, err := a.pe.IsAuthorized(ctx, req)
log.Info().
// request
Str("method", req.Method).
Str("url", req.URL).
// reply
Bool("allow", reply.Allow).
Strs("deny-reasons", reply.DenyReasons).
Str("user", reply.User).
Str("email", reply.Email).
Strs("groups", reply.Groups).
Msg("authorize.grpc.IsAuthorized")
return reply, err
} }
type protoHeader map[string]*authorize.IsAuthorizedRequest_Headers type protoHeader map[string]*authorize.IsAuthorizedRequest_Headers

6
go.sum
View file

@ -108,12 +108,11 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
@ -513,8 +512,6 @@ google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.14.0 h1:uMf5uLi4eQMRrMKhCplNik4U4H8Z6C1br3zOtAa/aDE= google.golang.org/api v0.14.0 h1:uMf5uLi4eQMRrMKhCplNik4U4H8Z6C1br3zOtAa/aDE=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0 h1:J1Pl9P2lnmYFSJvgs70DKELqHNh8CNWXPbud4njEE2s= google.golang.org/api v0.22.0 h1:J1Pl9P2lnmYFSJvgs70DKELqHNh8CNWXPbud4njEE2s=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@ -549,6 +546,7 @@ google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLY
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

View file

@ -16,8 +16,6 @@ func TestAuthorization(t *testing.T) {
defer clearTimeout() defer clearTimeout()
t.Run("public", func(t *testing.T) { t.Run("public", func(t *testing.T) {
t.Skip() // pomerium doesn't currently handle unauthenticated public routes
client := testcluster.NewHTTPClient() client := testcluster.NewHTTPClient()
req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io", nil) req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io", nil)
@ -33,7 +31,6 @@ func TestAuthorization(t *testing.T) {
assert.Equal(t, http.StatusOK, res.StatusCode, "unexpected status code, headers=%v", res.Header) assert.Equal(t, http.StatusOK, res.StatusCode, "unexpected status code, headers=%v", res.Header)
}) })
t.Run("domains", func(t *testing.T) { t.Run("domains", func(t *testing.T) {
t.Run("allowed", func(t *testing.T) { t.Run("allowed", func(t *testing.T) {
client := testcluster.NewHTTPClient() client := testcluster.NewHTTPClient()
@ -78,7 +75,7 @@ func TestAuthorization(t *testing.T) {
client := testcluster.NewHTTPClient() client := testcluster.NewHTTPClient()
res, err := flows.Authenticate(ctx, client, mustParseURL("https://httpdetails.localhost.pomerium.io/by-group"), "joe@cats.test", []string{"user"}) res, err := flows.Authenticate(ctx, client, mustParseURL("https://httpdetails.localhost.pomerium.io/by-group"), "joe@cats.test", []string{"user"})
if assert.NoError(t, err) { if assert.NoError(t, err) {
assertDeniedAccess(t, res, "expected Forbidden for user") assertDeniedAccess(t, res, "expected Forbidden for user, but got %d", res.StatusCode)
} }
}) })
}) })

View file

@ -28,6 +28,23 @@ func TestDashboard(t *testing.T) {
} }
defer res.Body.Close() defer res.Body.Close()
assert.Equal(t, http.StatusOK, res.StatusCode, "unexpected status code")
assert.Equal(t, "image/svg+xml", res.Header.Get("Content-Type"))
})
t.Run("forward auth image asset", func(t *testing.T) {
client := testcluster.NewHTTPClient()
req, err := http.NewRequestWithContext(ctx, "GET", "https://fa-httpdetails.localhost.pomerium.io/.pomerium/assets/img/pomerium.svg", nil)
if err != nil {
t.Fatal(err)
}
res, err := client.Do(req)
if !assert.NoError(t, err, "unexpected http error") {
return
}
defer res.Body.Close()
assert.Equal(t, http.StatusOK, res.StatusCode, "unexpected status code") assert.Equal(t, http.StatusOK, res.StatusCode, "unexpected status code")
assert.Equal(t, "image/svg+xml", res.Header.Get("Content-Type")) assert.Equal(t, "image/svg+xml", res.Header.Get("Content-Type"))
}) })

View file

@ -1,30 +1,33 @@
local tls = import './tls.libsonnet'; local tls = import './tls.libsonnet';
local PomeriumPolicy = function() [ local PomeriumPolicy = function() std.flattenArrays([
[
{ {
from: 'http://httpdetails.localhost.pomerium.io', from: 'http://' + domain + '.localhost.pomerium.io',
prefix: '/by-domain', prefix: '/by-domain',
to: 'http://httpdetails.default.svc.cluster.local', to: 'http://' + domain + '.default.svc.cluster.local',
allowed_domains: ['dogs.test'], allowed_domains: ['dogs.test'],
}, },
{ {
from: 'http://httpdetails.localhost.pomerium.io', from: 'http://' + domain + '.localhost.pomerium.io',
prefix: '/by-user', prefix: '/by-user',
to: 'http://httpdetails.default.svc.cluster.local', to: 'http://' + domain + '.default.svc.cluster.local',
allowed_users: ['bob@dogs.test'], allowed_users: ['bob@dogs.test'],
}, },
{ {
from: 'http://httpdetails.localhost.pomerium.io', from: 'http://' + domain + '.localhost.pomerium.io',
prefix: '/by-group', prefix: '/by-group',
to: 'http://httpdetails.default.svc.cluster.local', to: 'http://' + domain + '.default.svc.cluster.local',
allowed_groups: ['admin'], allowed_groups: ['admin'],
}, },
{ {
from: 'http://httpdetails.localhost.pomerium.io', from: 'http://' + domain + '.localhost.pomerium.io',
to: 'http://httpdetails.default.svc.cluster.local', to: 'http://' + domain + '.default.svc.cluster.local',
allow_public_unauthenticated_access: true, allow_public_unauthenticated_access: true,
}, },
]; ]
for domain in ['httpdetails', 'fa-httpdetails']
]);
local PomeriumPolicyHash = std.base64(std.md5(std.manifestJsonEx(PomeriumPolicy(), ''))); local PomeriumPolicyHash = std.base64(std.md5(std.manifestJsonEx(PomeriumPolicy(), '')));
@ -292,20 +295,27 @@ local PomeriumForwardAuthIngress = function() {
tls: [ tls: [
{ {
hosts: [ hosts: [
'fa-httpecho.localhost.pomerium.io', 'fa-httpdetails.localhost.pomerium.io',
], ],
secretName: 'pomerium-tls', secretName: 'pomerium-tls',
}, },
], ],
rules: [ rules: [
{ {
host: 'fa-httpecho.localhost.pomerium.io', host: 'fa-httpdetails.localhost.pomerium.io',
http: { http: {
paths: [ paths: [
{
path: '/.pomerium/',
backend: {
serviceName: 'proxy',
servicePort: 'https',
},
},
{ {
path: '/', path: '/',
backend: { backend: {
serviceName: 'httpecho', serviceName: 'httpdetails',
servicePort: 'http', servicePort: 'http',
}, },
}, },

View file

@ -117,6 +117,8 @@ func (p *Proxy) Verify(verifyOnly bool) http.Handler {
} }
originalRequest := p.getOriginalRequest(r, uri) originalRequest := p.getOriginalRequest(r, uri)
if err := p.authorize(w, originalRequest); err != nil {
// no session, so redirect
if _, err := sessions.FromContext(r.Context()); err != nil { if _, err := sessions.FromContext(r.Context()); err != nil {
if verifyOnly { if verifyOnly {
return httputil.NewError(http.StatusUnauthorized, err) return httputil.NewError(http.StatusUnauthorized, err)
@ -131,7 +133,6 @@ func (p *Proxy) Verify(verifyOnly bool) http.Handler {
return nil return nil
} }
if err := p.authorize(w, originalRequest); err != nil {
return err return err
} }

View file

@ -103,10 +103,7 @@ func (p *Proxy) AuthorizeSession(next http.Handler) http.Handler {
func (p *Proxy) authorize(w http.ResponseWriter, r *http.Request) error { func (p *Proxy) authorize(w http.ResponseWriter, r *http.Request) error {
ctx, span := trace.StartSpan(r.Context(), "proxy.authorize") ctx, span := trace.StartSpan(r.Context(), "proxy.authorize")
defer span.End() defer span.End()
jwt, err := sessions.FromContext(ctx) jwt, _ := sessions.FromContext(ctx)
if err != nil {
return httputil.NewError(http.StatusInternalServerError, err)
}
authz, err := p.AuthorizeClient.Authorize(ctx, jwt, r) authz, err := p.AuthorizeClient.Authorize(ctx, jwt, r)
if err != nil { if err != nil {
return httputil.NewError(http.StatusInternalServerError, err) return httputil.NewError(http.StatusInternalServerError, err)

View file

@ -159,7 +159,7 @@ func TestProxy_AuthorizeSession(t *testing.T) {
}{ }{
{"user is authorized", 200, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeResponse: &authorize.IsAuthorizedReply{Allow: true}}, nil, identity.MockProvider{}, http.StatusOK}, {"user is authorized", 200, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeResponse: &authorize.IsAuthorizedReply{Allow: true}}, nil, identity.MockProvider{}, http.StatusOK},
{"user is not authorized", 200, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeResponse: &authorize.IsAuthorizedReply{Allow: false}}, nil, identity.MockProvider{}, http.StatusForbidden}, {"user is not authorized", 200, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeResponse: &authorize.IsAuthorizedReply{Allow: false}}, nil, identity.MockProvider{}, http.StatusForbidden},
{"ctx error", 200, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeResponse: &authorize.IsAuthorizedReply{Allow: true}}, errors.New("hi"), identity.MockProvider{}, http.StatusInternalServerError}, {"ctx error", 200, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeResponse: &authorize.IsAuthorizedReply{Allow: true}}, errors.New("hi"), identity.MockProvider{}, http.StatusOK},
{"authz client error", 200, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeError: errors.New("err")}, nil, identity.MockProvider{}, http.StatusInternalServerError}, {"authz client error", 200, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeError: errors.New("err")}, nil, identity.MockProvider{}, http.StatusInternalServerError},
{"expired, reauth failed", 200, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeResponse: &authorize.IsAuthorizedReply{SessionExpired: true}}, nil, identity.MockProvider{}, http.StatusForbidden}, {"expired, reauth failed", 200, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Second))}}, client.MockAuthorize{AuthorizeResponse: &authorize.IsAuthorizedReply{SessionExpired: true}}, nil, identity.MockProvider{}, http.StatusForbidden},
//todo(bdd): it's a bit tricky to test the refresh flow //todo(bdd): it's a bit tricky to test the refresh flow