Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
219 commits
Select commit Hold shift + click to select a range
0e9aa15
Initial support for Shared Signals Framework
thomasdarimont Oct 20, 2025
cc0e8b3
Next iteration of SSF support
thomasdarimont Nov 4, 2025
9a66abd
WIP Next iteration of SSF support
thomasdarimont Nov 7, 2025
7d2983c
WIP Next iteration of SSF support
thomasdarimont Nov 9, 2025
e7fca99
WIP Refactoring
thomasdarimont Nov 9, 2025
657afd1
WIP Refactoring
thomasdarimont Nov 9, 2025
b243592
Make spotless happy
thomasdarimont Nov 24, 2025
aae676b
Revise SSF Receiver support for review
thomasdarimont Nov 27, 2025
9b5f166
Make it more explicit that this is only about SSF Receivers
thomasdarimont Nov 27, 2025
0178abe
Use password controls for push auth header and transmitter access token
thomasdarimont Jan 30, 2026
c651d55
Add HttpServerUtil helper to ease return HttpServer responses
thomasdarimont Jan 31, 2026
892fac3
Refactor SSF Receiver support
thomasdarimont Jan 31, 2026
40c3e19
Add initial SsfReceiverTests
thomasdarimont Jan 31, 2026
0fddb46
Revise DefaultSsfEventListener
thomasdarimont Feb 1, 2026
adcd026
Next iteration of SSF Receiver support
thomasdarimont Feb 12, 2026
3fe1998
Revise SSF Receiver Provider configuration UI
thomasdarimont Feb 12, 2026
e7bcbed
Split SSF Receiver Provider configuration UI in general / stream sett…
thomasdarimont Feb 12, 2026
89d9eb4
Split SSF Receiver Provider configuration UI in general / stream sett…
thomasdarimont Feb 12, 2026
ed0bc6f
Add Trigger Verification to SSF Receiver Provider toolbar
thomasdarimont Feb 12, 2026
94a4ce3
Polishing
thomasdarimont Feb 12, 2026
a5e8653
Fix code review issues
thomasdarimont Feb 12, 2026
82a400a
Fix code review issues
thomasdarimont Feb 12, 2026
a79736b
Fix code review issues
thomasdarimont Feb 12, 2026
0a0bf16
Fix code review issues
thomasdarimont Feb 12, 2026
3595a36
Fix code review issues
thomasdarimont Feb 12, 2026
5efdbd3
Revise transmitterMetadataUrlHelp
thomasdarimont Feb 12, 2026
28945e0
Add client authentication support to SSF Receiver
thomasdarimont Feb 18, 2026
d332597
Revise DefaultSsfEventListener
thomasdarimont Feb 18, 2026
9c73025
Allow idp lookups by SubjectUserLookup
thomasdarimont Feb 18, 2026
ffb34cf
Make SsfAdminRealmResourceProviderFactory environment dependent
thomasdarimont Feb 19, 2026
c661207
Initial support for SSF Transmitters
thomasdarimont Mar 9, 2026
eafe9e3
SSF Refactoring
thomasdarimont Mar 17, 2026
088a8d9
SSF Refactoring
thomasdarimont Mar 17, 2026
a99d202
Add support for transmitting SSF Events to multiple streams
thomasdarimont Mar 17, 2026
30352f2
Add support for async push delivery
thomasdarimont Mar 17, 2026
37ab2fa
SSF: Ensure to use the provided push authorization header as-is
thomasdarimont Apr 9, 2026
2d5ef98
SSF:Avoid using the legacy SSE CAEP events profile by default
thomasdarimont Apr 9, 2026
f8334be
SSF: Add admin UI for SSF Receiver capability and Transmitter toggle
thomasdarimont Apr 13, 2026
d60cc7b
SSF: Add dedicated SSF tab, events SPI, stream admin API
thomasdarimont Apr 13, 2026
cfdc808
SSF: Source push endpoint timeouts from the server config
thomasdarimont Apr 13, 2026
513b5ff
SSF: Ssf Cleanup
thomasdarimont Apr 13, 2026
51da3e7
SSF: Run spotless
thomasdarimont Apr 13, 2026
f3d35d1
SSF: Make transmitter defaults configurable via SPI
thomasdarimont Apr 13, 2026
81c033d
SSF: Add initial SSF transmitter metadata tests
thomasdarimont Apr 13, 2026
b724c9c
SSF: Add initial SSF transmitter stream management tests
thomasdarimont Apr 13, 2026
d5515b7
SSF: Add stream verification tests and fix async push delivery
thomasdarimont Apr 13, 2026
03a5ac4
SSF: Add push delivery tests and fix stream status persistence
thomasdarimont Apr 13, 2026
cb58d1c
SSF: Add SSF Transmitter push failure tests
thomasdarimont Apr 13, 2026
930778c
SSF: Add SSF Transmitter tests for custom events
thomasdarimont Apr 13, 2026
8696479
SSF: Rename findAllEnabledStreams to findStreamsForSsfReceiverClients
thomasdarimont Apr 13, 2026
b2b28e7
SSF: Filter default supported events to emittable set and expose as a…
thomasdarimont Apr 13, 2026
220e515
SSF: Make Ssf.events()/transmitter()/receiver() and SET deserializati…
thomasdarimont Apr 13, 2026
9d8fecb
SSF: Align SET signing and verification with CAEP interop profile §2.6
thomasdarimont Apr 13, 2026
de8e402
SSF: Add configurable user subject format for outgoing SETs
thomasdarimont Apr 13, 2026
6eb5c49
SSF: Revise SSF Receiver help message
thomasdarimont Apr 13, 2026
bb9b598
SSF: Fix build issue in IdentityProviderSsfReceiver.tsx
thomasdarimont Apr 13, 2026
2a183ba
SSF: Move ssf classes from org.keycloak.protocol.ssf to org.keycloak.ssf
thomasdarimont Apr 14, 2026
25c78c8
SSF: Split-up SSF support into dedicated modules
thomasdarimont Apr 15, 2026
e4d944f
SSF: Add stream description, createdAt, updatedAt to stream section i…
thomasdarimont Apr 15, 2026
4b14037
SSF: Add dedicated representations for StreamConfigInput and StreamCo…
thomasdarimont Apr 15, 2026
f0ebc3e
SSF: Enforce limits for SSF configuration request fields
thomasdarimont Apr 15, 2026
94d6b17
SSF: Store SSF Receiver / Stream configuration as client attributes
thomasdarimont Apr 15, 2026
1a74a80
SSF: Explicitly pass KeycloakSession to SsfTransmitter resources
thomasdarimont Apr 15, 2026
7858bcd
SSF: Return stream status vua SsfAdminResource
thomasdarimont Apr 15, 2026
969f182
SSF: Ensure compatibility with SSE CAEP Profile for Apple Business Ma…
thomasdarimont Apr 15, 2026
5d44596
SSF: Add support for sending unsolicited steam verification events fr…
thomasdarimont Apr 15, 2026
b9d3232
SSF: Record the timestamp of the last verification request
thomasdarimont Apr 15, 2026
5b0ed48
SSF: Revise SecurityEventTokenDispatcher
thomasdarimont Apr 15, 2026
d8b10c2
SSF: Add support for creating streams directly from the admin ui
thomasdarimont Apr 15, 2026
fbc388b
SSF: Optimize SET creation in SsfTransmitterEventListener
thomasdarimont Apr 15, 2026
3f1794e
SSF: Spotless
thomasdarimont Apr 15, 2026
3bbaf17
SSF: Send out verification events synchronously if possible
thomasdarimont Apr 16, 2026
c92869e
SSF: Mark SsfTransmitterEventListener as global
thomasdarimont Apr 16, 2026
a85ef2f
SSF: Revise SsfTransmitterEventListener
thomasdarimont Apr 16, 2026
82711b3
SSF: Revise SSF Transmitter endpoint authentication
thomasdarimont Apr 16, 2026
8e6a9f4
SSF: Remove support for using Keycloak as SSF Receiver
thomasdarimont Apr 16, 2026
d555e07
SSF: Remove unused logger from DefaultSsfTransmitterProvider
thomasdarimont Apr 16, 2026
ac7972c
SSF: PoC Transactional Outbox
thomasdarimont Apr 16, 2026
7d714f1
SSF: Refactor Transmitter and SsfOutboxStore
thomasdarimont Apr 16, 2026
f0545c5
SSF: Add tests for SsfOutboxStore
thomasdarimont Apr 16, 2026
a3e969b
SSF: Refactoring
thomasdarimont Apr 16, 2026
e4fa8ed
SSF: Spotless
thomasdarimont Apr 16, 2026
59fd6ef
SSF: Revise SsfTransmitterMetadataWellKnownProvider infrastructure
thomasdarimont Apr 16, 2026
faaaad7
SSF: Spotless
thomasdarimont Apr 16, 2026
596af25
SSF: Make default subjects configurable for transmitter
thomasdarimont Apr 16, 2026
1ea1676
SSF: Add transactional outbox, subject management, and admin UI
thomasdarimont Apr 17, 2026
32b3478
SSF: Rename URL helper methods and fix test imports after package move
thomasdarimont Apr 17, 2026
76b8736
Fix root pom.xml after merge conflict
thomasdarimont Apr 17, 2026
e2e6541
SSF: Add OpenAPI annotations to SSF endpoints
thomasdarimont Apr 17, 2026
c163251
SSF: Synthetic event emitter, nested receiver UI, factory-based event…
thomasdarimont Apr 17, 2026
cb35db1
SSF: Make outbox table partition-friendly
thomasdarimont Apr 17, 2026
0fe2abb
SSF: HTTP POLL delivery (RFC 8936), sse-caep gate, hide kc_* from wire
thomasdarimont Apr 18, 2026
94e5a20
SSF: Treat 2XX Status code as successful push
thomasdarimont Apr 18, 2026
955b02e
SSF: stream-updated SET, hold-on-pause, per-receiver event TTL
thomasdarimont Apr 18, 2026
275291b
SSF: discard on disabled, hold in-flight on pause, push order tiebreak
thomasdarimont Apr 18, 2026
9f7e8af
SSF: publish critical_subject_members in transmitter metadata
thomasdarimont Apr 18, 2026
3549bdf
SSF: Ensure StreamUpdatedEvent is only sent when transitioning stream…
thomasdarimont Apr 18, 2026
a17cab8
SSF: PollErrorRepresentation with RFC 8936 err/description keys
thomasdarimont Apr 18, 2026
f1f78f1
SSF: fail-loud when email subject format has no email
thomasdarimont Apr 18, 2026
9c9d7fa
SSF: complex(user + tenant) subject format for multi-tenant SETs
thomasdarimont Apr 18, 2026
7b05a70
SSF: map credential events to accurate CAEP change_type
thomasdarimont Apr 18, 2026
3405b49
SSF: admin "Pending Events" tab — lookup + emit from the console
thomasdarimont Apr 18, 2026
59f1d29
SSF: inactivity_timeout + events-map=1 guard
thomasdarimont Apr 18, 2026
99a26af
SSF: Wrap SSF Tab contents into Cards
thomasdarimont Apr 18, 2026
3818d53
SSF: migrate queued outbox rows on delivery-method change; apply stre…
thomasdarimont Apr 18, 2026
c93dd49
SSF: add Prometheus metrics binder for dispatcher / drainer / poll
thomasdarimont Apr 19, 2026
122309b
SSF: meter the verification path (receiver / admin / transmitter)
thomasdarimont Apr 19, 2026
d80a518
SSF: §9.3 subject-removal grace tombstone (transmitter + per-receiver)
thomasdarimont Apr 19, 2026
e07e27a
SSF: TransmitterContext bootstrap refactor + registry available/built…
thomasdarimont Apr 19, 2026
8a2ea31
SSF: pluggable SsfSubjectInclusionResolver via TransmitterProvider
thomasdarimont Apr 19, 2026
fff7cf1
SSF: Move logging of event SET to trace logging in PushDeliveryService
thomasdarimont Apr 19, 2026
9379959
SSF: add Description field to the Receiver tab
thomasdarimont Apr 19, 2026
ef44227
SSF: fix Audience help text on Receiver tab
thomasdarimont Apr 19, 2026
2a73c40
SSF: per-receiver ssf.manualOnlyEvents (suppress native auto-emit)
thomasdarimont Apr 19, 2026
e1776ca
SSF: smooth the custom-event extension surface
thomasdarimont Apr 19, 2026
0fedb09
SSF: keycloak_ssf_drainer_tick_last_at_seconds gauge for stall detection
thomasdarimont Apr 19, 2026
d4e5113
SSF: per-event validate() hook + CAEP credential_type translation
thomasdarimont Apr 20, 2026
a61f2ca
SSF: Remove dangling add SSF receiver provider message
thomasdarimont Apr 20, 2026
61f21ad
SSF: Add SSF tag to org.keycloak.common.constants.KeycloakOpenAPI
thomasdarimont Apr 20, 2026
eddffcf
SSF: pre-token subject gate in native event listener
thomasdarimont Apr 20, 2026
c33a6db
SSF: Run ssf integration tests in the CI
thomasdarimont Apr 20, 2026
5374fa7
SSF: Fix timestamp checks in SsfPendingEventStoreTests to support pos…
thomasdarimont Apr 20, 2026
5c68c5a
SSF: Fix timestamp checks in SsfPendingEventStoreTests to support pos…
thomasdarimont Apr 20, 2026
714cc63
SSF: Revise logging in SsfStreamManagementResource
thomasdarimont Apr 20, 2026
f4e7920
SSF: Ensure the stream config is correctly populated with overlays af…
thomasdarimont Apr 20, 2026
66e0616
SSF: Only stamp stream verification on explicitly requested verificat…
thomasdarimont Apr 20, 2026
b87aaf0
SSF: Handle duplicate JSON export/import of SSF clients
thomasdarimont Apr 20, 2026
858a8bd
SSF: Log unexpected exceptions as errors in createStream
thomasdarimont Apr 20, 2026
bffc4e9
SSF: Async batched outbox cleanup for client/realm removal
thomasdarimont Apr 20, 2026
e986285
SSF: Make spotless happy
thomasdarimont Apr 21, 2026
8d90a26
SSF: Map admin-initiated credential changes to CAEP events
thomasdarimont Apr 21, 2026
93fc22e
SSF: Fix polynomial ReDoS in admin-event path matchers
thomasdarimont Apr 22, 2026
6483d2a
SSF: Rename SecurityEventTokenMapper.toSecurityEvent->toSecurityEven…
thomasdarimont Apr 22, 2026
b140c55
Adapted SSF tests for the Refactor builders in testsuite part 1
thomasdarimont Apr 23, 2026
30ec57a
SSF: Allow exposing custom attributes in SsfEvents
thomasdarimont Apr 23, 2026
8a6a3e5
SSF: Add additional credential attributes to CaepCredentialChange Event
thomasdarimont Apr 23, 2026
10ad70c
SSF: Avoid NPE in SsfTab when emitting synthetic events
thomasdarimont Apr 24, 2026
a83c8cc
SSF: Make outbox DELIVERED row retention configurable
thomasdarimont Apr 24, 2026
978e136
SSF: Extract SsfPushOutboxDrainerTask config parameters into SsfPushO…
thomasdarimont Apr 24, 2026
b3e0ad0
SSF: Revise UI
thomasdarimont Apr 24, 2026
4c45ddb
SSF: Add missing null check to applyCustomAttributes
thomasdarimont Apr 24, 2026
59558ce
SSF: Audit synthetic event emissions and refactor result mapping
thomasdarimont Apr 27, 2026
73de6b6
SSF: Distinguish emit error categories on the wire
thomasdarimont Apr 27, 2026
00e6b22
SSF: Rename SsfPendingEventEntity to SsfEventEntity
thomasdarimont Apr 27, 2026
f37ac9f
SSF: Move SsfPendingEventStore to store.SsfEventStore
thomasdarimont Apr 27, 2026
e28f621
SSF: Rename pendingEventStore fields and factories to eventStore
thomasdarimont Apr 27, 2026
f819068
SSF: Migrate outbox table rename via dedicated 1.0.2 changeset
thomasdarimont Apr 27, 2026
c22576b
SSF: Rename SsfPendingEventStatus to SsfEventStatus
thomasdarimont Apr 27, 2026
7d976a3
SSF: Move SsfJpaEntityProvider to store.jpa
thomasdarimont Apr 27, 2026
2f255fd
SSF: Spotless
thomasdarimont Apr 27, 2026
cf6794d
SSF: Adapt SSF tests for Refactor builders in testsuite part 2
thomasdarimont Apr 28, 2026
11775a5
SSF: Rename manualOnlyEvents to emitOnlyEvents
thomasdarimont Apr 28, 2026
f1af2ce
SSF: Hide admin-ui surfaces when SSF is disabled, move SsfTab to subf…
thomasdarimont Apr 28, 2026
650ff3c
SSF: Bootstrap client scopes when transmitter is enabled on existing …
thomasdarimont Apr 29, 2026
a1c037c
SSF: Replace verification trigger config with opt-in Auto-Verify Stre…
thomasdarimont Apr 29, 2026
cc8d23a
SSF: Improve subjects management admin UI
thomasdarimont Apr 29, 2026
57a31ce
SSF: Extract Subjects sub-tab from SsfTab into its own file
thomasdarimont Apr 29, 2026
284b4ee
SSF: Extract Pending Events sub-tab from SsfTab into its own file
thomasdarimont Apr 29, 2026
4260adc
SSF: Extract Stream sub-tab from SsfTab into its own file
thomasdarimont Apr 29, 2026
f6a90cb
SSF: Extract Receiver sub-tab from SsfTab into its own file
thomasdarimont Apr 29, 2026
1f3cba3
SSF: Move Delivery field next to Audience in the Stream config form
thomasdarimont Apr 29, 2026
489ba3d
SSF: Split Pending Events tab into Event Search + Emit Events
thomasdarimont Apr 29, 2026
292c0a7
SSF: Limit Emit Events dropdown to the stream's events_delivered
thomasdarimont Apr 29, 2026
def4630
SSF: Dead-letter pending events on narrow stream update; consistent s…
thomasdarimont Apr 29, 2026
c9e2c5d
SSF: Clarify Default Subjects help text — talk about users, not subjects
thomasdarimont Apr 29, 2026
dd46e76
SSF: Drop vendor-specific examples from Default Subjects help texts
thomasdarimont Apr 29, 2026
01f4e29
SSF: Skip auto-notify-on-login when org membership already covers, an…
thomasdarimont Apr 29, 2026
889fafc
SSF: Drive admin stream-status changes through the spec-mandated tran…
thomasdarimont Apr 29, 2026
15e5a1e
SSF: Tighten create-stream UX and fix events_delivered intersection
thomasdarimont Apr 29, 2026
523715e
SSF: Split natively-emitted from emittable, sort event-picker dropdowns
thomasdarimont Apr 29, 2026
36f775d
SSF: Gate Emit Events tab on allowEmitEvents
thomasdarimont Apr 30, 2026
602bc01
SSF: Only expose SSF/SSE Metadata if SSF Transmitter feature is enabl…
thomasdarimont May 4, 2026
fb94af9
SSF: Disable SSE metadata by default
thomasdarimont May 4, 2026
facabbf
SSF: Disable SSF Transmitter endpoints if SSF Transmitter feature is …
thomasdarimont May 4, 2026
e7ad561
SSF: Update javadoc about availability of SSF transmitter metadata
thomasdarimont May 4, 2026
cde2252
SSF: Also ignore AdminEvents if SSF Transmitter is not enabled for realm
thomasdarimont May 4, 2026
a2209cc
SSF: Skip outbox push for realms with SSF Transmitter disabled
thomasdarimont May 4, 2026
464f33d
SSF: Document pause-streams-first guidance on transmitter realm toggle
thomasdarimont May 4, 2026
81c0ae4
SSF: Apply trivial UI review fixes for SSF client tabs
thomasdarimont May 4, 2026
9cff332
SSF: Replace native subject-type select with PatternFly KeycloakSelect
thomasdarimont May 4, 2026
9a8b37b
SSF: Debounce live JSON validation in the SSF emit-events payload editor
thomasdarimont May 4, 2026
d610691
SSF: Migrate emit-events tab to react-hook-form and fetchWithError
thomasdarimont May 4, 2026
5a297f7
SSF: Add backstop that promotes stale PENDING outbox rows to DEAD_LETTER
thomasdarimont May 4, 2026
4e0e801
SSF: Extract realm-settings transmitter-disable confirm dialog
thomasdarimont May 4, 2026
86e6434
SSF: Add admin REST endpoints for SSF event stats and bulk delete
thomasdarimont May 4, 2026
b778ab7
SSF: Replace skip-on-disable machinery with delete-on-save, add event…
thomasdarimont May 4, 2026
3bc317a
Outbox: Introduce generic durable event-queue infrastructure
thomasdarimont May 4, 2026
e5d1d18
SSF: Add outbox handler scaffolding for the generic-outbox migration …
thomasdarimont May 4, 2026
c6ae5b3
Outbox: Add container_id column for stream-scoped sub-grouping
thomasdarimont May 4, 2026
2581586
Outbox: Add owner-scoped read / lifecycle helpers for SSF parity
thomasdarimont May 4, 2026
7f9a301
Outbox: Wire OutboxStore alongside SsfEventStore for the SSF cutover
thomasdarimont May 4, 2026
81d70af
SSF: Cut over SecurityEventTokenDispatcher enqueue paths to OutboxStore
thomasdarimont May 4, 2026
dfe9f60
SSF: Cut over SSF drainer + cleanup listener to the generic outbox
thomasdarimont May 4, 2026
b94aea0
SSF: Cut over PollDeliveryService to OutboxStore
thomasdarimont May 4, 2026
ec4c8ec
SSF: Cut over StreamService lifecycle ops to OutboxStore
thomasdarimont May 4, 2026
87e94c1
SSF: Cut over SsfAdminResource endpoints to OutboxStore
thomasdarimont May 4, 2026
c67cbab
SSF: Rename SsfPendingEventRepresentation -> SsfEventRepresentation
thomasdarimont May 4, 2026
11758c1
Outbox: Delete the SSF-specific outbox code now that callers are migr…
thomasdarimont May 4, 2026
9cb9ca6
Outbox: Spotless
thomasdarimont May 4, 2026
b2e0d59
Outbox: Recreate the SsfEventStore tests as OutboxStoreTests against …
thomasdarimont May 4, 2026
a75ce56
Outbox: Move org.keycloak.outbox -> org.keycloak.events.outbox
thomasdarimont May 4, 2026
53aac27
SSF: Wire createDispatcher/createPollDelivery to createOutboxStore di…
thomasdarimont May 4, 2026
0cc2c41
Outbox: Move OutboxStoreTests under tests/events/outbox to mirror prod
thomasdarimont May 4, 2026
448bc17
SSF: Prefer to store SSF event type aliases as OutboxEntry entryTypes
thomasdarimont May 4, 2026
ca8a692
Outbox: Capture push failure detail in last_error
thomasdarimont May 4, 2026
92805c7
SSF: Fix SA gate to compare against internal client UUID
thomasdarimont May 5, 2026
33e07a7
SSF: Rename SsfTransmitterTests to SsfTransmitterMetadataTests
thomasdarimont May 5, 2026
5da31f5
SSF: Add SSRF gate for receiver-supplied push URLs
thomasdarimont May 5, 2026
c9df859
SSF: Reorder createStream validation before stateful checks
thomasdarimont May 6, 2026
66d4758
SSF: Add admin UI Delivery section in ReceiverTab
thomasdarimont May 6, 2026
c931186
Outbox: Use HQL ON CONFLICT DO NOTHING for race-safe enqueue
thomasdarimont May 6, 2026
e3c1234
SSF: Store stream event sets as aliases to save attribute space
thomasdarimont May 6, 2026
3c60c5d
SSF: Trim CAEP + RISC events to the v1 emittable set
thomasdarimont May 6, 2026
d92a2cc
SSF: Move AddSubjectRequest and RemoveSubjectRequest next to SsfSubje…
thomasdarimont May 6, 2026
9dd8b63
SSF: Spotless
thomasdarimont May 6, 2026
5140178
SSF: Schedule transmitter-initiated verification on the managed pool
thomasdarimont May 6, 2026
3f0a6b1
SSF: Use NCLOB for outbox payload/metadata columns
thomasdarimont May 6, 2026
7a85eab
SSF: Resolve vault expressions in stream authorization_header
thomasdarimont May 7, 2026
cfa9ae3
SSF: spotless
thomasdarimont May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,27 @@ jobs:
- name: Run tests
run: ./mvnw package -f scim/tests/pom.xml

ssf-integration-tests:
name: SSF IT
runs-on: ubuntu-latest
needs:
- build
timeout-minutes: 45
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

- id: integration-test-setup
name: Integration test setup
uses: ./.github/actions/integration-test-setup

# This step is necessary because test/clustering requires building a new Keycloak image built from tar.gz
# file that is not part of m2-keycloak.tzts archive
- name: Build tar keycloak-quarkus-dist
run: ./mvnw package -pl quarkus/server/,quarkus/dist/

- name: Run tests
run: ./mvnw package -f ssf/tests/pom.xml

authzen-integration-tests:
name: AuthZen IT
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions common/src/main/java/org/keycloak/common/Profile.java
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ public enum Feature {

DB_TIDB("TiDB database type", Type.EXPERIMENTAL),

SSF("Shared Signals Framework", Type.EXPERIMENTAL),

HTTP_OPTIMIZED_SERIALIZERS("Optimized JSON serializers for better performance of the HTTP layer", Type.PREVIEW),

OPENAPI("OpenAPI specification served at runtime", Type.EXPERIMENTAL, CLIENT_ADMIN_API_V2),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,19 @@ public static class Tags {
public static final String USERS = "Users";
public static final String ORGANIZATIONS = "Organizations";
public static final String WORKFLOWS = "Workflows";
public static final String SSF = "SSF";
private Tags() { }
}

}

public static class Ssf {

private Ssf() { }

public static class Tags {
public static final String TRANSMITTER = "SSF Transmitter";
private Tags() { }
}
}
}

Large diffs are not rendered by default.

121 changes: 120 additions & 1 deletion js/apps/admin-ui/src/clients/ClientDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import useToggle from "../utils/useToggle";
import { AdvancedTab } from "./AdvancedTab";
import { ClientSessions } from "./ClientSessions";
import { ClientSettings } from "./ClientSettings";
import { SsfTab } from "./ssf/SsfTab";
import { AuthorizationEvaluate } from "./authorization/AuthorizationEvaluate";
import { AuthorizationExport } from "./authorization/AuthorizationExport";
import { AuthorizationPermissions } from "./authorization/Permissions";
Expand All @@ -67,6 +68,7 @@ import {
import { ClientParams, ClientTab, toClient } from "./routes/Client";
import { toClientRole } from "./routes/ClientRole";
import { ClientScopesTab, toClientScopesTab } from "./routes/ClientScopeTab";
import { SsfClientTab, toSsfClientTab } from "./routes/ClientSsfTab";
import { toClients } from "./routes/Clients";
import { toCreateRole } from "./routes/NewRole";
import { ClientScopes } from "./scopes/ClientScopes";
Expand Down Expand Up @@ -194,7 +196,7 @@ export default function ClientDetails() {

const { t } = useTranslation();
const { addAlert, addError } = useAlerts();
const { realm } = useRealm();
const { realm, realmRepresentation } = useRealm();
const { hasAccess } = useAccess();
const isFeatureEnabled = useIsFeatureEnabled();

Expand Down Expand Up @@ -225,6 +227,31 @@ export default function ClientDetails() {
defaultValue: "client-secret",
});

const ssfEnabled = useWatch({
control: form.control,
name: convertAttributeNameToForm<FormFields>("attributes.ssf.enabled"),
});

const ssfAllowEmitEvents = useWatch({
control: form.control,
name: convertAttributeNameToForm<FormFields>(
"attributes.ssf.allowEmitEvents",
),
});
const showSsfEmitEventsTab = ssfAllowEmitEvents?.toString() === "true";

// Gate every SSF surface on three conditions: server feature flag,
// realm-level transmitter toggle, and per-client opt-in. Missing any
// one means SSF endpoints aren't available — surfacing the tab would
// crash on the first API call (the original bug from the review).
const isSsfFeatureEnabled = isFeatureEnabled(Feature.Ssf);
const isSsfRealmEnabled =
realmRepresentation?.attributes?.["ssf.transmitterEnabled"] === "true";
const showSsfTab =
isSsfFeatureEnabled &&
isSsfRealmEnabled &&
ssfEnabled?.toString() === "true";

const [client, setClient] = useState<ClientRepresentation>();

const loader = async () => {
Expand All @@ -249,6 +276,7 @@ export default function ClientDetails() {
const sessionsTab = useRoutableTab(tab("sessions"));
const permissionsTab = useRoutableTab(tab("permissions"));
const advancedTab = useRoutableTab(tab("advanced"));
const ssfTab = useRoutableTab(tab("ssf"));
const eventsTab = useRoutableTab(tab("events"));

const [activeEventsTab, setActiveEventsTab] = useState("userEvents");
Expand All @@ -265,6 +293,18 @@ export default function ClientDetails() {
clientScopesTabRoute("evaluate"),
);

const ssfTabRoute = (tab: SsfClientTab) =>
toSsfClientTab({
realm,
clientId,
tab,
});
const ssfReceiverTab = useRoutableTab(ssfTabRoute("receiver"));
const ssfStreamTab = useRoutableTab(ssfTabRoute("stream"));
const ssfSubjectsTab = useRoutableTab(ssfTabRoute("subjects"));
const ssfEventSearchTab = useRoutableTab(ssfTabRoute("event-search"));
const ssfEmitEventsTab = useRoutableTab(ssfTabRoute("emit-events"));

const authorizationTabRoute = (tab: AuthorizationTab) =>
toAuthorizationTab({
realm,
Expand Down Expand Up @@ -688,6 +728,85 @@ export default function ClientDetails() {
>
<AdvancedTab save={save} client={client} />
</Tab>
{client.protocol === "openid-connect" &&
!client.publicClient &&
showSsfTab && (
<Tab
id="ssf"
data-testid="ssfTab"
title={<TabTitleText>{t("ssf")}</TabTitleText>}
{...ssfTab}
>
<RoutableTabs
defaultLocation={ssfTabRoute("receiver")}
mountOnEnter
unmountOnExit
>
<Tab
id="ssfReceiverTab"
data-testid="ssfReceiverTab"
title={<TabTitleText>{t("ssfTabReceiver")}</TabTitleText>}
{...ssfReceiverTab}
>
<SsfTab
save={save}
client={client}
activeTab="receiver"
/>
</Tab>
<Tab
id="ssfStreamTab"
data-testid="ssfStreamTab"
title={<TabTitleText>{t("ssfTabStream")}</TabTitleText>}
{...ssfStreamTab}
>
<SsfTab save={save} client={client} activeTab="stream" />
</Tab>
<Tab
id="ssfSubjectsTab"
data-testid="ssfSubjectsTab"
title={<TabTitleText>{t("ssfTabSubjects")}</TabTitleText>}
{...ssfSubjectsTab}
>
<SsfTab
save={save}
client={client}
activeTab="subjects"
/>
</Tab>
<Tab
id="ssfEventSearchTab"
data-testid="ssfEventSearchTab"
title={
<TabTitleText>{t("ssfTabEventSearch")}</TabTitleText>
}
{...ssfEventSearchTab}
>
<SsfTab
save={save}
client={client}
activeTab="event-search"
/>
</Tab>
{showSsfEmitEventsTab && (
<Tab
id="ssfEmitEventsTab"
data-testid="ssfEmitEventsTab"
title={
<TabTitleText>{t("ssfTabEmitEvents")}</TabTitleText>
}
{...ssfEmitEventsTab}
>
<SsfTab
save={save}
client={client}
activeTab="emit-events"
/>
</Tab>
)}
</RoutableTabs>
</Tab>
)}
{hasAccess("view-events") && (
<Tab
data-testid="events-tab"
Expand Down
25 changes: 25 additions & 0 deletions js/apps/admin-ui/src/clients/add/CapabilityConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { FormFields } from "../ClientDetails";
import { IdentityProviderSelect } from "../../components/identity-provider/IdentityProviderSelect";
import { IdentityProviderType } from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
import { useAccess } from "../../context/access/Access";
import { useRealm } from "../../context/realm-context/RealmContext";

type CapabilityConfigProps = {
unWrap?: boolean;
Expand Down Expand Up @@ -50,7 +51,15 @@ export const CapabilityConfig = ({
);
const isFeatureEnabled = useIsFeatureEnabled();
const { hasSomeAccess } = useAccess();
const { realmRepresentation } = useRealm();
const showIdentityProviders = hasSomeAccess("view-identity-providers");
// Mirror the gate in ClientDetails: only expose the SSF capability
// toggle when both the server feature is enabled and the realm has
// opted in. Otherwise enabling the toggle would just render a tab
// that crashes on its first API call.
const showSsfReceiverToggle =
isFeatureEnabled(Feature.Ssf) &&
realmRepresentation?.attributes?.["ssf.transmitterEnabled"] === "true";
return (
<FormAccess
isHorizontal
Expand Down Expand Up @@ -106,6 +115,12 @@ export const CapabilityConfig = ({
),
false,
);
setValue(
convertAttributeNameToForm<FormFields>(
"attributes.ssf.enabled",
),
false,
);
}
}}
aria-label={t("clientAuthentication")}
Expand Down Expand Up @@ -498,6 +513,16 @@ export const CapabilityConfig = ({
)}
</>
)}
{!clientAuthentication && showSsfReceiverToggle && (
<DefaultSwitchControl
name={convertAttributeNameToForm<FormFields>(
"attributes.ssf.enabled",
)}
label={t("ssfReceiverEnabled")}
labelIcon={t("ssfReceiverEnabledHelp")}
stringify
/>
)}
</>
)}
{protocol === "saml" && (
Expand Down
2 changes: 2 additions & 0 deletions js/apps/admin-ui/src/clients/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ClientRegistrationRoute } from "./routes/ClientRegistration";
import { ClientRoleRoute } from "./routes/ClientRole";
import { ClientsRoute, ClientsRouteWithTab } from "./routes/Clients";
import { ClientScopesRoute } from "./routes/ClientScopeTab";
import { ClientSsfTabRoute } from "./routes/ClientSsfTab";
import { CreateInitialAccessTokenRoute } from "./routes/CreateInitialAccessToken";
import {
DedicatedScopeDetailsRoute,
Expand Down Expand Up @@ -50,6 +51,7 @@ const routes: AppRouteObject[] = [
DedicatedScopeDetailsRoute,
DedicatedScopeDetailsWithTabRoute,
ClientScopesRoute,
ClientSsfTabRoute,
ClientRoleRoute,
AuthorizationRoute,
NewResourceRoute,
Expand Down
3 changes: 2 additions & 1 deletion js/apps/admin-ui/src/clients/routes/Client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export type ClientTab =
| "serviceAccount"
| "permissions"
| "sessions"
| "events";
| "events"
| "ssf";

export type ClientParams = {
realm: string;
Expand Down
37 changes: 37 additions & 0 deletions js/apps/admin-ui/src/clients/routes/ClientSsfTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { lazy } from "react";
import type { Path } from "react-router-dom";
import { generateEncodedPath } from "../../utils/generateEncodedPath";
import type { AppRouteObject } from "../../routes";

/**
* The five sub-tabs of the SSF view on a client. Mirrors the pattern
* used by Client Scopes (setup / evaluate) so the SSF view's sub-tabs
* are deep-linkable from URLs and bookmarkable per section.
*/
export type SsfClientTab =
| "receiver"
| "stream"
| "subjects"
| "event-search"
| "emit-events";

export type ClientSsfTabParams = {
realm: string;
clientId: string;
tab: SsfClientTab;
};

const ClientDetails = lazy(() => import("../ClientDetails"));

export const ClientSsfTabRoute: AppRouteObject = {
path: "/:realm/clients/:clientId/ssf/:tab",
element: <ClientDetails />,
handle: {
access: "view-clients",
breadcrumb: (t) => t("clientSettings"),
},
};

export const toSsfClientTab = (params: ClientSsfTabParams): Partial<Path> => ({
pathname: generateEncodedPath(ClientSsfTabRoute.path, params),
});
Loading
Loading