From eeaabaebe8385d553d010889efd039e543ddea3a Mon Sep 17 00:00:00 2001 From: till Date: Tue, 8 Feb 2022 22:05:51 +0100 Subject: [PATCH 01/10] Fix: response lost I am not sure where to start, we run against ory platform and noticed that the response "from" oathkeeper did not contain a subject when it got to the backend service. I managed to find out that the "body" in `forwardRequestToSessionStore()` was nil/empty in some cases and the gjson calls failed silently. I started log.Printf() debugging in these two service files and simplified the code a bit to make it more readable. And that seemed to have fixed it. --- pipeline/authn/authenticator_bearer_token.go | 4 ++++ pipeline/authn/authenticator_cookie_session.go | 13 +++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pipeline/authn/authenticator_bearer_token.go b/pipeline/authn/authenticator_bearer_token.go index 20b97f53b2..96e3d73f0f 100644 --- a/pipeline/authn/authenticator_bearer_token.go +++ b/pipeline/authn/authenticator_bearer_token.go @@ -107,6 +107,10 @@ func (a *AuthenticatorBearerToken) Authenticate(r *http.Request, session *Authen return helper.ErrForbidden.WithReasonf("The configured extra_from GJSON path returned an error on JSON output: %s", err.Error()).WithDebugf("GJSON path: %s\nBody: %s\nResult: %s", cf.ExtraFrom, body, extraRaw).WithTrace(err) } + if len(subject) == 0 { + return helper.ErrForbidden.WithReasonf("Could not decode subject from response.") + } + session.Subject = subject session.Extra = extra return nil diff --git a/pipeline/authn/authenticator_cookie_session.go b/pipeline/authn/authenticator_cookie_session.go index 8b974e35ab..1c4062a744 100644 --- a/pipeline/authn/authenticator_cookie_session.go +++ b/pipeline/authn/authenticator_cookie_session.go @@ -171,13 +171,14 @@ func forwardRequestToSessionStore(r *http.Request, checkSessionURL string, prese return nil, helper.ErrForbidden.WithReason(err.Error()).WithTrace(err) } + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return json.RawMessage{}, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to read session context from remote: %+v", err)) + } + if res.StatusCode == 200 { - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return json.RawMessage{}, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to fetch cookie session context from remote: %+v", err)) - } return body, nil - } else { - return json.RawMessage{}, errors.WithStack(helper.ErrUnauthorized) } + + return json.RawMessage{}, errors.WithStack(helper.ErrUnauthorized.WithReasonf("API said: %d -- %+v", res.StatusCode, body)) } From 3994e9a7251dbbc18f5508c0feba26fde89d21c2 Mon Sep 17 00:00:00 2001 From: till Date: Wed, 9 Feb 2022 10:02:59 +0100 Subject: [PATCH 02/10] Chore: include tag/commit (instead of master) --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8cac564f79..fd38a8f649 100644 --- a/Makefile +++ b/Makefile @@ -75,7 +75,8 @@ install: .bin/packr2 .PHONY: docker docker: .bin/packr2 packr2 || (GO111MODULE=on go install github.com/gobuffalo/packr/v2/packr2 && packr2) - CGO_ENABLED=0 GO111MODULE=on GOOS=linux GOARCH=amd64 go build + CGO_ENABLED=0 GO111MODULE=on GOOS=linux GOARCH=amd64 go build \ + -ldflags "-X github.com/ory/oathkeeper/x.Version=$$(git describe)" packr2 clean docker build -t oryd/oathkeeper:dev . docker build -t oryd/oathkeeper:dev-alpine -f Dockerfile-alpine . From 16baaf1553eead89732721b2db3846a8643da11f Mon Sep 17 00:00:00 2001 From: till Date: Wed, 9 Feb 2022 12:30:00 +0100 Subject: [PATCH 03/10] Fix: gzip handling Related: ory/oathkeeper#836 --- pipeline/authn/authenticator_cookie_session.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pipeline/authn/authenticator_cookie_session.go b/pipeline/authn/authenticator_cookie_session.go index 1c4062a744..9d2d66ef0b 100644 --- a/pipeline/authn/authenticator_cookie_session.go +++ b/pipeline/authn/authenticator_cookie_session.go @@ -155,6 +155,10 @@ func forwardRequestToSessionStore(r *http.Request, checkSessionURL string, prese // We need to make a COPY of the header, not modify r.Header! for k, v := range r.Header { + // remove Accept-Encoding to let the transport handle gzip + if k == "Accept-Encoding" { + continue + } req.Header[k] = v } From 2e1fd656ac1c042c54275e0dbab93cd4cf8f3849 Mon Sep 17 00:00:00 2001 From: till Date: Wed, 9 Feb 2022 12:30:00 +0100 Subject: [PATCH 04/10] Fix: gzip handling Related: ory/oathkeeper#836 --- .../authn/authenticator_bearer_token_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pipeline/authn/authenticator_bearer_token_test.go b/pipeline/authn/authenticator_bearer_token_test.go index 75b53c0c45..3e73506389 100644 --- a/pipeline/authn/authenticator_bearer_token_test.go +++ b/pipeline/authn/authenticator_bearer_token_test.go @@ -199,6 +199,24 @@ func TestAuthenticatorBearerToken(t *testing.T) { Subject: "123", }, }, + { + d: "should NOT pass Accept-Encoding", + r: &http.Request{Host: "some-host", Header: http.Header{"Authorization": {"bearer zyx"}, "Accept-Encoding": {"gzip"}}, URL: &url.URL{Path: "/users/123", RawQuery: "query=string"}, Method: "PUT"}, + router: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "PUT") + assert.Equal(t, "some-host", r.Header.Get("X-Forwarded-Host")) + assert.Equal(t, "bar", r.Header.Get("X-Foo")) + assert.Equal(t, r.Header.Get("Authorization"), "bearer zyx") + assert.Equal(t, "", r.Header.Get("Accept-Encoding")) + w.WriteHeader(200) + w.Write([]byte(`{"sub": "123"}`)) + }, + config: []byte(`{"preserve_host": true, "additional_headers": {"X-Foo": "bar","X-Forwarded-For": "not-some-host"}}`), + expectErr: false, + expectSess: &AuthenticationSession{ + Subject: "123", + }, + }, { d: "does not pass request body through to auth server", r: &http.Request{ From eeeb41f1142e5355dd5eb378fb5c81ecafd0b4be Mon Sep 17 00:00:00 2001 From: till Date: Thu, 10 Feb 2022 10:40:19 +0100 Subject: [PATCH 05/10] Chore: more generic error responses --- pipeline/authn/authenticator_cookie_session.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pipeline/authn/authenticator_cookie_session.go b/pipeline/authn/authenticator_cookie_session.go index 9d2d66ef0b..5bccc8a435 100644 --- a/pipeline/authn/authenticator_cookie_session.go +++ b/pipeline/authn/authenticator_cookie_session.go @@ -177,12 +177,12 @@ func forwardRequestToSessionStore(r *http.Request, checkSessionURL string, prese body, err := ioutil.ReadAll(res.Body) if err != nil { - return json.RawMessage{}, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to read session context from remote: %+v", err)) + return json.RawMessage{}, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to read response from remote: %s", err)) } if res.StatusCode == 200 { return body, nil } - return json.RawMessage{}, errors.WithStack(helper.ErrUnauthorized.WithReasonf("API said: %d -- %+v", res.StatusCode, body)) + return json.RawMessage{}, errors.WithStack(helper.ErrUnauthorized.WithReasonf("Remote returned non 200 status code: %d", res.StatusCode)) } From 1385af7627b43dbb16922ae37cad7802af229281 Mon Sep 17 00:00:00 2001 From: till Date: Thu, 10 Feb 2022 10:55:14 +0100 Subject: [PATCH 06/10] Chore: introduce logger --- .../configuration/provider_viper_public_test.go | 2 +- driver/registry_memory.go | 4 ++-- pipeline/authn/authenticator_bearer_token.go | 12 ++++++++---- pipeline/authn/authenticator_cookie_session.go | 16 +++++++++++----- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/driver/configuration/provider_viper_public_test.go b/driver/configuration/provider_viper_public_test.go index 4315b9f3dd..5df0190b31 100644 --- a/driver/configuration/provider_viper_public_test.go +++ b/driver/configuration/provider_viper_public_test.go @@ -258,7 +258,7 @@ func TestViperProvider(t *testing.T) { }) t.Run("authenticator=cookie_session", func(t *testing.T) { - a := authn.NewAuthenticatorCookieSession(p) + a := authn.NewAuthenticatorCookieSession(p, logger) assert.True(t, p.AuthenticatorIsEnabled(a.GetID())) require.NoError(t, a.Validate(nil)) diff --git a/driver/registry_memory.go b/driver/registry_memory.go index 783beee6f0..5a93089944 100644 --- a/driver/registry_memory.go +++ b/driver/registry_memory.go @@ -365,8 +365,8 @@ func (r *RegistryMemory) prepareAuthn() { if r.authenticators == nil { interim := []authn.Authenticator{ authn.NewAuthenticatorAnonymous(r.c), - authn.NewAuthenticatorCookieSession(r.c), - authn.NewAuthenticatorBearerToken(r.c), + authn.NewAuthenticatorCookieSession(r.c, r.Logger()), + authn.NewAuthenticatorBearerToken(r.c, r.Logger()), authn.NewAuthenticatorJWT(r.c, r), authn.NewAuthenticatorNoOp(r.c), authn.NewAuthenticatorOAuth2ClientCredentials(r.c, r.Logger()), diff --git a/pipeline/authn/authenticator_bearer_token.go b/pipeline/authn/authenticator_bearer_token.go index 96e3d73f0f..80acb2d4e4 100644 --- a/pipeline/authn/authenticator_bearer_token.go +++ b/pipeline/authn/authenticator_bearer_token.go @@ -12,6 +12,8 @@ import ( "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/helper" "github.com/ory/oathkeeper/pipeline" + + "github.com/ory/x/logrusx" ) func init() { @@ -36,12 +38,14 @@ type AuthenticatorBearerTokenConfiguration struct { } type AuthenticatorBearerToken struct { - c configuration.Provider + c configuration.Provider + logger *logrusx.Logger } -func NewAuthenticatorBearerToken(c configuration.Provider) *AuthenticatorBearerToken { +func NewAuthenticatorBearerToken(c configuration.Provider, logger *logrusx.Logger) *AuthenticatorBearerToken { return &AuthenticatorBearerToken{ - c: c, + c: c, + logger: logger, } } @@ -86,7 +90,7 @@ func (a *AuthenticatorBearerToken) Authenticate(r *http.Request, session *Authen return errors.WithStack(ErrAuthenticatorNotResponsible) } - body, err := forwardRequestToSessionStore(r, cf.CheckSessionURL, cf.PreserveQuery, cf.PreservePath, cf.PreserveHost, cf.SetHeaders, cf.ForceMethod) + body, err := forwardRequestToSessionStore(r, cf.CheckSessionURL, cf.PreserveQuery, cf.PreservePath, cf.PreserveHost, cf.SetHeaders, cf.ForceMethod, a.logger) if err != nil { return err } diff --git a/pipeline/authn/authenticator_cookie_session.go b/pipeline/authn/authenticator_cookie_session.go index 5bccc8a435..b02461dc94 100644 --- a/pipeline/authn/authenticator_cookie_session.go +++ b/pipeline/authn/authenticator_cookie_session.go @@ -16,6 +16,8 @@ import ( "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/helper" "github.com/ory/oathkeeper/pipeline" + + "github.com/ory/x/logrusx" ) func init() { @@ -40,12 +42,14 @@ type AuthenticatorCookieSessionConfiguration struct { } type AuthenticatorCookieSession struct { - c configuration.Provider + c configuration.Provider + logger *logrusx.Logger } -func NewAuthenticatorCookieSession(c configuration.Provider) *AuthenticatorCookieSession { +func NewAuthenticatorCookieSession(c configuration.Provider, logger *logrusx.Logger) *AuthenticatorCookieSession { return &AuthenticatorCookieSession{ - c: c, + c: c, + logger: logger, } } @@ -89,7 +93,7 @@ func (a *AuthenticatorCookieSession) Authenticate(r *http.Request, session *Auth return errors.WithStack(ErrAuthenticatorNotResponsible) } - body, err := forwardRequestToSessionStore(r, cf.CheckSessionURL, cf.PreserveQuery, cf.PreservePath, cf.PreserveHost, cf.SetHeaders, cf.ForceMethod) + body, err := forwardRequestToSessionStore(r, cf.CheckSessionURL, cf.PreserveQuery, cf.PreservePath, cf.PreserveHost, cf.SetHeaders, cf.ForceMethod, a.logger) if err != nil { return err } @@ -129,7 +133,7 @@ func cookieSessionResponsible(r *http.Request, only []string) bool { return false } -func forwardRequestToSessionStore(r *http.Request, checkSessionURL string, preserveQuery bool, preservePath bool, preserveHost bool, setHeaders map[string]string, m string) (json.RawMessage, error) { +func forwardRequestToSessionStore(r *http.Request, checkSessionURL string, preserveQuery bool, preservePath bool, preserveHost bool, setHeaders map[string]string, m string, logger *logrusx.Logger) (json.RawMessage, error) { reqUrl, err := url.Parse(checkSessionURL) if err != nil { return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to parse session check URL: %s", err)) @@ -177,6 +181,7 @@ func forwardRequestToSessionStore(r *http.Request, checkSessionURL string, prese body, err := ioutil.ReadAll(res.Body) if err != nil { + logger.Tracef("Error reading response from remote: %v", err) return json.RawMessage{}, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to read response from remote: %s", err)) } @@ -184,5 +189,6 @@ func forwardRequestToSessionStore(r *http.Request, checkSessionURL string, prese return body, nil } + logger.Tracef("Remote returned non-200 status code '%d' with body: %s", res.StatusCode, body) return json.RawMessage{}, errors.WithStack(helper.ErrUnauthorized.WithReasonf("Remote returned non 200 status code: %d", res.StatusCode)) } From f46521a0efcb72a494c1a0ad14e66ac8ea6ceaf2 Mon Sep 17 00:00:00 2001 From: till Date: Thu, 10 Feb 2022 11:03:00 +0100 Subject: [PATCH 07/10] Chore: add test for error when subject is empty --- pipeline/authn/authenticator_bearer_token_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pipeline/authn/authenticator_bearer_token_test.go b/pipeline/authn/authenticator_bearer_token_test.go index 3e73506389..1d306e7c05 100644 --- a/pipeline/authn/authenticator_bearer_token_test.go +++ b/pipeline/authn/authenticator_bearer_token_test.go @@ -272,6 +272,19 @@ func TestAuthenticatorBearerToken(t *testing.T) { Extra: map[string]interface{}{"session": map[string]interface{}{"foo": "bar"}, "identity": map[string]interface{}{"id": "123"}}, }, }, + { + d: "it should error when the subject is empty", + r: &http.Request{Header: http.Header{"Authorization": {"bearer token"}}, URL: &url.URL{Path: ""}}, + setup: func(t *testing.T, m *httprouter.Router) { + m.GET("/", func(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) { + w.WriteHeader(200) + w.Write([]byte(`{"identity": {"id": ""}, "session": {"foo": "bar"}}`)) + }) + }, + config: []byte(`{"subject_from": "identity.id", "extra_from": "@this"}`), + expectErr: true, + expectSess: &AuthenticationSession{}, + }, } { t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.d), func(t *testing.T) { var ts *httptest.Server From d6c05118a0414e6cd913e5c6451da730ff1c0e0d Mon Sep 17 00:00:00 2001 From: till Date: Thu, 10 Feb 2022 11:04:52 +0100 Subject: [PATCH 08/10] Chore: add a trace to the error --- pipeline/authn/authenticator_bearer_token.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipeline/authn/authenticator_bearer_token.go b/pipeline/authn/authenticator_bearer_token.go index 80acb2d4e4..e9d32462d7 100644 --- a/pipeline/authn/authenticator_bearer_token.go +++ b/pipeline/authn/authenticator_bearer_token.go @@ -112,7 +112,7 @@ func (a *AuthenticatorBearerToken) Authenticate(r *http.Request, session *Authen } if len(subject) == 0 { - return helper.ErrForbidden.WithReasonf("Could not decode subject from response.") + return errors.WithStack(helper.ErrForbidden.WithReasonf("Subject field from remote endpoint is empty.")) } session.Subject = subject From bcb5deaa09e6f0bebae2a3e5cdfffcca47d32272 Mon Sep 17 00:00:00 2001 From: till Date: Thu, 23 Jun 2022 15:08:00 +0200 Subject: [PATCH 09/10] Chore: address CR --- pipeline/authn/authenticator_cookie_session.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pipeline/authn/authenticator_cookie_session.go b/pipeline/authn/authenticator_cookie_session.go index b02461dc94..3248d8298d 100644 --- a/pipeline/authn/authenticator_cookie_session.go +++ b/pipeline/authn/authenticator_cookie_session.go @@ -184,11 +184,13 @@ func forwardRequestToSessionStore(r *http.Request, checkSessionURL string, prese logger.Tracef("Error reading response from remote: %v", err) return json.RawMessage{}, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to read response from remote: %s", err)) } + defer res.Body.Close() if res.StatusCode == 200 { return body, nil } - logger.Tracef("Remote returned non-200 status code '%d' with body: %s", res.StatusCode, body) + logger.WithField("response_code", res.StatusCode).WithField("body", string(body)).Trace() + return json.RawMessage{}, errors.WithStack(helper.ErrUnauthorized.WithReasonf("Remote returned non 200 status code: %d", res.StatusCode)) } From 4a4fd23fc59c2120472677fd295b78687d955526 Mon Sep 17 00:00:00 2001 From: till Date: Wed, 22 Feb 2023 17:16:05 +0100 Subject: [PATCH 10/10] Fix: grpc middleware test - refactors test helpers into middleware_test.go - provides a "complete" response from the test server - complete response is necessary in order to provide subject (to allow the request) --- middleware/grpc_middleware_test.go | 61 +---------------- middleware/middleware_test.go | 105 +++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 60 deletions(-) create mode 100644 middleware/middleware_test.go diff --git a/middleware/grpc_middleware_test.go b/middleware/grpc_middleware_test.go index 7cc0be3d62..ec5b0f100c 100644 --- a/middleware/grpc_middleware_test.go +++ b/middleware/grpc_middleware_test.go @@ -9,11 +9,6 @@ import ( "context" "encoding/json" "fmt" - "io" - "net" - "net/http" - "net/http/httptest" - "os" "testing" "time" @@ -22,10 +17,8 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/status" "google.golang.org/grpc/test/bufconn" - "google.golang.org/grpc/test/grpc_testing" grpcTesting "google.golang.org/grpc/test/grpc_testing" "github.com/ory/oathkeeper/driver" @@ -34,59 +27,6 @@ import ( "github.com/ory/oathkeeper/rule" ) -func testClient(t *testing.T, l *bufconn.Listener, dialOpts ...grpc.DialOption) grpcTesting.TestServiceClient { - conn, err := grpc.Dial("bufnet", - append(dialOpts, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithAuthority("myproject.apis.ory.sh"), - grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return l.Dial() }), - )..., - ) - require.NoError(t, err) - t.Cleanup(func() { conn.Close() }) - - return grpcTesting.NewTestServiceClient(conn) -} - -func testTokenCheckServer(t *testing.T) *httptest.Server { - s := httptest.NewServer(http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("authorization") != "Bearer correct token" { - t.Logf("denied request %+v", r) - w.WriteHeader(http.StatusForbidden) - return - } - t.Logf("allowed request %+v", r) - io.WriteString(w, "{}") - })) - t.Cleanup(s.Close) - return s -} - -func writeTestConfig(t *testing.T, pattern string, content string) string { - f, err := os.CreateTemp(t.TempDir(), pattern) - if err != nil { - t.Error(err) - return "" - } - defer f.Close() - io.WriteString(f, content) - - return f.Name() -} - -type testToken string - -func (t testToken) GetRequestMetadata(context.Context, ...string) (map[string]string, error) { - return map[string]string{"authorization": "Bearer " + string(t)}, nil -} -func (t testToken) RequireTransportSecurity() bool { return false } - -type upstream struct { - *MockTestServiceServer - grpc_testing.UnsafeTestServiceServer -} - func TestMiddleware(t *testing.T) { ctx := context.Background() @@ -104,6 +44,7 @@ authenticators: bearer_token: enabled: true config: + subject_from: identity.id check_session_url: %s authorizers: allow: diff --git a/middleware/middleware_test.go b/middleware/middleware_test.go new file mode 100644 index 0000000000..66ebb68116 --- /dev/null +++ b/middleware/middleware_test.go @@ -0,0 +1,105 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package middleware_test + +import ( + "context" + "io" + "net" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/test/bufconn" + grpcTesting "google.golang.org/grpc/test/grpc_testing" +) + +func testClient(t *testing.T, l *bufconn.Listener, dialOpts ...grpc.DialOption) grpcTesting.TestServiceClient { + conn, err := grpc.Dial("bufnet", + append(dialOpts, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithAuthority("myproject.apis.ory.sh"), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return l.Dial() }), + )..., + ) + require.NoError(t, err) + t.Cleanup(func() { conn.Close() }) + + return grpcTesting.NewTestServiceClient(conn) +} + +// testTokenCheckServer is a kratos-style mock server which responds to requests to authenticate the request +// for a successful response the token has to be `Beaerer correct token`. +func testTokenCheckServer(t *testing.T) *httptest.Server { + s := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("authorization") != "Bearer correct token" { + t.Logf("denied request %+v", r) + w.WriteHeader(http.StatusForbidden) + return + } + t.Logf("allowed request %+v", r) + io.WriteString(w, `{ + "id": "user-id", + "active": true, + "expires_at": "2022-12-31T13:50:30.427292Z", + "authenticated_at": "2022-12-01T13:50:30.825516Z", + "authenticator_assurance_level": "aal1", + "authentication_methods": [ + { + "method": "password", + "aal": "aal1", + "completed_at": "2022-12-01T13:50:30.427375604Z" + } + ], + "issued_at": "2022-12-01T13:50:30.427292Z", + "identity": { + "id": "user-id", + "schema_id": "schema-id", + "state": "active", + "state_changed_at": "2022-12-01T13:50:30.331786Z", + "traits": { + "email": "user@example.org", + "name": "User" + }, + "verifiable_addresses": [], + "recovery_addresses": [], + "metadata_public": null, + "created_at": "2022-12-01T13:50:30.340643Z", + "updated_at": "2022-12-01T13:50:30.340643Z" + }, + "devices": [] + }`) + })) + t.Cleanup(s.Close) + return s +} + +func writeTestConfig(t *testing.T, pattern string, content string) string { + f, err := os.CreateTemp(t.TempDir(), pattern) + if err != nil { + t.Error(err) + return "" + } + defer f.Close() + io.WriteString(f, content) + + return f.Name() +} + +type testToken string + +func (t testToken) GetRequestMetadata(context.Context, ...string) (map[string]string, error) { + return map[string]string{"authorization": "Bearer " + string(t)}, nil +} +func (t testToken) RequireTransportSecurity() bool { return false } + +type upstream struct { + *MockTestServiceServer + grpcTesting.UnsafeTestServiceServer +}