diff --git a/lib/services/identity.go b/lib/services/identity.go index a152020e2787..af38d53babbc 100644 --- a/lib/services/identity.go +++ b/lib/services/identity.go @@ -226,6 +226,9 @@ type Identity interface { // GetOIDCAuthRequest returns OIDC auth request if found GetOIDCAuthRequest(ctx context.Context, stateToken string) (*types.OIDCAuthRequest, error) + // DeleteOIDCAuthRequest deletes OIDC auth request + DeleteOIDCAuthRequest(ctx context.Context, stateToken string) error + // CreateSAMLConnector creates a new SAML connector. CreateSAMLConnector(ctx context.Context, connector types.SAMLConnector) (types.SAMLConnector, error) // UpdateSAMLConnector updates an existing SAML connector diff --git a/lib/services/local/users.go b/lib/services/local/users.go index 3c6fad42fb18..6e4228cba4c6 100644 --- a/lib/services/local/users.go +++ b/lib/services/local/users.go @@ -1715,6 +1715,14 @@ func (s *IdentityService) GetOIDCAuthRequest(ctx context.Context, stateToken str return req, nil } +// DeleteOIDCAuthRequest deletes OIDC auth request +func (s *IdentityService) DeleteOIDCAuthRequest(ctx context.Context, stateToken string) error { + if stateToken == "" { + return trace.BadParameter("missing parameter stateToken") + } + return trace.Wrap(s.Delete(ctx, backend.NewKey(webPrefix, connectorsPrefix, oidcPrefix, requestsPrefix, stateToken))) +} + // UpsertSAMLConnector upserts SAML Connector func (s *IdentityService) UpsertSAMLConnector(ctx context.Context, connector types.SAMLConnector) (types.SAMLConnector, error) { if err := services.ValidateSAMLConnector(connector, nil); err != nil { diff --git a/lib/services/local/users_test.go b/lib/services/local/users_test.go index 8097a9c05500..f673bc6ff2f8 100644 --- a/lib/services/local/users_test.go +++ b/lib/services/local/users_test.go @@ -1785,3 +1785,70 @@ func TestIdentityService_SSOMFASessionDataCRUD(t *testing.T) { _, err = identity.GetMFASessionData(ctx, sd.RequestID) require.True(t, trace.IsNotFound(err)) } + +func TestIdentityService_OIDCAuthRequestCRUD(t *testing.T) { + t.Parallel() + ctx := context.Background() + clock := clockwork.NewFakeClock() + identity := newIdentityService(t, clock) + + req := types.OIDCAuthRequest{ + ConnectorID: "google", + StateToken: "test-state-token", + CSRFToken: "test-csrf-token", + } + + // Verify create. + err := identity.CreateOIDCAuthRequest(ctx, req, time.Minute) + require.NoError(t, err) + + // Verify read. + got, err := identity.GetOIDCAuthRequest(ctx, req.StateToken) + require.NoError(t, err) + require.Equal(t, req.ConnectorID, got.ConnectorID) + require.Equal(t, req.StateToken, got.StateToken) + + // Verify delete. + err = identity.DeleteOIDCAuthRequest(ctx, req.StateToken) + require.NoError(t, err) + + // Verify get-after-delete returns NotFound. + _, err = identity.GetOIDCAuthRequest(ctx, req.StateToken) + require.True(t, trace.IsNotFound(err)) + + // Deleting again should also return NotFound. + err = identity.DeleteOIDCAuthRequest(ctx, req.StateToken) + require.True(t, trace.IsNotFound(err)) + + // Deleting or getting with an empty state token should be a BadParameter. + err = identity.DeleteOIDCAuthRequest(ctx, "") + require.True(t, trace.IsBadParameter(err)) +} + +func TestIdentityService_OIDCAuthRequestTTLExpiry(t *testing.T) { + t.Parallel() + ctx := context.Background() + clock := clockwork.NewFakeClock() + identity := newIdentityService(t, clock) + + req := types.OIDCAuthRequest{ + ConnectorID: "google", + StateToken: "expiring-state-token", + CSRFToken: "test-csrf-token", + } + + ttl := time.Minute + err := identity.CreateOIDCAuthRequest(ctx, req, ttl) + require.NoError(t, err) + + // Still readable before TTL elapses. + _, err = identity.GetOIDCAuthRequest(ctx, req.StateToken) + require.NoError(t, err) + + // Advance the clock past the TTL; the request should expire on its own, + // independent of any explicit delete. + clock.Advance(ttl + time.Second) + + _, err = identity.GetOIDCAuthRequest(ctx, req.StateToken) + require.True(t, trace.IsNotFound(err)) +}