diff --git a/api/session-manager.yaml b/api/session-manager.yaml index 312b27ca..0326017e 100644 --- a/api/session-manager.yaml +++ b/api/session-manager.yaml @@ -15,8 +15,7 @@ paths: - generate: - a state ID for the state parameter of the OIDC authorize endpoint - a PKCE verifier and the related PKCE challenge (S256) - - create a fingerprint according to the selected method (see below) - - based on the state ID, persist the PKCE verifier, fingerprint, CMK tenant ID and the given request URI + - based on the state ID, persist the PKCE verifier, CMK tenant ID and the given request URI - build the authorize URI and return it with a 302 response. With this the CMK UI initiated the flow and the browser is redirected to the correct OIDC provider. Based on the embedded redirect_uri the Session Manager will directly receive the auth code from the OIDC provider on the /callback endpoint to continue the flow. @@ -145,16 +144,14 @@ paths: get: description: | After creating the auth code, the OIDC provider calls this endpoint, delivering the code. The Session Manager can then requests the OIDC provider to exchange the code for a valid token, additionally providing the related PKCE verifier. Steps: - - for the given state ID, look up the request URI, PKCE verifier, fingerprint, + - for the given state ID, look up the request URI, PKCE verifier, CMK tenant ID and token endpoint from the storage - - verify the current fingerprint matches the one from the storage - perform a POST request to the token endpoint - based on the returned OIDC token, generate a session ID and CSRF token and store: - the session ID - the CSRF token - the CMK tenant ID - parts of the the OIDC token: (claims, access token, refresh token) - - the fingerprint - the date and time the session shall expire - remove the state for the state ID from the storage - in the response set the session and CSRF cookies @@ -189,12 +186,6 @@ paths: Location: schema: type: string - "403": - description: Fingerprint mismatch (returned when error_uri is not available) - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorModel" default: description: Unexpected error (returned when error_uri is not available) content: diff --git a/go.mod b/go.mod index f751424e..940b6c59 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/jellydator/ttlcache/v3 v3.4.0 github.com/moby/moby/api v1.54.2 github.com/oapi-codegen/runtime v1.4.1 - github.com/openkcm/api-sdk v0.18.0 + github.com/openkcm/api-sdk v0.18.1 github.com/openkcm/common-sdk v1.16.1 github.com/pressly/goose/v3 v3.27.1 github.com/samber/oops v1.22.0 @@ -51,7 +51,6 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect @@ -66,8 +65,6 @@ require ( github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/ebitengine/purego v0.10.0 // indirect - github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect - github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.10.1 // indirect @@ -124,7 +121,6 @@ require ( github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 // indirect github.com/pingcap/log v1.1.0 // indirect github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0 // indirect - github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.23.2 // indirect diff --git a/go.sum b/go.sum index 563e92a9..460159a8 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,6 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= -github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= @@ -68,10 +66,6 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= -github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= -github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= -github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= github.com/exaring/otelpgx v0.11.1 h1:pE79fIg/qh/Lpu00kvswFC5dKfqyJJhMJ4Y4N3w5Lj4= github.com/exaring/otelpgx v0.11.1/go.mod h1:3OojrUKhhy3lTbYIMBijP3YjMey/jo14eHAW5cXcUdk= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= @@ -254,8 +248,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/openkcm/api-sdk v0.18.0 h1:bLURye5GkxSM9SqT3ji94A9HgI9/KgHKFTNgnIeSRWk= -github.com/openkcm/api-sdk v0.18.0/go.mod h1:3CQUZVNl/Nu5K71M1arSiTsJhyLloO3pJpqwBL1oE1o= +github.com/openkcm/api-sdk v0.18.1 h1:Ch8iPTKz/PAgHI1HVHeYHKT8iuKjb5jHHRJoPl10HiM= +github.com/openkcm/api-sdk v0.18.1/go.mod h1:3CQUZVNl/Nu5K71M1arSiTsJhyLloO3pJpqwBL1oE1o= github.com/openkcm/common-sdk v1.16.1 h1:0GIFrEDR7qXj/ghSvwWVHnWCAzBGD7iFbdRN4WWv9tM= github.com/openkcm/common-sdk v1.16.1/go.mod h1:2UuIjhOuee6gQKJt3EnenhB8mXWriGHEXdxCGPWfRxw= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= @@ -275,8 +269,6 @@ github.com/pingcap/log v1.1.0/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoO github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0 h1:W3rpAI3bubR6VWOcwxDIG0Gz9G5rl5b3SL116T0vBt0= github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0/go.mod h1:+8feuexTKcXHZF/dkDfvCwEyBAmgb4paFc3/WeYV2eE= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= diff --git a/integration/session_grpc_test.go b/integration/session_grpc_test.go index 7a235e4e..b4df8b1f 100644 --- a/integration/session_grpc_test.go +++ b/integration/session_grpc_test.go @@ -44,9 +44,8 @@ func TestSessionGRPC(t *testing.T) { t.Run("GetSession - session not found", func(t *testing.T) { resp, err := sessionClient.GetSession(ctx, &sessionv1.GetSessionRequest{ - SessionId: "non-existent-session", - TenantId: "tenant-123", - Fingerprint: "fingerprint-123", + SessionId: "non-existent-session", + TenantId: "tenant-123", }) assert.NoError(t, err) assert.NotNil(t, resp) @@ -58,7 +57,6 @@ func TestSessionGRPC(t *testing.T) { sess := session.Session{ ID: uuid.Must(uuid.NewV4()).String(), TenantID: "tenant-inactive", - Fingerprint: "fingerprint-inactive", Issuer: "https://issuer.example.com", ProviderID: "provider-123", AccessToken: "token-123", @@ -71,9 +69,8 @@ func TestSessionGRPC(t *testing.T) { require.NoError(t, err) resp, err := sessionClient.GetSession(ctx, &sessionv1.GetSessionRequest{ - SessionId: sess.ID, - TenantId: sess.TenantID, - Fingerprint: sess.Fingerprint, + SessionId: sess.ID, + TenantId: sess.TenantID, }) assert.NoError(t, err) assert.NotNil(t, resp) @@ -85,7 +82,6 @@ func TestSessionGRPC(t *testing.T) { sess := session.Session{ ID: uuid.Must(uuid.NewV4()).String(), TenantID: "tenant-active", - Fingerprint: "fingerprint-active", Issuer: "https://issuer.example.com", ProviderID: "provider-active", AccessToken: "token-active", @@ -109,9 +105,8 @@ func TestSessionGRPC(t *testing.T) { // Note: This test will fail validation because there's no trust mapping configured // but it tests the session retrieval path resp, err := sessionClient.GetSession(ctx, &sessionv1.GetSessionRequest{ - SessionId: sess.ID, - TenantId: sess.TenantID, - Fingerprint: sess.Fingerprint, + SessionId: sess.ID, + TenantId: sess.TenantID, }) assert.NoError(t, err) assert.NotNil(t, resp) @@ -119,37 +114,10 @@ func TestSessionGRPC(t *testing.T) { assert.False(t, resp.GetValid()) }) - t.Run("GetSession - fingerprint mismatch", func(t *testing.T) { - sess := session.Session{ - ID: uuid.Must(uuid.NewV4()).String(), - TenantID: "tenant-fingerprint", - Fingerprint: "correct-fingerprint", - Issuer: "https://issuer.example.com", - ProviderID: "provider-fp", - AccessToken: "token-fp", - Expiry: time.Now().Add(1 * time.Hour), - } - err := sessionRepo.StoreSession(ctx, sess) - require.NoError(t, err) - - err = sessionRepo.BumpActive(ctx, sess.ID, 1*time.Hour) - require.NoError(t, err) - - resp, err := sessionClient.GetSession(ctx, &sessionv1.GetSessionRequest{ - SessionId: sess.ID, - TenantId: sess.TenantID, - Fingerprint: "wrong-fingerprint", - }) - assert.NoError(t, err) - assert.NotNil(t, resp) - assert.False(t, resp.GetValid()) - }) - t.Run("GetSession - tenant ID mismatch", func(t *testing.T) { sess := session.Session{ ID: uuid.Must(uuid.NewV4()).String(), TenantID: "correct-tenant", - Fingerprint: "fingerprint-tenant", Issuer: "https://issuer.example.com", ProviderID: "provider-tenant", AccessToken: "token-tenant", @@ -162,9 +130,8 @@ func TestSessionGRPC(t *testing.T) { require.NoError(t, err) resp, err := sessionClient.GetSession(ctx, &sessionv1.GetSessionRequest{ - SessionId: sess.ID, - TenantId: "wrong-tenant", - Fingerprint: sess.Fingerprint, + SessionId: sess.ID, + TenantId: "wrong-tenant", }) assert.NoError(t, err) assert.NotNil(t, resp) diff --git a/internal/business/server/http_server.go b/internal/business/server/http_server.go index acaa9ec5..42aa8db2 100644 --- a/internal/business/server/http_server.go +++ b/internal/business/server/http_server.go @@ -8,8 +8,6 @@ import ( "net/http" "strings" - "github.com/openkcm/common-sdk/pkg/fingerprint" - commonmiddleware "github.com/openkcm/common-sdk/pkg/middleware" slogctx "github.com/veqryn/slog-context" @@ -35,7 +33,7 @@ func createHTTPServer(_ context.Context, cfg *config.Config, sManager *session.M }, ) - handler := fingerprint.FingerprintCtxMiddleware(openapi.Handler(strictHandler)) + handler := openapi.Handler(strictHandler) handler = middleware.ResponseWriterMiddleware(handler) handler = commonmiddleware.SecurityHeadersMiddleware(handler, map[string]string{ "Content-Security-Policy": "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';", diff --git a/internal/business/server/openapi.go b/internal/business/server/openapi.go index 7a5b8a47..8f4bd9ba 100644 --- a/internal/business/server/openapi.go +++ b/internal/business/server/openapi.go @@ -9,7 +9,6 @@ import ( "strings" "github.com/openkcm/common-sdk/pkg/csrf" - "github.com/openkcm/common-sdk/pkg/fingerprint" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/codes" @@ -24,8 +23,8 @@ import ( // sessionManager defines the interface for session management operations // used by the OpenAPI server. type sessionManager interface { - MakeAuthURI(ctx context.Context, tenantID, fingerprint, requestURI, errorURI string) (string, string, error) - FinaliseOIDCLogin(ctx context.Context, state, code, fingerprint string) (session.OIDCSessionData, error) + MakeAuthURI(ctx context.Context, tenantID, requestURI, errorURI string) (string, string, error) + FinaliseOIDCLogin(ctx context.Context, state, code string) (session.OIDCSessionData, error) MakeSessionCookie(ctx context.Context, tenantID, sessionID string) (*http.Cookie, error) MakeCSRFCookie(ctx context.Context, tenantID, csrfToken string) (*http.Cookie, error) MakeLoginCSRFCookie(ctx context.Context, csrfToken string) (*http.Cookie, error) @@ -88,15 +87,7 @@ func (s *openAPIServer) Auth(ctx context.Context, request openapi.AuthRequestObj return s.authErrorResponse(errorURI, serviceerr.ErrInvalidRequest), nil } - fingerprint, err := fingerprint.ExtractFingerprint(ctx) - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, "failed to extract fingerprint") - slogctx.Error(ctx, "Failed to extract fingerprint", "error", err) - return s.authErrorResponse(errorURI, serviceerr.ErrUnknown), nil - } - - url, csrfToken, err := s.sManager.MakeAuthURI(ctx, request.Params.TenantID, fingerprint, request.Params.RequestURI, errorURI) + url, csrfToken, err := s.sManager.MakeAuthURI(ctx, request.Params.TenantID, request.Params.RequestURI, errorURI) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, "failed to build auth URI") @@ -146,14 +137,6 @@ func (s *openAPIServer) Callback(ctx context.Context, req openapi.CallbackReques // Try to load error_uri from state (best-effort, for error redirect) errorURI := s.getErrorURIFromState(ctx, req.Params.State) - currentFingerprint, err := fingerprint.ExtractFingerprint(ctx) - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, "failed extract fingerprint") - slogctx.Error(ctx, "Failed to extract fingerprint", "error", err) - return s.callbackErrorResponse(errorURI, serviceerr.ErrUnknown), nil - } - // Get the response writer from the context rw, err := middleware.ResponseWriterFromContext(ctx) if err != nil { @@ -170,7 +153,7 @@ func (s *openAPIServer) Callback(ctx context.Context, req openapi.CallbackReques return s.callbackErrorResponse(errorURI, err), nil } - result, err := s.sManager.FinaliseOIDCLogin(ctx, req.Params.State, req.Params.Code, currentFingerprint) + result, err := s.sManager.FinaliseOIDCLogin(ctx, req.Params.State, req.Params.Code) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, "failed to finalise OIDC login") @@ -227,7 +210,7 @@ func (s *openAPIServer) callbackErrorResponse(errorURI string, err error) openap } // callbackFinaliseErrorResponse handles the error case after FinaliseOIDCLogin fails, -// masking fingerprint mismatch details when no error redirect is available. +// masking sensitive details when no error redirect is available. func (s *openAPIServer) callbackFinaliseErrorResponse(errorURI string, err error) openapi.CallbackResponseObject { if redirectURL := s.buildErrorRedirectURL(errorURI, err); redirectURL != "" { return openapi.Callback302Response{ @@ -237,8 +220,7 @@ func (s *openAPIServer) callbackFinaliseErrorResponse(errorURI string, err error body, status := s.toErrorModel(err) if status == 403 { - // return generic Unauthorized for 403 Forbidden to avoid leaking information on - // fingerprint mismatch in the original error body + // return generic Unauthorized for 403 Forbidden to avoid leaking sensitive information body, status = s.toErrorModel(serviceerr.ErrUnauthorized) } return openapi.CallbackdefaultJSONResponse{ diff --git a/internal/business/server/openapi_test.go b/internal/business/server/openapi_test.go index 00364ffa..6827ac38 100644 --- a/internal/business/server/openapi_test.go +++ b/internal/business/server/openapi_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/openkcm/common-sdk/pkg/csrf" - "github.com/openkcm/common-sdk/pkg/fingerprint" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -26,8 +25,8 @@ const ( // mockSessionManager is a mock implementation of sessionManager interface for testing type mockSessionManager struct { - makeAuthURIFunc func(ctx context.Context, tenantID, fingerprint, requestURI, errorURI string) (string, string, error) - finaliseOIDCLoginFunc func(ctx context.Context, state, code, fingerprint string) (session.OIDCSessionData, error) + makeAuthURIFunc func(ctx context.Context, tenantID, requestURI, errorURI string) (string, string, error) + finaliseOIDCLoginFunc func(ctx context.Context, state, code string) (session.OIDCSessionData, error) makeSessionCookieFunc func(ctx context.Context, tenantID, sessionID string) (*http.Cookie, error) makeCSRFCookieFunc func(ctx context.Context, tenantID, csrfToken string) (*http.Cookie, error) makeLoginCSRFCookieFunc func(ctx context.Context, csrfToken string) (*http.Cookie, error) @@ -36,16 +35,16 @@ type mockSessionManager struct { bcLogoutFunc func(ctx context.Context, logoutToken string) error } -func (m *mockSessionManager) MakeAuthURI(ctx context.Context, tenantID, fp, requestURI, errorURI string) (string, string, error) { +func (m *mockSessionManager) MakeAuthURI(ctx context.Context, tenantID, requestURI, errorURI string) (string, string, error) { if m.makeAuthURIFunc != nil { - return m.makeAuthURIFunc(ctx, tenantID, fp, requestURI, errorURI) + return m.makeAuthURIFunc(ctx, tenantID, requestURI, errorURI) } return "", "", errors.New("not implemented") } -func (m *mockSessionManager) FinaliseOIDCLogin(ctx context.Context, state, code, fp string) (session.OIDCSessionData, error) { +func (m *mockSessionManager) FinaliseOIDCLogin(ctx context.Context, state, code string) (session.OIDCSessionData, error) { if m.finaliseOIDCLoginFunc != nil { - return m.finaliseOIDCLoginFunc(ctx, state, code, fp) + return m.finaliseOIDCLoginFunc(ctx, state, code) } return session.OIDCSessionData{}, errors.New("not implemented") } @@ -107,26 +106,14 @@ func TestNewOpenAPIServer(t *testing.T) { }) } -func TestOpenAPIServer_Auth_ContextCanceled(t *testing.T) { - ctx, cancel := context.WithCancel(t.Context()) - cancel() - server := newOpenAPIServer(nil, nil, "", "", []string{allowedBaseURL}) - req := openapi.AuthRequestObject{ - Params: openapi.AuthParams{RequestURI: allowedBaseURL + "/page"}, +func TestOpenAPIServer_Auth_MakeAuthURI_NilManager(t *testing.T) { + mock := &mockSessionManager{ + makeAuthURIFunc: func(ctx context.Context, tenantID, requestURI, errorURI string) (string, string, error) { + return "", "", errors.New("context canceled") + }, } - resp, err := server.Auth(ctx, req) - assert.NoError(t, err) - - assert.IsType(t, openapi.AuthdefaultJSONResponse{}, resp) - - r, _ := resp.(openapi.AuthdefaultJSONResponse) - assert.Equal(t, string(serviceerr.CodeUnknown), r.Body.Error) - assert.Equal(t, http.StatusInternalServerError, r.StatusCode) -} - -func TestOpenAPIServer_Auth_ExtractFingerprint_Failed(t *testing.T) { ctx := t.Context() - server := newOpenAPIServer(nil, nil, "", "", []string{allowedBaseURL}) + server := newOpenAPIServer(mock, nil, "", "", []string{allowedBaseURL}) req := openapi.AuthRequestObject{ Params: openapi.AuthParams{RequestURI: allowedBaseURL + "/page"}, } @@ -141,12 +128,12 @@ func TestOpenAPIServer_Auth_ExtractFingerprint_Failed(t *testing.T) { } func TestOpenAPIServer_Auth_MakeAuthURI_Failed(t *testing.T) { - ctx := fingerprint.WithFingerprint(t.Context(), "") mock := &mockSessionManager{ - makeAuthURIFunc: func(ctx context.Context, tenantID string, fingerprint string, requestURI string, errorURI string) (string, string, error) { + makeAuthURIFunc: func(ctx context.Context, tenantID, requestURI, errorURI string) (string, string, error) { return "", "", errors.New("error") }, } + ctx := t.Context() server := newOpenAPIServer(mock, nil, "", "", []string{allowedBaseURL}) req := openapi.AuthRequestObject{ Params: openapi.AuthParams{RequestURI: allowedBaseURL + "/page"}, @@ -162,15 +149,15 @@ func TestOpenAPIServer_Auth_MakeAuthURI_Failed(t *testing.T) { } func TestOpenAPIServer_Auth_MakeCSRFCookie_Failed(t *testing.T) { - ctx := fingerprint.WithFingerprint(t.Context(), "") mock := &mockSessionManager{ - makeAuthURIFunc: func(ctx context.Context, tenantID string, fingerprint string, requestURI string, errorURI string) (string, string, error) { + makeAuthURIFunc: func(ctx context.Context, tenantID, requestURI, errorURI string) (string, string, error) { return "https://example.com/redirect", "token", nil }, makeLoginCSRFCookieFunc: func(ctx context.Context, csrfToken string) (*http.Cookie, error) { return nil, errors.New("error") }, } + ctx := t.Context() server := newOpenAPIServer(mock, nil, "", "", []string{allowedBaseURL}) req := openapi.AuthRequestObject{ Params: openapi.AuthParams{RequestURI: allowedBaseURL + "/page"}, @@ -185,15 +172,15 @@ func TestOpenAPIServer_Auth_MakeCSRFCookie_Failed(t *testing.T) { } func TestOpenAPIServer_Auth_MakeAuthURI_Success(t *testing.T) { - ctx := fingerprint.WithFingerprint(t.Context(), "") mock := &mockSessionManager{ - makeAuthURIFunc: func(ctx context.Context, tenantID string, fingerprint string, requestURI string, errorURI string) (string, string, error) { + makeAuthURIFunc: func(ctx context.Context, tenantID, requestURI, errorURI string) (string, string, error) { return "https://example.com/redirect", "token", nil }, makeLoginCSRFCookieFunc: func(ctx context.Context, csrfToken string) (*http.Cookie, error) { return &http.Cookie{Name: "csrf-token", Value: csrfToken}, nil }, } + ctx := t.Context() server := newOpenAPIServer(mock, nil, "", "", []string{"https://example.com"}) req := openapi.AuthRequestObject{ Params: openapi.AuthParams{ @@ -251,10 +238,9 @@ func TestOpenAPIServer_Callback_ExtractFingerprint_Failed(t *testing.T) { } func TestOpenAPIServer_Callback_NoResponseWriter(t *testing.T) { - t.Run("returns an error when fingerprint is not in the context", func(t *testing.T) { - ctx := fingerprint.WithFingerprint(t.Context(), "fingerprint") - + t.Run("returns error when no response writer", func(t *testing.T) { server := newOpenAPIServer(nil, nil, "", "", []string{allowedBaseURL}) + ctx := t.Context() callbackReq := openapi.CallbackRequestObject{ Params: openapi.CallbackParams{ @@ -277,16 +263,15 @@ func TestOpenAPIServer_Callback_NoResponseWriter(t *testing.T) { func TestOpenAPIServer_Callback_FinaliseOIDCLogin_Failed(t *testing.T) { t.Run("returns an error when FinaliseOIDCLogin failed", func(t *testing.T) { - ctx := fingerprint.WithFingerprint(t.Context(), "fingerprint") w := httptest.NewRecorder() - ctx = context.WithValue(ctx, middleware.ResponseWriterKey, w) + ctx := context.WithValue(t.Context(), middleware.ResponseWriterKey, w) csrfSecret := []byte("test-secret") state := "state" loginCsrfToken := csrf.NewToken(state, csrfSecret) mock := &mockSessionManager{ - finaliseOIDCLoginFunc: func(ctx context.Context, state, code, fingerprint string) (session.OIDCSessionData, error) { + finaliseOIDCLoginFunc: func(ctx context.Context, state, code string) (session.OIDCSessionData, error) { return session.OIDCSessionData{}, serviceerr.ErrAccessDenied }, } @@ -314,16 +299,15 @@ func TestOpenAPIServer_Callback_FinaliseOIDCLogin_Failed(t *testing.T) { func TestOpenAPIServer_Callback_MakeSessionCookie_Failed(t *testing.T) { t.Run("returns an error when MakeSessionCookie failed", func(t *testing.T) { - ctx := fingerprint.WithFingerprint(t.Context(), "fingerprint") w := httptest.NewRecorder() - ctx = context.WithValue(ctx, middleware.ResponseWriterKey, w) + ctx := context.WithValue(t.Context(), middleware.ResponseWriterKey, w) csrfSecret := []byte("test-secret") state := "state" loginCsrfToken := csrf.NewToken(state, csrfSecret) mock := &mockSessionManager{ - finaliseOIDCLoginFunc: func(ctx context.Context, state, code, fingerprint string) (session.OIDCSessionData, error) { + finaliseOIDCLoginFunc: func(ctx context.Context, state, code string) (session.OIDCSessionData, error) { return session.OIDCSessionData{ SessionID: "s-id", TenantID: "t-id", @@ -359,9 +343,8 @@ func TestOpenAPIServer_Callback_MakeSessionCookie_Failed(t *testing.T) { } func TestOpenAPIServer_Callback_InvalidCsrfToken_Failed(t *testing.T) { t.Run("returns an error when login CSRF token is invalid", func(t *testing.T) { - ctx := fingerprint.WithFingerprint(t.Context(), "fingerprint") w := httptest.NewRecorder() - ctx = context.WithValue(ctx, middleware.ResponseWriterKey, w) + ctx := context.WithValue(t.Context(), middleware.ResponseWriterKey, w) csrfSecret := []byte("test-secret") @@ -389,16 +372,15 @@ func TestOpenAPIServer_Callback_InvalidCsrfToken_Failed(t *testing.T) { func TestOpenAPIServer_Callback_MakeCSRFCookie_Failed(t *testing.T) { t.Run("returns an error when MakeCSRFCookie failed", func(t *testing.T) { - ctx := fingerprint.WithFingerprint(t.Context(), "fingerprint") w := httptest.NewRecorder() - ctx = context.WithValue(ctx, middleware.ResponseWriterKey, w) + ctx := context.WithValue(t.Context(), middleware.ResponseWriterKey, w) csrfSecret := []byte("test-secret") state := "state" loginCsrfToken := csrf.NewToken(state, csrfSecret) mock := &mockSessionManager{ - finaliseOIDCLoginFunc: func(ctx context.Context, state, code, fingerprint string) (session.OIDCSessionData, error) { + finaliseOIDCLoginFunc: func(ctx context.Context, state, code string) (session.OIDCSessionData, error) { return session.OIDCSessionData{ SessionID: "s-id", TenantID: "t-id", @@ -438,16 +420,15 @@ func TestOpenAPIServer_Callback_MakeCSRFCookie_Failed(t *testing.T) { func TestOpenAPIServer_Callback_Success(t *testing.T) { t.Run("success", func(t *testing.T) { - ctx := fingerprint.WithFingerprint(t.Context(), "fingerprint") w := httptest.NewRecorder() - ctx = context.WithValue(ctx, middleware.ResponseWriterKey, w) + ctx := context.WithValue(t.Context(), middleware.ResponseWriterKey, w) csrfSecret := []byte("test-secret") state := "state" loginCsrfToken := csrf.NewToken(state, csrfSecret) mock := &mockSessionManager{ - finaliseOIDCLoginFunc: func(ctx context.Context, state, code, fingerprint string) (session.OIDCSessionData, error) { + finaliseOIDCLoginFunc: func(ctx context.Context, state, code string) (session.OIDCSessionData, error) { return session.OIDCSessionData{ SessionID: "s-id", TenantID: "t-id", @@ -814,10 +795,9 @@ func TestOpenAPIServer_BuildErrorRedirectURL(t *testing.T) { assert.Contains(t, result, "errorDescription=unknown+error") }) - t.Run("maps fingerprint mismatch to fingerprint_mismatch", func(t *testing.T) { - result := server.buildErrorRedirectURL("https://app.example.com/error", serviceerr.ErrFingerprintMismatch) - assert.Contains(t, result, "errorCode=fingerprint_mismatch") - assert.Contains(t, result, "errorDescription=fingerprint+mismatch") + t.Run("handles access denied error", func(t *testing.T) { + result := server.buildErrorRedirectURL("https://app.example.com/error", serviceerr.ErrAccessDenied) + assert.Contains(t, result, "errorCode=access_denied") }) } @@ -888,16 +868,15 @@ func TestOpenAPIServer_CallbackErrorResponse(t *testing.T) { func TestOpenAPIServer_CallbackFinaliseErrorResponse(t *testing.T) { t.Run("returns redirect when errorURI is valid", func(t *testing.T) { server := newOpenAPIServer(nil, nil, "", "", []string{allowedBaseURL}) - resp := server.callbackFinaliseErrorResponse("https://app.example.com/error", serviceerr.ErrFingerprintMismatch) + resp := server.callbackFinaliseErrorResponse("https://app.example.com/error", serviceerr.ErrAccessDenied) assert.IsType(t, openapi.Callback302Response{}, resp) - r, ok := resp.(openapi.Callback302Response) + _, ok := resp.(openapi.Callback302Response) require.True(t, ok) - assert.Contains(t, r.Headers.Location, "errorCode=fingerprint_mismatch") }) t.Run("masks 403 to unauthorized when no errorURI", func(t *testing.T) { server := newOpenAPIServer(nil, nil, "", "", []string{allowedBaseURL}) - resp := server.callbackFinaliseErrorResponse("", serviceerr.ErrFingerprintMismatch) + resp := server.callbackFinaliseErrorResponse("", serviceerr.ErrAccessDenied) assert.IsType(t, openapi.CallbackdefaultJSONResponse{}, resp) r, ok := resp.(openapi.CallbackdefaultJSONResponse) require.True(t, ok) @@ -917,10 +896,8 @@ func TestOpenAPIServer_CallbackFinaliseErrorResponse(t *testing.T) { } // Note: Auth and Callback functions have lower unit test coverage because they depend on -// fingerprint.ExtractFingerprint() from the common-sdk package, which requires proper // HTTP middleware setup. These functions are more thoroughly tested in integration tests // where the full HTTP middleware stack is available. The current unit tests cover: -// - Error handling when fingerprint extraction fails // - Error handling when response writer is not in context // - Error model conversion // For full coverage of success paths and session manager interactions, see integration tests. diff --git a/internal/grpc/session.go b/internal/grpc/session.go index ab226a3f..e49b2148 100644 --- a/internal/grpc/session.go +++ b/internal/grpc/session.go @@ -137,13 +137,6 @@ func (s *SessionServer) GetSession(ctx context.Context, req *sessionv1.GetSessio return nil, dt.Err() } - // Compare fingerprints - if sess.Fingerprint != req.GetFingerprint() { - span.SetStatus(codes.Ok, "fingerprint mismatch") - slogctx.Warn(ctx, "Is this an attack? Fingerprints do not match", "sessionFingerprint", sess.Fingerprint, "requestFingerprint", req.GetFingerprint()) - return &sessionv1.GetSessionResponse{Valid: false}, nil - } - // Compare tenant IDs if sess.TenantID != req.GetTenantId() { span.SetStatus(codes.Ok, "tenant id mismatch") diff --git a/internal/grpc/session_test.go b/internal/grpc/session_test.go index 2dda10fe..ba035659 100644 --- a/internal/grpc/session_test.go +++ b/internal/grpc/session_test.go @@ -95,7 +95,6 @@ func TestGetSession(t *testing.T) { sess := session.Session{ ID: "session-123", TenantID: "tenant-123", - Fingerprint: "fingerprint-123", Issuer: testServer.URL, AccessToken: "access-token-123", Claims: session.Claims{ @@ -128,9 +127,8 @@ func TestGetSession(t *testing.T) { ) req := &sessionv1.GetSessionRequest{ - SessionId: "session-123", - TenantId: "tenant-123", - Fingerprint: "fingerprint-123", + SessionId: "session-123", + TenantId: "tenant-123", } resp, err := server.GetSession(ctx, req) @@ -171,7 +169,6 @@ func TestGetSession(t *testing.T) { sess := session.Session{ ID: "session-groups", TenantID: "tenant-groups", - Fingerprint: "fingerprint-groups", Issuer: testServer.URL, AccessToken: "access-token-groups", Claims: session.Claims{ @@ -199,9 +196,8 @@ func TestGetSession(t *testing.T) { ) req := &sessionv1.GetSessionRequest{ - SessionId: "session-groups", - TenantId: "tenant-groups", - Fingerprint: "fingerprint-groups", + SessionId: "session-groups", + TenantId: "tenant-groups", } resp, err := server.GetSession(ctx, req) @@ -227,10 +223,9 @@ func TestGetSession(t *testing.T) { defer testServer.Close() sess := session.Session{ - ID: "session-456", - TenantID: "tenant-456", - Fingerprint: "fingerprint-456", - Issuer: testServer.URL, + ID: "session-456", + TenantID: "tenant-456", + Issuer: testServer.URL, Claims: session.Claims{ Subject: "user-456", }, @@ -255,9 +250,8 @@ func TestGetSession(t *testing.T) { ) req := &sessionv1.GetSessionRequest{ - SessionId: "session-456", - TenantId: "tenant-456", - Fingerprint: "fingerprint-456", + SessionId: "session-456", + TenantId: "tenant-456", } resp, err := server.GetSession(ctx, req) @@ -277,9 +271,8 @@ func TestGetSession(t *testing.T) { server := grpc.NewSessionServer(ctx, sessionRepo, trustRepo, 90*time.Minute, "") req := &sessionv1.GetSessionRequest{ - SessionId: "session-123", - TenantId: "tenant-123", - Fingerprint: "fingerprint-123", + SessionId: "session-123", + TenantId: "tenant-123", } resp, err := server.GetSession(ctx, req) @@ -291,9 +284,8 @@ func TestGetSession(t *testing.T) { t.Run("invalid - session not active", func(t *testing.T) { sess := session.Session{ - ID: "session-789", - TenantID: "tenant-789", - Fingerprint: "fingerprint-789", + ID: "session-789", + TenantID: "tenant-789", } sessionRepo := sessionmock.NewInMemRepository( @@ -306,9 +298,8 @@ func TestGetSession(t *testing.T) { server := grpc.NewSessionServer(ctx, sessionRepo, trustRepo, 90*time.Minute, "") req := &sessionv1.GetSessionRequest{ - SessionId: "session-789", - TenantId: "tenant-789", - Fingerprint: "fingerprint-789", + SessionId: "session-789", + TenantId: "tenant-789", } resp, err := server.GetSession(ctx, req) @@ -333,9 +324,8 @@ func TestGetSession(t *testing.T) { server := grpc.NewSessionServer(ctx, sessionRepo, trustRepo, 90*time.Minute, "") req := &sessionv1.GetSessionRequest{ - SessionId: "session-fail", - TenantId: "tenant-123", - Fingerprint: "fingerprint-123", + SessionId: "session-fail", + TenantId: "tenant-123", } resp, err := server.GetSession(ctx, req) @@ -347,10 +337,9 @@ func TestGetSession(t *testing.T) { t.Run("invalid - trust mapping not found", func(t *testing.T) { sess := session.Session{ - ID: "session-no-provider", - TenantID: "tenant-no-provider", - Fingerprint: "fingerprint-123", - Issuer: "https://issuer.example.com", + ID: "session-no-provider", + TenantID: "tenant-no-provider", + Issuer: "https://issuer.example.com", } sessionRepo := sessionmock.NewInMemRepository( @@ -364,9 +353,8 @@ func TestGetSession(t *testing.T) { server := grpc.NewSessionServer(ctx, sessionRepo, trustRepo, 90*time.Minute, "") req := &sessionv1.GetSessionRequest{ - SessionId: "session-no-provider", - TenantId: "tenant-no-provider", - Fingerprint: "fingerprint-123", + SessionId: "session-no-provider", + TenantId: "tenant-no-provider", } resp, err := server.GetSession(ctx, req) @@ -378,10 +366,9 @@ func TestGetSession(t *testing.T) { t.Run("invalid - trust mapping is blocked", func(t *testing.T) { sess := session.Session{ - ID: "session-blocked", - TenantID: "tenant-blocked", - Fingerprint: "fingerprint-123", - Issuer: "https://issuer.example.com", + ID: "session-blocked", + TenantID: "tenant-blocked", + Issuer: "https://issuer.example.com", } sessionRepo := sessionmock.NewInMemRepository( @@ -400,9 +387,8 @@ func TestGetSession(t *testing.T) { server := grpc.NewSessionServer(ctx, sessionRepo, trustRepo, 90*time.Minute, "") req := &sessionv1.GetSessionRequest{ - SessionId: "session-blocked", - TenantId: "tenant-blocked", - Fingerprint: "fingerprint-123", + SessionId: "session-blocked", + TenantId: "tenant-blocked", } resp, err := server.GetSession(ctx, req) @@ -424,48 +410,11 @@ func TestGetSession(t *testing.T) { assert.Nil(t, resp) }) - t.Run("invalid - fingerprint mismatch", func(t *testing.T) { - sess := session.Session{ - ID: "session-fingerprint", - TenantID: "tenant-fingerprint", - Fingerprint: "correct-fingerprint", - Issuer: "https://issuer.example.com", - } - - sessionRepo := sessionmock.NewInMemRepository( - sessionmock.WithSession(sess), - ) - _ = sessionRepo.BumpActive(ctx, sess.ID, 1*time.Hour) - - mapping := trust.OIDCMapping{ - IssuerURL: "https://issuer.example.com", - Blocked: false, - } - trustRepo := trustmock.NewInMemRepository( - trustmock.WithTrust(sess.TenantID, mapping), - ) - - server := grpc.NewSessionServer(ctx, sessionRepo, trustRepo, 90*time.Minute, "") - - req := &sessionv1.GetSessionRequest{ - SessionId: "session-fingerprint", - TenantId: "tenant-fingerprint", - Fingerprint: "wrong-fingerprint", // Mismatch - } - - resp, err := server.GetSession(ctx, req) - - require.NoError(t, err) - assert.NotNil(t, resp) - assert.False(t, resp.GetValid()) - }) - t.Run("invalid - tenant ID mismatch", func(t *testing.T) { sess := session.Session{ - ID: "session-tenant", - TenantID: "correct-tenant", - Fingerprint: "fingerprint-123", - Issuer: "https://issuer.example.com", + ID: "session-tenant", + TenantID: "correct-tenant", + Issuer: "https://issuer.example.com", } sessionRepo := sessionmock.NewInMemRepository( @@ -484,9 +433,8 @@ func TestGetSession(t *testing.T) { server := grpc.NewSessionServer(ctx, sessionRepo, trustRepo, 90*time.Minute, "") req := &sessionv1.GetSessionRequest{ - SessionId: "session-tenant", - TenantId: "wrong-tenant", // Mismatch - Fingerprint: "fingerprint-123", + SessionId: "session-tenant", + TenantId: "wrong-tenant", // Mismatch } resp, err := server.GetSession(ctx, req) @@ -498,10 +446,9 @@ func TestGetSession(t *testing.T) { t.Run("error - GetOpenIDConfig fails", func(t *testing.T) { sess := session.Session{ - ID: "session-config-fail", - TenantID: "tenant-config-fail", - Fingerprint: "fingerprint-123", - Issuer: "https://invalid-issuer-no-server.example.com", + ID: "session-config-fail", + TenantID: "tenant-config-fail", + Issuer: "https://invalid-issuer-no-server.example.com", } sessionRepo := sessionmock.NewInMemRepository( @@ -520,9 +467,8 @@ func TestGetSession(t *testing.T) { server := grpc.NewSessionServer(ctx, sessionRepo, trustRepo, 90*time.Minute, "") req := &sessionv1.GetSessionRequest{ - SessionId: "session-config-fail", - TenantId: "tenant-config-fail", - Fingerprint: "fingerprint-123", + SessionId: "session-config-fail", + TenantId: "tenant-config-fail", } resp, err := server.GetSession(ctx, req) @@ -551,7 +497,6 @@ func TestGetSession(t *testing.T) { sess := session.Session{ ID: "session-introspect-fail", TenantID: "tenant-introspect-fail", - Fingerprint: "fingerprint-123", Issuer: testServer.URL, AccessToken: "access-token-123", } @@ -574,9 +519,8 @@ func TestGetSession(t *testing.T) { ) req := &sessionv1.GetSessionRequest{ - SessionId: "session-introspect-fail", - TenantId: "tenant-introspect-fail", - Fingerprint: "fingerprint-123", + SessionId: "session-introspect-fail", + TenantId: "tenant-introspect-fail", } resp, err := server.GetSession(ctx, req) @@ -607,7 +551,6 @@ func TestGetSession(t *testing.T) { sess := session.Session{ ID: "session-inactive-token", TenantID: "tenant-inactive-token", - Fingerprint: "fingerprint-123", Issuer: testServer.URL, AccessToken: "expired-token", } @@ -630,9 +573,8 @@ func TestGetSession(t *testing.T) { ) req := &sessionv1.GetSessionRequest{ - SessionId: "session-inactive-token", - TenantId: "tenant-inactive-token", - Fingerprint: "fingerprint-123", + SessionId: "session-inactive-token", + TenantId: "tenant-inactive-token", } resp, err := server.GetSession(ctx, req) @@ -654,10 +596,9 @@ func TestGetSession(t *testing.T) { defer testServer.Close() sess := session.Session{ - ID: "session-bump-fail", - TenantID: "tenant-bump-fail", - Fingerprint: "fingerprint-123", - Issuer: testServer.URL, + ID: "session-bump-fail", + TenantID: "tenant-bump-fail", + Issuer: testServer.URL, } sessionRepo := sessionmock.NewInMemRepository( @@ -679,9 +620,8 @@ func TestGetSession(t *testing.T) { ) req := &sessionv1.GetSessionRequest{ - SessionId: "session-bump-fail", - TenantId: "tenant-bump-fail", - Fingerprint: "fingerprint-123", + SessionId: "session-bump-fail", + TenantId: "tenant-bump-fail", } resp, err := server.GetSession(ctx, req) diff --git a/internal/openapi/server.go b/internal/openapi/server.go index 19cab573..af7f5fca 100644 --- a/internal/openapi/server.go +++ b/internal/openapi/server.go @@ -29,7 +29,7 @@ type AuthParams struct { // ErrorURI Base URL of the UI error page. When provided and an error occurs during the // auth flow (including the callback), the session manager redirects to this URL // with an appended errorCode query parameter instead of returning a JSON error body. - // Example: https://ui.cmk.sap/#/tenantID/forbidden + // Example: https://openkcm.com/#/tenantID/forbidden ErrorURI *string `form:"error_uri,omitempty" json:"error_uri,omitempty"` } @@ -510,15 +510,6 @@ func (response Callback302Response) VisitCallbackResponse(w http.ResponseWriter) return nil } -type Callback403JSONResponse ErrorModel - -func (response Callback403JSONResponse) VisitCallbackResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(403) - - return json.NewEncoder(w).Encode(response) -} - type CallbackdefaultJSONResponse struct { Body ErrorModel StatusCode int diff --git a/internal/serviceerr/errors.go b/internal/serviceerr/errors.go index c995770d..679438ef 100644 --- a/internal/serviceerr/errors.go +++ b/internal/serviceerr/errors.go @@ -33,7 +33,6 @@ const ( CodeUnknown Code = "unknown" CodeConflict Code = "conflict" CodeNotFound Code = "not_found" - CodeFingerprintMismatch Code = "fingerprint_mismatch" CodeStateExpired Code = "state_expired" CodeInvalidOIDCProvider Code = "invalid_oidc_provider" CodeInvalidCSRFToken Code = "invalid_csrf_token" @@ -68,7 +67,6 @@ var ( ErrUnknown = newErr("unknown error", CodeUnknown) ErrConflict = newErr("already exists", CodeConflict) ErrNotFound = newErr("not found", CodeNotFound) - ErrFingerprintMismatch = newErr("fingerprint mismatch", CodeFingerprintMismatch) ErrStateExpired = newErr("state expired", CodeStateExpired) ErrInvalidOIDCProvider = newErr("invalid OIDC provider", CodeInvalidOIDCProvider) ErrInvalidCSRFToken = newErr("invalid CSRF token", CodeInvalidCSRFToken) @@ -129,8 +127,6 @@ func (e Error) HTTPStatus() int { return http.StatusConflict case CodeNotFound: return http.StatusNotFound - case CodeFingerprintMismatch: - return http.StatusForbidden case CodeStateExpired: return http.StatusGone case CodeInvalidOIDCProvider: diff --git a/internal/serviceerr/errors_test.go b/internal/serviceerr/errors_test.go index 730360b3..1cd78650 100644 --- a/internal/serviceerr/errors_test.go +++ b/internal/serviceerr/errors_test.go @@ -126,11 +126,6 @@ func TestError_HTTPStatus(t *testing.T) { code: serviceerr.CodeNotFound, expectedHTTPStatus: http.StatusNotFound, }, - { - name: "CodeFingerprintMismatch returns Forbidden", - code: serviceerr.CodeFingerprintMismatch, - expectedHTTPStatus: http.StatusForbidden, - }, { name: "CodeStateExpired returns Gone", code: serviceerr.CodeStateExpired, @@ -197,7 +192,6 @@ func TestPredefinedErrors(t *testing.T) { {name: "ErrUnknown", err: serviceerr.ErrUnknown, expectedErr: serviceerr.CodeUnknown, hasDesc: true}, {name: "ErrConflict", err: serviceerr.ErrConflict, expectedErr: serviceerr.CodeConflict, hasDesc: true}, {name: "ErrNotFound", err: serviceerr.ErrNotFound, expectedErr: serviceerr.CodeNotFound, hasDesc: true}, - {name: "ErrFingerprintMismatch", err: serviceerr.ErrFingerprintMismatch, expectedErr: serviceerr.CodeFingerprintMismatch, hasDesc: true}, {name: "ErrStateExpired", err: serviceerr.ErrStateExpired, expectedErr: serviceerr.CodeStateExpired, hasDesc: true}, {name: "ErrInvalidOIDCProvider", err: serviceerr.ErrInvalidOIDCProvider, expectedErr: serviceerr.CodeInvalidOIDCProvider, hasDesc: true}, {name: "ErrInvalidCSRFToken", err: serviceerr.ErrInvalidCSRFToken, expectedErr: serviceerr.CodeInvalidCSRFToken, hasDesc: true}, @@ -246,7 +240,6 @@ func TestErrorCodes(t *testing.T) { {name: "CodeUnknown", code: serviceerr.CodeUnknown, expected: "unknown"}, {name: "CodeConflict", code: serviceerr.CodeConflict, expected: "conflict"}, {name: "CodeNotFound", code: serviceerr.CodeNotFound, expected: "not_found"}, - {name: "CodeFingerprintMismatch", code: serviceerr.CodeFingerprintMismatch, expected: "fingerprint_mismatch"}, {name: "CodeStateExpired", code: serviceerr.CodeStateExpired, expected: "state_expired"}, {name: "CodeInvalidOIDCProvider", code: serviceerr.CodeInvalidOIDCProvider, expected: "invalid_oidc_provider"}, {name: "CodeInvalidCSRFToken", code: serviceerr.CodeInvalidCSRFToken, expected: "invalid_csrf_token"}, diff --git a/internal/session/manager.go b/internal/session/manager.go index 2e1530b9..a7d1f1bd 100644 --- a/internal/session/manager.go +++ b/internal/session/manager.go @@ -120,7 +120,7 @@ func NewManager( } // MakeAuthURI returns an OIDC authentication URI. -func (m *Manager) MakeAuthURI(ctx context.Context, tenantID, fingerprint, requestURI, errorURI string) (string, string, error) { +func (m *Manager) MakeAuthURI(ctx context.Context, tenantID, requestURI, errorURI string) (string, string, error) { mapping, err := m.trustRepo.Get(ctx, tenantID) if err != nil { return "", "", fmt.Errorf("getting trust mapping: %w", err) @@ -142,7 +142,6 @@ func (m *Manager) MakeAuthURI(ctx context.Context, tenantID, fingerprint, reques state := State{ ID: stateID, TenantID: tenantID, - Fingerprint: fingerprint, PKCEVerifier: pkce.Verifier, RequestURI: requestURI, ErrorURI: errorURI, @@ -214,7 +213,7 @@ func (m *Manager) getProviderKeySet(ctx context.Context, oidcConf *oidc.Configur return &keySet, nil } -func (m *Manager) FinaliseOIDCLogin(ctx context.Context, stateID, code, fingerprint string) (OIDCSessionData, error) { +func (m *Manager) FinaliseOIDCLogin(ctx context.Context, stateID, code string) (OIDCSessionData, error) { state, err := m.sessions.LoadState(ctx, stateID) if err != nil { return OIDCSessionData{}, fmt.Errorf("loading state from the storage: %w", err) @@ -234,11 +233,6 @@ func (m *Manager) FinaliseOIDCLogin(ctx context.Context, stateID, code, fingerpr return OIDCSessionData{}, serviceerr.ErrStateExpired } - if state.Fingerprint != fingerprint { - m.sendUserLoginFailureAudit(ctx, metadata, state.TenantID, "fingerprint mismatch") - return OIDCSessionData{}, serviceerr.ErrFingerprintMismatch - } - mapping, err := m.trustRepo.Get(ctx, state.TenantID) if err != nil { m.sendUserLoginFailureAudit(ctx, metadata, state.TenantID, "failed to get trust mapping") @@ -261,82 +255,11 @@ func (m *Manager) FinaliseOIDCLogin(ctx context.Context, stateID, code, fingerpr sessionID := m.pkce.SessionID() csrfToken := csrf.NewToken(sessionID, m.csrfSecret) - algs := make([]jose.SignatureAlgorithm, 0, len(openidConf.IDTokenSigningAlgValuesSupported)) - for _, alg := range openidConf.IDTokenSigningAlgValuesSupported { - algs = append(algs, jose.SignatureAlgorithm(alg)) - } - token, err := jwt.ParseSigned(tokens.IDToken, algs) - if err != nil { - m.sendUserLoginFailureAudit(ctx, metadata, state.TenantID, "failed to parse id token") - return OIDCSessionData{}, fmt.Errorf("parsing id token: %w, %s", err, algs) - } - - keyset, err := m.getProviderKeySet(ctx, openidConf) - if err != nil { - m.sendUserLoginFailureAudit(ctx, metadata, state.TenantID, "failed to get jwks for provider") - return OIDCSessionData{}, fmt.Errorf("getting jwks for a provider: %w", err) - } - type CustomClaims struct { - SID string `json:"sid"` - UserUUID string `json:"user_uuid"` - GivenName string `json:"given_name"` - FamilyName string `json:"family_name"` - Email string `json:"email"` - Groups []string `json:"groups"` - } - - type ExtraClaims struct { - AtHash string `json:"at_hash,omitempty"` - } - - var standardClaims jwt.Claims - var customClaims CustomClaims - var extraClaims ExtraClaims - err = token.Claims(keyset, &standardClaims, &customClaims, &extraClaims) + session, err := m.buildSessionFromTokens(ctx, openidConf, tokens, mapping, sessionID, csrfToken, state.TenantID) if err != nil { - m.sendUserLoginFailureAudit(ctx, metadata, state.TenantID, "failed to get JWT claims") - return OIDCSessionData{}, fmt.Errorf("getting JWT claims: %w", err) - } - - if extraClaims.AtHash != "" { - err := m.verifyAccessToken(tokens.AccessToken, extraClaims.AtHash, token) - if err != nil { - return OIDCSessionData{}, err - } - } - - // prepare the auth context used by ExtAuthZ - authContext := map[string]string{ - "issuer": mapping.IssuerURL, - "client_id": m.getClientID(mapping), - } - for _, parameter := range m.authContextKeys { - value, ok := mapping.Properties[parameter] - if ok { - authContext[parameter] = value - } - } - - session := Session{ - ID: sessionID, - TenantID: state.TenantID, - ProviderID: customClaims.SID, - Fingerprint: fingerprint, - CSRFToken: csrfToken, - Issuer: mapping.IssuerURL, - Claims: Claims{ - Subject: standardClaims.Subject, - UserUUID: customClaims.UserUUID, - GivenName: customClaims.GivenName, - FamilyName: customClaims.FamilyName, - Email: customClaims.Email, - Groups: customClaims.Groups, - }, - AccessToken: tokens.AccessToken, - RefreshToken: tokens.RefreshToken, - Expiry: time.Now().Add(m.sessionDuration), - AuthContext: authContext, + m.sendUserLoginFailureAudit(ctx, metadata, state.TenantID, err.Error()) + return OIDCSessionData{}, err } err = m.sessions.StoreSession(ctx, session) @@ -377,6 +300,86 @@ func (m *Manager) FinaliseOIDCLogin(ctx context.Context, stateID, code, fingerpr }, nil } +// buildSessionFromTokens parses the ID token, verifies claims, and constructs a Session object. +func (m *Manager) buildSessionFromTokens(ctx context.Context, openidConf *oidc.Configuration, tokens tokenResponse, mapping trust.OIDCMapping, sessionID, csrfToken, tenantID string) (Session, error) { + algs := make([]jose.SignatureAlgorithm, 0, len(openidConf.IDTokenSigningAlgValuesSupported)) + for _, alg := range openidConf.IDTokenSigningAlgValuesSupported { + algs = append(algs, jose.SignatureAlgorithm(alg)) + } + token, err := jwt.ParseSigned(tokens.IDToken, algs) + if err != nil { + return Session{}, fmt.Errorf("parsing id token: %w, %s", err, algs) + } + + keyset, err := m.getProviderKeySet(ctx, openidConf) + if err != nil { + return Session{}, fmt.Errorf("getting jwks for a provider: %w", err) + } + + type customClaims struct { + SID string `json:"sid"` + UserUUID string `json:"user_uuid"` + GivenName string `json:"given_name"` + FamilyName string `json:"family_name"` + Email string `json:"email"` + Groups []string `json:"groups"` + } + + type extraClaims struct { + AtHash string `json:"at_hash,omitempty"` + } + + var standardClaims jwt.Claims + var custom customClaims + var extra extraClaims + if err = token.Claims(keyset, &standardClaims, &custom, &extra); err != nil { + return Session{}, fmt.Errorf("getting JWT claims: %w", err) + } + + if extra.AtHash != "" { + if err := m.verifyAccessToken(tokens.AccessToken, extra.AtHash, token); err != nil { + return Session{}, err + } + } + + authContext := m.buildAuthContext(mapping) + + return Session{ + ID: sessionID, + TenantID: tenantID, + ProviderID: custom.SID, + CSRFToken: csrfToken, + Issuer: mapping.IssuerURL, + Claims: Claims{ + Subject: standardClaims.Subject, + UserUUID: custom.UserUUID, + GivenName: custom.GivenName, + FamilyName: custom.FamilyName, + Email: custom.Email, + Groups: custom.Groups, + }, + AccessToken: tokens.AccessToken, + RefreshToken: tokens.RefreshToken, + Expiry: time.Now().Add(m.sessionDuration), + AuthContext: authContext, + }, nil +} + +// buildAuthContext prepares the authentication context map used by ExtAuthZ. +func (m *Manager) buildAuthContext(mapping trust.OIDCMapping) map[string]string { + authContext := map[string]string{ + "issuer": mapping.IssuerURL, + "client_id": m.getClientID(mapping), + } + for _, parameter := range m.authContextKeys { + value, ok := mapping.Properties[parameter] + if ok { + authContext[parameter] = value + } + } + return authContext +} + func (m *Manager) Logout(ctx context.Context, sessionID, postLogoutRedirectURL string) (string, error) { session, err := m.sessions.LoadSession(ctx, sessionID) if err != nil { diff --git a/internal/session/manager_test.go b/internal/session/manager_test.go index 171d1da6..7939696d 100644 --- a/internal/session/manager_test.go +++ b/internal/session/manager_test.go @@ -61,16 +61,15 @@ func TestManager_Auth(t *testing.T) { } tests := []struct { - name string - oidc *trustmock.Repository - sessions *sessionmock.Repository - requestURI string - cfg *config.SessionManager - tenantID string - fingerprint string - wantURL string - errAssert assert.ErrorAssertionFunc - mapping trust.OIDCMapping + name string + oidc *trustmock.Repository + sessions *sessionmock.Repository + requestURI string + cfg *config.SessionManager + tenantID string + wantURL string + errAssert assert.ErrorAssertionFunc + mapping trust.OIDCMapping }{ { name: "Success", @@ -87,10 +86,9 @@ func TestManager_Auth(t *testing.T) { }, CSRFSecretParsed: []byte(testCSRFSecret), }, - tenantID: tenantID, - fingerprint: "fingerprint", - wantURL: oidcServer.URL + "/oauth2/authorize?client_id=my-client-id&code_challenge=someChallenge&code_challenge_method=S256&redirect_uri=" + callbackURL + "&response_type=code&scope=openid+profile+email+groups&state=someState¶mAuth1=paramAuth1", - errAssert: assert.NoError, + tenantID: tenantID, + wantURL: oidcServer.URL + "/oauth2/authorize?client_id=my-client-id&code_challenge=someChallenge&code_challenge_method=S256&redirect_uri=" + callbackURL + "&response_type=code&scope=openid+profile+email+groups&state=someState¶mAuth1=paramAuth1", + errAssert: assert.NoError, }, { name: "Get trust mapping error", @@ -105,10 +103,9 @@ func TestManager_Auth(t *testing.T) { CallbackURL: callbackURL, CSRFSecretParsed: []byte(testCSRFSecret), }, - tenantID: tenantID, - fingerprint: "fingerprint", - wantURL: "", - errAssert: assert.Error, + tenantID: tenantID, + wantURL: "", + errAssert: assert.Error, }, { name: "Save state error", @@ -120,10 +117,9 @@ func TestManager_Auth(t *testing.T) { CallbackURL: callbackURL, CSRFSecretParsed: []byte(testCSRFSecret), }, - tenantID: tenantID, - fingerprint: "fingerprint", - wantURL: "", - errAssert: assert.Error, + tenantID: tenantID, + wantURL: "", + errAssert: assert.Error, }, } for _, tt := range tests { @@ -150,7 +146,7 @@ func TestManager_Auth(t *testing.T) { session.WithAllowHttpScheme(true), ) require.NoError(t, err) - got, _, err := m.MakeAuthURI(t.Context(), tt.tenantID, tt.fingerprint, tt.requestURI, "") + got, _, err := m.MakeAuthURI(t.Context(), tt.tenantID, tt.requestURI, "") if !tt.errAssert(t, err, fmt.Sprintf("Manager.Auth() error = %v", err)) || err != nil { return @@ -200,14 +196,12 @@ func TestManager_FinaliseOIDCLogin(t *testing.T) { tenantID = "tenant-id" stateID = "test-state-id" code = "auth-code" - fingerprint = "test-fingerprint" pkceVerifier = "test-verifier" ) validState := session.State{ ID: stateID, TenantID: tenantID, - Fingerprint: fingerprint, PKCEVerifier: pkceVerifier, RequestURI: requestURI, Expiry: time.Now().Add(time.Hour), @@ -216,28 +210,17 @@ func TestManager_FinaliseOIDCLogin(t *testing.T) { expiredState := session.State{ ID: stateID, TenantID: tenantID, - Fingerprint: fingerprint, PKCEVerifier: pkceVerifier, RequestURI: requestURI, Expiry: time.Now().Add(-time.Hour), } - mismatchState := session.State{ - ID: stateID, - TenantID: tenantID, - Fingerprint: "different-fingerprint", - PKCEVerifier: pkceVerifier, - RequestURI: requestURI, - Expiry: time.Now().Add(time.Hour), - } - tests := []struct { name string oidc *trustmock.Repository sessions *sessionmock.Repository stateID string code string - fingerprint string cfg *config.SessionManager oidcServerFail bool wantSessionID bool @@ -246,12 +229,11 @@ func TestManager_FinaliseOIDCLogin(t *testing.T) { errAssert assert.ErrorAssertionFunc }{ { - name: "Success", - oidc: trustmock.NewInMemRepository(), - sessions: sessionmock.NewInMemRepository(sessionmock.WithState(validState)), - stateID: stateID, - code: code, - fingerprint: fingerprint, + name: "Success", + oidc: trustmock.NewInMemRepository(), + sessions: sessionmock.NewInMemRepository(sessionmock.WithState(validState)), + stateID: stateID, + code: code, cfg: &config.SessionManager{ SessionDuration: time.Hour, CallbackURL: callbackURL, @@ -265,12 +247,11 @@ func TestManager_FinaliseOIDCLogin(t *testing.T) { errAssert: assert.NoError, }, { - name: "State load error", - oidc: trustmock.NewInMemRepository(), - sessions: sessionmock.NewInMemRepository(sessionmock.WithLoadStateError(errors.New("state not found"))), - stateID: stateID, - code: code, - fingerprint: fingerprint, + name: "State load error", + oidc: trustmock.NewInMemRepository(), + sessions: sessionmock.NewInMemRepository(sessionmock.WithLoadStateError(errors.New("state not found"))), + stateID: stateID, + code: code, cfg: &config.SessionManager{ SessionDuration: time.Hour, CSRFSecretParsed: []byte(testCSRFSecret), @@ -281,27 +262,11 @@ func TestManager_FinaliseOIDCLogin(t *testing.T) { errAssert: assert.Error, }, { - name: "State expired", - oidc: trustmock.NewInMemRepository(), - sessions: sessionmock.NewInMemRepository(sessionmock.WithState(expiredState)), - stateID: stateID, - code: code, - fingerprint: fingerprint, - cfg: &config.SessionManager{ - CSRFSecretParsed: []byte(testCSRFSecret), - }, - wantSessionID: false, - wantCSRFToken: false, - wantRedirectURI: "", - errAssert: assert.Error, - }, - { - name: "Fingerprint mismatch", - oidc: trustmock.NewInMemRepository(), - sessions: sessionmock.NewInMemRepository(sessionmock.WithState(mismatchState)), - stateID: stateID, - code: code, - fingerprint: fingerprint, + name: "State expired", + oidc: trustmock.NewInMemRepository(), + sessions: sessionmock.NewInMemRepository(sessionmock.WithState(expiredState)), + stateID: stateID, + code: code, cfg: &config.SessionManager{ CSRFSecretParsed: []byte(testCSRFSecret), }, @@ -311,12 +276,11 @@ func TestManager_FinaliseOIDCLogin(t *testing.T) { errAssert: assert.Error, }, { - name: "Trust mapping get error", - oidc: trustmock.NewInMemRepository(trustmock.WithGetError(errors.New("trust mapping not found"))), - sessions: sessionmock.NewInMemRepository(sessionmock.WithState(validState)), - stateID: stateID, - code: code, - fingerprint: fingerprint, + name: "Trust mapping get error", + oidc: trustmock.NewInMemRepository(trustmock.WithGetError(errors.New("trust mapping not found"))), + sessions: sessionmock.NewInMemRepository(sessionmock.WithState(validState)), + stateID: stateID, + code: code, cfg: &config.SessionManager{ CSRFSecretParsed: []byte(testCSRFSecret), }, @@ -326,12 +290,11 @@ func TestManager_FinaliseOIDCLogin(t *testing.T) { errAssert: assert.Error, }, { - name: "Token exchange error", - oidc: trustmock.NewInMemRepository(), - sessions: sessionmock.NewInMemRepository(sessionmock.WithState(validState)), - stateID: stateID, - code: code, - fingerprint: fingerprint, + name: "Token exchange error", + oidc: trustmock.NewInMemRepository(), + sessions: sessionmock.NewInMemRepository(sessionmock.WithState(validState)), + stateID: stateID, + code: code, cfg: &config.SessionManager{ CSRFSecretParsed: []byte(testCSRFSecret), }, @@ -380,7 +343,7 @@ func TestManager_FinaliseOIDCLogin(t *testing.T) { ) require.NoError(t, err) - result, err := m.FinaliseOIDCLogin(context.Background(), tt.stateID, tt.code, tt.fingerprint) + result, err := m.FinaliseOIDCLogin(context.Background(), tt.stateID, tt.code) if !tt.errAssert(t, err, fmt.Sprintf("Manager.Callback() error = %v", err)) { return diff --git a/internal/session/model.go b/internal/session/model.go index 2b333982..69f41452 100644 --- a/internal/session/model.go +++ b/internal/session/model.go @@ -8,7 +8,6 @@ import "time" type State struct { ID string // State ID to align the auth request with the callback TenantID string // Tenant ID for which the login is done - Fingerprint string // Fingerprint to bind the login to a specific client PKCEVerifier string // PKCE verifier to validate the PKCE challenge RequestURI string // Request URI for the eventual redirect ErrorURI string // Error URI for redirecting to UI error page on failure (optional) @@ -21,7 +20,6 @@ type Session struct { ID string // Session ID in our system TenantID string // Tenant ID for which the session is created ProviderID string // Provider session ID defined by the OIDC provider (`sid` claim) - Fingerprint string // Fingerprint to bind the session to a specific client CSRFToken string // CSRF token to prevent CSRF attacks Issuer string // Issuer of the OIDC tokens Claims Claims // Claims from the ID token diff --git a/internal/session/valkey/repository_test.go b/internal/session/valkey/repository_test.go index 647a4cc7..40aaae2e 100644 --- a/internal/session/valkey/repository_test.go +++ b/internal/session/valkey/repository_test.go @@ -66,7 +66,6 @@ func TestRepository_LoadState(t *testing.T) { prepareState(t, prefix, session.State{ ID: "stateid-one", TenantID: "tenant1-id", - Fingerprint: "fingerprint-one", PKCEVerifier: "verifier-one", RequestURI: "http://localhost", Expiry: testTime, @@ -86,7 +85,6 @@ func TestRepository_LoadState(t *testing.T) { wantState: session.State{ ID: "stateid-one", TenantID: "tenant1-id", - Fingerprint: "fingerprint-one", PKCEVerifier: "verifier-one", RequestURI: "http://localhost", Expiry: testTime, @@ -121,7 +119,6 @@ func TestRepository_StoreState(t *testing.T) { upsertState := session.State{ ID: "stateid-to-upsert", TenantID: upsertTenantID, - Fingerprint: "fingerprint-upsert", PKCEVerifier: "verifier", RequestURI: "example.com", Expiry: testTime, @@ -141,7 +138,6 @@ func TestRepository_StoreState(t *testing.T) { state: session.State{ ID: "state-id-store-success", TenantID: "tenant-id-store-success", - Fingerprint: "fingerprint", PKCEVerifier: "verifier", RequestURI: "http://example.com", Expiry: testTime, @@ -154,7 +150,6 @@ func TestRepository_StoreState(t *testing.T) { state: session.State{ ID: upsertState.ID, TenantID: upsertState.TenantID, - Fingerprint: "fingerprint-upsert", PKCEVerifier: "verifier-upsert", RequestURI: "upsert.example.com", Expiry: testTime, @@ -184,7 +179,6 @@ func TestRepository_LoadSession(t *testing.T) { prepareSession(t, prefix, session.Session{ ID: "sessionid-one", TenantID: "tenant1-id", - Fingerprint: "fingerprint-one", AccessToken: "access-token-one", RefreshToken: "refresh-token-one", Expiry: testTime, @@ -205,7 +199,6 @@ func TestRepository_LoadSession(t *testing.T) { wantSession: session.Session{ ID: "sessionid-one", TenantID: "tenant1-id", - Fingerprint: "fingerprint-one", AccessToken: "access-token-one", RefreshToken: "refresh-token-one", Expiry: testTime, @@ -241,7 +234,6 @@ func TestRepository_StoreSession_Success(t *testing.T) { upsertSession := session.Session{ ID: "sessionid-to-upsert", TenantID: upsertTenantID, - Fingerprint: "fingerprint-upsert", AccessToken: "access-token-upsert", RefreshToken: "refresh-token-upsert", Expiry: testTime, @@ -262,7 +254,6 @@ func TestRepository_StoreSession_Success(t *testing.T) { session: session.Session{ ID: "sessionid-id-store-session-success", TenantID: "tenant-id-store-session-success", - Fingerprint: "fingerprint-one", AccessToken: "access-token-one", RefreshToken: "refresh-token-one", AccessTokenExpiry: testTime, @@ -275,7 +266,6 @@ func TestRepository_StoreSession_Success(t *testing.T) { session: session.Session{ ID: upsertSession.ID, TenantID: upsertSession.TenantID, - Fingerprint: "fingerprint-upsert-new", AccessToken: "access-token-upsert-new", RefreshToken: "refresh-token-upsert-new", AccessTokenExpiry: testTime, @@ -327,7 +317,6 @@ func TestRepository_StoreSession_Fail(t *testing.T) { session: session.Session{ ID: "sessionid-id-store-session-successful-cleanup", TenantID: "tenant-id-store-session-successful-cleanup", - Fingerprint: "fingerprint-upsert-new", AccessToken: "access-token-upsert-new", RefreshToken: "refresh-token-upsert-new", Expiry: time.Now().Add(-100 * time.Second).UTC(), @@ -359,7 +348,6 @@ func TestRepository_ListSessions(t *testing.T) { prepareSession(t, prefix, session.Session{ ID: "sessionid-one", TenantID: "tenant1-id", - Fingerprint: "fingerprint-one", AccessToken: "access-token-one", RefreshToken: "refresh-token-one", Expiry: testTime, @@ -368,7 +356,6 @@ func TestRepository_ListSessions(t *testing.T) { prepareSession(t, prefix, session.Session{ ID: "sessionid-two", TenantID: "tenant2-id", - Fingerprint: "fingerprint-two", AccessToken: "access-token-two", RefreshToken: "refresh-token-two", Expiry: testTime, @@ -377,7 +364,6 @@ func TestRepository_ListSessions(t *testing.T) { prepareSession(t, prefix, session.Session{ ID: "sessionid-three", TenantID: "tenant3-id", - Fingerprint: "fingerprint-three", AccessToken: "access-token-three", RefreshToken: "refresh-token-three", Expiry: testTime, @@ -397,7 +383,6 @@ func TestRepository_ListSessions(t *testing.T) { { ID: "sessionid-one", TenantID: "tenant1-id", - Fingerprint: "fingerprint-one", AccessToken: "access-token-one", RefreshToken: "refresh-token-one", Expiry: testTime, @@ -406,7 +391,6 @@ func TestRepository_ListSessions(t *testing.T) { { ID: "sessionid-two", TenantID: "tenant2-id", - Fingerprint: "fingerprint-two", AccessToken: "access-token-two", RefreshToken: "refresh-token-two", Expiry: testTime, @@ -415,7 +399,6 @@ func TestRepository_ListSessions(t *testing.T) { { ID: "sessionid-three", TenantID: "tenant3-id", - Fingerprint: "fingerprint-three", AccessToken: "access-token-three", RefreshToken: "refresh-token-three", Expiry: testTime, @@ -448,10 +431,9 @@ func TestRepository_DeleteState(t *testing.T) { const prefix = "session-manager-delete-state-test" state := session.State{ - ID: stateID, - TenantID: tenantID, - Fingerprint: "fingerprint-delete", - Expiry: testTime, + ID: stateID, + TenantID: tenantID, + Expiry: testTime, } prepareState(t, prefix, state)