From fad5715a167e3185d908aceed98352a866276660 Mon Sep 17 00:00:00 2001 From: Mauro Mugnaini Date: Thu, 18 Apr 2024 12:16:23 +0200 Subject: [PATCH] Completely rewrote the implementation and configuration method, now both `admin` and `d4science` realms are configured by default as interesting realms. Added specific SPI configurations with `spi-events-listener-orchestrator-event-publisher-` prefix: `include-realms`, `exclude-realms`, `include-admin-types`, `exclude-admin-types`, `include-events` and `exclude-events` --- event-listener-provider/README.md | 26 ++ .../gcube/keycloak/event/KeycloakEvent.java | 30 +- .../event/NoOpEventPublisherProvider.java | 69 ----- .../OrchestratorEventPublisherProvider.java | 172 ++++++++---- ...estratorEventPublisherProviderFactory.java | 265 ++++++++++++------ 5 files changed, 343 insertions(+), 219 deletions(-) delete mode 100644 event-listener-provider/src/main/java/org/gcube/keycloak/event/NoOpEventPublisherProvider.java diff --git a/event-listener-provider/README.md b/event-listener-provider/README.md index 4aa5d4b..14aba36 100644 --- a/event-listener-provider/README.md +++ b/event-listener-provider/README.md @@ -25,6 +25,32 @@ To build the JAR file it is sufficient to type In order to deploy the module it is sufficient to copy into the `[keycloak-home]/providers` folder. +### Configuration + +In order to configure the `Orchestrator`'s server endpoint URL it is sufficient to create a new client inside the `d4science` realm (or in the realm specified in the `target-orchestrator-realm` configuration) with id `conductor-server` and set-up the API URL in the `Home URL` field (e.g. https://conductor.dev.d4science.org/api/workflow/). + +To perform authorized requests to the `orchestrator`, with an UMA token as `bearer`, a specific client with id `keycloak-server` has to be created in the same realm of the orchestrator client. This client should be configured to obtain an UMA ticket with audience: `conductor-server`. +Additionally, if the `Home URL` for this client is configured with a token endpoint URL, this will be used to obtain the UMA ticket, instead of the token URL of the Keycloak instance where the SPI is running. + +Since version `2.24.0` it is possible to configure the SPI configurations with the following entries: + +* `target-orchestrator-realm` - to specify in which realm looking for the the configured clients, overriding the default that is: `d4science` +* `include-realms` - to specify the interesting realms (default are: `d4science` and `master`) +* `exclude-realms` - to specify the non interesting realms +* `include-admin-types` - To specify the admin's `ResourceType`s to include (default are: `user`, `client` and `realm`) +* `exclude-admin-types`- To specify the admin's `ResourceType`s to exclude +* `include-events` - To specify the `EventType`s to include (default are: `register` and `delete_account`) +* `exclude-events` - To specify the `EventType`s to exclude + +The prefix to add for the configuration is `spi-events-listener-orchestrator-event-publisher-` and f.e. to configure the interesting realms it is sufficient to use: + +```bash + spi-events-listener-orchestrator-event-publisher-include-realms=d4science,master +``` + +This can be achieved either by adding some lines to the `[keycloak-home]/conf/keycloak.conf` configuration file, by using the CLI parameters or using the ENV variables. +(Please, refer to the [Keycloak documentation section](https://www.keycloak.org/server/configuration) for further info) + ## Change log See [CHANGELOG.md](CHANGELOG.md). diff --git a/event-listener-provider/src/main/java/org/gcube/keycloak/event/KeycloakEvent.java b/event-listener-provider/src/main/java/org/gcube/keycloak/event/KeycloakEvent.java index cc63a00..1dd7cb0 100644 --- a/event-listener-provider/src/main/java/org/gcube/keycloak/event/KeycloakEvent.java +++ b/event-listener-provider/src/main/java/org/gcube/keycloak/event/KeycloakEvent.java @@ -62,17 +62,23 @@ public class KeycloakEvent extends Event { } public static KeycloakEvent newKeycloakAdminEvent(AdminEvent adminEvent, boolean includeRepresentation) { - KeycloakEvent keycloakEvent = new KeycloakEvent(ADMIN_NAME, adminEvent.getRealmId(), adminEvent.getTime()); + KeycloakEvent keycloakEvent = new KeycloakEvent(constructAdminEventName(adminEvent), adminEvent.getRealmId(), + adminEvent.getTime()); - keycloakEvent.setOperation(adminEvent.getOperationType().name()); - if (includeRepresentation) { + keycloakEvent.setOperation(adminEvent.getOperationType().name().toLowerCase()); + if (includeRepresentation && adminEvent.getRepresentation() != null) { keycloakEvent.setRepresentation(adminEvent.getRepresentation()); } keycloakEvent.setResource(adminEvent.getResourcePath()); - keycloakEvent.setResourceType(adminEvent.getResourceTypeAsString()); + keycloakEvent.setResourceType(adminEvent.getResourceTypeAsString().toLowerCase()); return keycloakEvent; } + protected static String constructAdminEventName(AdminEvent adminEvent) { + return ADMIN_NAME + "_" + adminEvent.getResourceTypeAsString().toLowerCase() + "_" + + adminEvent.getOperationType().name().toLowerCase(); + } + public static KeycloakEvent newKeycloakEvent(org.keycloak.events.Event event) { KeycloakEvent keycloakEvent = new KeycloakEvent(event.getType().name().toLowerCase(), event.getRealmId(), event.getTime(), event.getDetails()); @@ -88,7 +94,7 @@ public class KeycloakEvent extends Event { } private static OffsetDateTime convertEventDate(long millis) { - OrchestratorEventPublisherProvider.logger.debugf("Creating offset date time from millis %d -> %t", millis, millis); + OrchestratorEventPublisherProvider.logger.debugf("Creating offset date time from millis: %d", millis); return Instant.ofEpochMilli(millis).atZone(ZoneOffset.systemDefault()).toOffsetDateTime(); } @@ -102,11 +108,15 @@ public class KeycloakEvent extends Event { @SuppressWarnings("unchecked") public void setRepresentation(String representation) { - try { - put(REPRESENTATION, new JSONParser().parse(representation)); - } catch (ParseException e) { - e.printStackTrace(); - set(REPRESENTATION, representation); + if (representation != null) { + try { + put(REPRESENTATION, new JSONParser().parse(representation)); + } catch (ParseException e) { + e.printStackTrace(); + set(REPRESENTATION, representation); + } + } else { + remove(REPRESENTATION); } } diff --git a/event-listener-provider/src/main/java/org/gcube/keycloak/event/NoOpEventPublisherProvider.java b/event-listener-provider/src/main/java/org/gcube/keycloak/event/NoOpEventPublisherProvider.java deleted file mode 100644 index e17e0bf..0000000 --- a/event-listener-provider/src/main/java/org/gcube/keycloak/event/NoOpEventPublisherProvider.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.gcube.keycloak.event; - -import org.gcube.event.publisher.EventSender; -import org.json.simple.JSONObject; -import org.keycloak.events.Event; -import org.keycloak.events.admin.AdminEvent; - -/** - * Added to avoid errors for not configured provider also in realm where event listener is not configured (KC bug?) - * - * @author Mauro Mugnaini - * - */ -public class NoOpEventPublisherProvider extends OrchestratorEventPublisherProvider { - - public NoOpEventPublisherProvider() { - super(); - } - - @Override - public void close() { - } - - @Override - public void onEvent(Event event) { - // Nothing to do - } - - @Override - public void onEvent(AdminEvent event, boolean includeRepresentation) { - // Nothing to do - } - - @Override - protected EventSender createEventSender() { - return new EventSender() { - - @Override - public void send(org.gcube.event.publisher.Event event) { - // Nothing to do - } - - @Override - public String sendAndGetResult(org.gcube.event.publisher.Event event) { - // Nothing to do - return null; - } - - @Override - public JSONObject retrive(String id) { - // Nothing to do - return null; - } - - @Override - public int getLastSendHTTPResponseCode() { - // Nothing to do - return 0; - } - - @Override - public int getLastRetrieveHTTPResponseCode() { - // Nothing to do - return 0; - } - }; - } - -} diff --git a/event-listener-provider/src/main/java/org/gcube/keycloak/event/OrchestratorEventPublisherProvider.java b/event-listener-provider/src/main/java/org/gcube/keycloak/event/OrchestratorEventPublisherProvider.java index 8e0964a..d3a29c7 100644 --- a/event-listener-provider/src/main/java/org/gcube/keycloak/event/OrchestratorEventPublisherProvider.java +++ b/event-listener-provider/src/main/java/org/gcube/keycloak/event/OrchestratorEventPublisherProvider.java @@ -1,7 +1,9 @@ package org.gcube.keycloak.event; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collections; import java.util.EventListener; -import java.util.HashSet; import java.util.Set; import org.gcube.event.publisher.AbstractEventPublisher; @@ -10,8 +12,11 @@ import org.gcube.event.publisher.HTTPWithUMAAuthEventSender; import org.jboss.logging.Logger; import org.keycloak.events.Event; import org.keycloak.events.EventListenerProvider; +import org.keycloak.events.EventListenerTransaction; import org.keycloak.events.EventType; import org.keycloak.events.admin.AdminEvent; +import org.keycloak.events.admin.ResourceType; +import org.keycloak.models.KeycloakTransactionManager; /** * @author Marco Lettere @@ -22,69 +27,140 @@ public class OrchestratorEventPublisherProvider extends AbstractEventPublisher public static final Logger logger = Logger.getLogger(OrchestratorEventPublisherProvider.class); - private static final Set INTERESTING_EVENTS = new HashSet<>(); + private final String realmName; + private final EventListenerTransaction tx; + private final String orchestratorEndpoint; + private final String keycloakTokenEndpoint; + private final String keycloakClientId; + private final String keycloakClientSecret; + private final String orchestratorAudience; + private final Set interestingAdminEventResourceTypes; + private final Set interestingEvents; - static { - INTERESTING_EVENTS.add(EventType.CLIENT_DELETE); - INTERESTING_EVENTS.add(EventType.CLIENT_DELETE_ERROR); - INTERESTING_EVENTS.add(EventType.FEDERATED_IDENTITY_LINK); - INTERESTING_EVENTS.add(EventType.FEDERATED_IDENTITY_LINK_ERROR); - INTERESTING_EVENTS.add(EventType.IDENTITY_PROVIDER_FIRST_LOGIN); - INTERESTING_EVENTS.add(EventType.IDENTITY_PROVIDER_FIRST_LOGIN_ERROR); - INTERESTING_EVENTS.add(EventType.REGISTER); - INTERESTING_EVENTS.add(EventType.REGISTER_ERROR); - INTERESTING_EVENTS.add(EventType.UPDATE_EMAIL); - INTERESTING_EVENTS.add(EventType.VERIFY_EMAIL_ERROR); - INTERESTING_EVENTS.add(EventType.DELETE_ACCOUNT); + /** + * Default constructor for dummy use + */ + public OrchestratorEventPublisherProvider() { + this.realmName = null; + this.tx = null; + orchestratorEndpoint = null; + keycloakTokenEndpoint = null; + keycloakClientId = null; + keycloakClientSecret = null; + orchestratorAudience = null; + this.interestingAdminEventResourceTypes = Collections.emptySet(); + this.interestingEvents = Collections.emptySet(); + logger.tracef("Created new dummy instance"); } - public OrchestratorEventPublisherProvider() { + public OrchestratorEventPublisherProvider(String realmName, KeycloakTransactionManager transactionManager, + String orchestratorEndpoint, String keycloakTokenEndpoint, String keycloakClientId, + String keycloakClientSecret, String orchestratorAudience, + Set interestingAdminEventResourceTypes, Set interestingEvents) { + super(); + this.realmName = realmName; + this.interestingAdminEventResourceTypes = interestingAdminEventResourceTypes; + this.interestingEvents = interestingEvents; + tx = new EventListenerTransaction(this::sendAdminEvent, this::sendEvent); + transactionManager.enlistAfterCompletion(tx); + this.orchestratorEndpoint = orchestratorEndpoint; + this.keycloakTokenEndpoint = keycloakTokenEndpoint; + this.keycloakClientId = keycloakClientId; + this.keycloakClientSecret = keycloakClientSecret; + this.orchestratorAudience = orchestratorAudience; + // Creating every time a new sender to be sure new settings are used, if changed + this.setEventSender(newEventSender(realmName)); + logger.tracef("Created new instance for realm: %s. Admin events: %2. Events:", realmName, + interestingAdminEventResourceTypes, interestingEvents); + } + + @Override + protected EventSender createEventSender() { + // Will be set in the constructor by using the setter, instance variables are still not available at this moment + return null; + } + + protected EventSender newEventSender(String realmName) { + if (orchestratorEndpoint != null) { + try { + if (keycloakClientSecret != null) { + logger.tracef( + "Creating event sender for realm %s. Endpoint: %s, KC token endpoint: %s, UMA adience: %s", + realmName, orchestratorEndpoint, keycloakTokenEndpoint, orchestratorAudience); + + return new HTTPWithUMAAuthEventSender(new URL(orchestratorEndpoint), keycloakClientId, + keycloakClientSecret, new URL(keycloakTokenEndpoint), orchestratorAudience); + } else { + logger.debugf("Creating unauthorized event sender with endpoint: %s", orchestratorEndpoint); + return new HTTPWithUMAAuthEventSender(new URL(orchestratorEndpoint), null, null, null, null); + } + } catch (MalformedURLException e) { + logger.errorf("Can't construct endpoint URL from string", e); + } + } else { + logger.errorf("Orchestrator endpoint is not available for realm: %s", realmName); + } + return null; } @Override public void close() { } + @Override + public void onEvent(AdminEvent adminEvent, boolean includeRepresentation) { + if (getEventSender() != null) { + if (adminEvent.getError() != null) { + logger.debug("Skipping error admin event publish"); + return; + } + if (interestingAdminEventResourceTypes.contains(adminEvent.getResourceType())) { + logger.tracef("Enqued admin event for resource type: %s", adminEvent.getResourceType().name()); + tx.addAdminEvent(adminEvent, includeRepresentation); + } else { + logger.tracef("Skipping not interesting admin event resource type: %s", + adminEvent.getResourceType().name()); + } + } else { + logger.debugf("Can't publish admin events since since event sender is null for realm: %s", realmName); + } + } + + public void sendAdminEvent(AdminEvent adminEvent, boolean includeRepresentation) { + if (getEventSender() != null) { + logger.debugf("Publishing new admin event '%s' to orchestrator for realm: %s", + adminEvent.getOperationType().name(), realmName); + + publish(KeycloakEvent.newKeycloakAdminEvent(adminEvent, includeRepresentation)); + } else { + logger.debugf("Can't publish admin events since event sender is null. Current realm: %s", realmName); + } + } + @Override public void onEvent(Event event) { - if (!INTERESTING_EVENTS.contains(event.getType())) { - logger.tracef("Skipping publish of not interesting event: %s", event.getType().toString()); - return; + if (getEventSender() != null) { + EventType eventType = event.getType(); + if (interestingEvents.contains(eventType)) { + logger.tracef("Enqued event of type: %s", eventType.name()); + tx.addEvent(event); + return; + } else { + logger.tracef("Skipping not interesting event: %s", eventType.name()); + } + } else { + logger.debugf("Can't publish events since since event sender is null for realm: %s", realmName); } - logger.debug("Publishing new event to orchestrator"); - publish(KeycloakEvent.newKeycloakEvent(event)); } - @Override - public void onEvent(AdminEvent event, boolean includeRepresentation) { - if (event.getError() != null) { - logger.debug("Skipping error admin event publish"); - return; + public void sendEvent(Event event) { + if (getEventSender() != null) { + logger.debugf("Publishing new event '%s' to orchestrator for realm %s", event.getType().name(), realmName); + publish(KeycloakEvent.newKeycloakEvent(event)); + } else { + logger.debugf("Can't publish events since event sender is null. Current realm: %s", realmName); } - logger.debug("Publishing new admin event to orchestrator"); - publish(KeycloakEvent.newKeycloakAdminEvent(event, includeRepresentation)); - } - - @Override - protected EventSender createEventSender() { - logger.infof( - "Creating the HTTP event sender with endpoint: %s, clientId: %s, KC token endpoint: %s, UMA adience: %s", - OrchestratorEventPublisherProviderFactory.ORCHESTRATOR_ENDPOINT, - OrchestratorEventPublisherProviderFactory.KEYCLOAK_CLIENT_ID, - OrchestratorEventPublisherProviderFactory.KEYCLOAK_ENDPOINT, - OrchestratorEventPublisherProviderFactory.ORCHESTRATOR_AUDIENCE_ID); - - return OrchestratorEventPublisherProviderFactory.KEYCLOAK_ENDPOINT != null - ? new HTTPWithUMAAuthEventSender( - OrchestratorEventPublisherProviderFactory.ORCHESTRATOR_ENDPOINT, - OrchestratorEventPublisherProviderFactory.KEYCLOAK_CLIENT_ID, - OrchestratorEventPublisherProviderFactory.KEYCLOAK_CLIENT_SECRET, - OrchestratorEventPublisherProviderFactory.KEYCLOAK_ENDPOINT, - OrchestratorEventPublisherProviderFactory.ORCHESTRATOR_AUDIENCE_ID) - : new HTTPWithUMAAuthEventSender( - OrchestratorEventPublisherProviderFactory.ORCHESTRATOR_ENDPOINT, - null, null, null, null); } } diff --git a/event-listener-provider/src/main/java/org/gcube/keycloak/event/OrchestratorEventPublisherProviderFactory.java b/event-listener-provider/src/main/java/org/gcube/keycloak/event/OrchestratorEventPublisherProviderFactory.java index c76b879..83103e3 100644 --- a/event-listener-provider/src/main/java/org/gcube/keycloak/event/OrchestratorEventPublisherProviderFactory.java +++ b/event-listener-provider/src/main/java/org/gcube/keycloak/event/OrchestratorEventPublisherProviderFactory.java @@ -1,15 +1,22 @@ package org.gcube.keycloak.event; -import java.net.MalformedURLException; -import java.net.URL; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import org.gcube.event.publisher.EventSender; import org.jboss.logging.Logger; import org.keycloak.Config.Scope; +import org.keycloak.events.Event; import org.keycloak.events.EventListenerProviderFactory; +import org.keycloak.events.EventType; +import org.keycloak.events.admin.ResourceType; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; +import org.keycloak.protocol.oidc.OIDCWellKnownProviderFactory; +import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; /** * @author Marco Lettere @@ -19,23 +26,50 @@ public class OrchestratorEventPublisherProviderFactory implements EventListenerP private static final Logger logger = Logger.getLogger(OrchestratorEventPublisherProviderFactory.class); - public static final String MASTER_REALM_NAME = "master"; - public static final String ORCHESTRATOR_CLIENT_ID = "orchestrator"; - public static final String ORCHESTRATOR_AUDIENCE_ID = "conductor-server"; - - public static final String KEYCLOAK_CLIENT_ID = "keycloak-client"; + private static final String MASTER_REALM_NAME = "master"; + private static final String D4SCIENCE_REALM_NAME = "d4science"; + private static final String ORCHESTRATOR_CLIENT_ID = "conductor-server"; + private static final String ORCHESTRATOR_AUDIENCE = ORCHESTRATOR_CLIENT_ID; + private static final String KEYCLOAK_CLIENT_ID = "keycloak-server"; - private static final int CHECK_DELAY = 60 * 1000; // One minute + private static final int CHECK_DELAY = 30 * 1000; + private static final String DEFAULT_TARGET_ORCHESTRATOR_REALM; + private static final Set DEFAULT_REALM_NAMES; + private static final Set DEFAULT_INTERESTING_EVENTS; + private static final Set DEFAULT_INTERESTING_ADMIN_EVENT_RESOURCE_TYPES; + protected static Long LAST_ENDPOINT_CHECK = Long.valueOf(0); - public static URL ORCHESTRATOR_ENDPOINT; - public static URL KEYCLOAK_ENDPOINT; - public static String KEYCLOAK_CLIENT_SECRET; + private static String THIS_KEYCLOAK_TOKEN_ENDPOINT; + private static ClientModel ORCHESTRATOR_CLIENT; + private static ClientModel KEYCLOAK_CLIENT; - protected Long lastEndpointCheck = Long.valueOf(0); - protected OrchestratorEventPublisherProvider oepp; + private String targetOrchestratorRealm; + private Set interestingRealms = new HashSet<>(); + private Set interestingEvents = new HashSet<>(); + private Set interestingAdminEventResourceTypes = new HashSet<>(); - public OrchestratorEventPublisherProviderFactory() { - logger.info("New OrchestratorEventPublisherProviderFactory has been created"); + static { + DEFAULT_TARGET_ORCHESTRATOR_REALM = D4SCIENCE_REALM_NAME; + DEFAULT_REALM_NAMES = new HashSet<>(); + DEFAULT_INTERESTING_EVENTS = new HashSet<>(); + DEFAULT_INTERESTING_ADMIN_EVENT_RESOURCE_TYPES = new HashSet<>(); + + Collections.addAll(DEFAULT_REALM_NAMES, + MASTER_REALM_NAME, + D4SCIENCE_REALM_NAME); + + Collections.addAll(DEFAULT_INTERESTING_ADMIN_EVENT_RESOURCE_TYPES, + ResourceType.CLIENT, + ResourceType.REALM, + ResourceType.USER); + + Collections.addAll(DEFAULT_INTERESTING_EVENTS, + // EventType.FEDERATED_IDENTITY_LINK, + // EventType.IDENTITY_PROVIDER_FIRST_LOGIN, + // EventType.LOGIN, + EventType.REGISTER, + // EventType.UPDATE_EMAIL, + EventType.DELETE_ACCOUNT); } @Override @@ -43,88 +77,82 @@ public class OrchestratorEventPublisherProviderFactory implements EventListenerP } @Override - public synchronized OrchestratorEventPublisherProvider create(KeycloakSession keycloakSession) { - Long now = System.currentTimeMillis(); - Long elapsed = now - lastEndpointCheck; - if (oepp == null || elapsed > CHECK_DELAY) { - lastEndpointCheck = now; - ClientModel orchestratorClient = getClientInActualOrMasterRealm(keycloakSession, ORCHESTRATOR_CLIENT_ID); - URL newOrchestratorEndpoint; - if (orchestratorClient != null) { - logger.trace("Getting configured orchestrator endpoint address from client's base URL"); - try { - newOrchestratorEndpoint = new URL(orchestratorClient.getBaseUrl()); - } catch (MalformedURLException e) { - logger.errorf("Can't create new orchestrator endpoint address: %s", orchestratorClient.getBaseUrl()); - oepp = new NoOpEventPublisherProvider(); - return oepp; - } - } else { - logger.debugf("Can't go ahead without a configured '%f' client", ORCHESTRATOR_CLIENT_ID); - oepp = null; - return oepp; + public OrchestratorEventPublisherProvider create(KeycloakSession keycloakSession) { + synchronized (this) { + Long now = System.currentTimeMillis(); + Long elapsed = now - LAST_ENDPOINT_CHECK; + if (elapsed > CHECK_DELAY) { + LAST_ENDPOINT_CHECK = now; + checkForConfigChanges(keycloakSession); + // } else { + // logger.tracef("Next check is in %d millis", CHECK_DELAY - elapsed); } - ClientModel keycloakClient = getClientInActualOrMasterRealm(keycloakSession, KEYCLOAK_CLIENT_ID); - URL newKeycloakEndpoint; - String keycloakClientSecret; - if (keycloakClient != null) { - try { - logger.debug("Getting configured keycloak endpoint address from client's base URL"); - newKeycloakEndpoint = new URL(keycloakClient.getBaseUrl()); - // Only do it if URL has been configured properly - logger.debug("Getting configured keycloak client client-secret from client"); - keycloakClientSecret = keycloakClient.getSecret(); - } catch (MalformedURLException e) { - logger.errorf("Can't create new keycloak token address: %s", keycloakClient.getBaseUrl(), e); - oepp = new NoOpEventPublisherProvider(); - return oepp; - } - } else { - logger.debugf("Can't go ahead without a configured '%f' client", KEYCLOAK_CLIENT_ID); - oepp = new NoOpEventPublisherProvider(); - return oepp; - } - - if (oepp == null || !newOrchestratorEndpoint.equals(ORCHESTRATOR_ENDPOINT) - || !newKeycloakEndpoint.equals(KEYCLOAK_ENDPOINT) - || !keycloakClientSecret.equals(KEYCLOAK_CLIENT_SECRET)) { - - logger.info("Creating new orchestrator event publisher provider"); - // Address and other fileds will be then read from static fields in this class by - // the createEventSender() called by the superclass' constructor, overridden in the impl. - ORCHESTRATOR_ENDPOINT = newOrchestratorEndpoint; - KEYCLOAK_ENDPOINT = newKeycloakEndpoint; - KEYCLOAK_CLIENT_SECRET = keycloakClientSecret; - oepp = new OrchestratorEventPublisherProvider(); - } - - } else { - logger.tracef("Next check is in %d millis", CHECK_DELAY - elapsed); } - return oepp; + String realmName = keycloakSession.getContext().getRealm().getName(); + if (interestingRealms.contains(realmName)) { + // logger.debugf("Returning new provider to handle event in realm: %s", realmName); + return new OrchestratorEventPublisherProvider(realmName, keycloakSession.getTransactionManager(), + getOrchestratorEndpoint(), getKeycloakTokenEndpoint(), KEYCLOAK_CLIENT_ID, + getKeycloakClientSecret(), ORCHESTRATOR_AUDIENCE, interestingAdminEventResourceTypes, + interestingEvents); + } else { + logger.debugf("Returning a dummy provider to handle the event in realm: %s", realmName); + return new OrchestratorEventPublisherProvider() { + @Override + public void onEvent(Event event) { + } + + @Override + public void onEvent(org.keycloak.events.admin.AdminEvent adminEvent, boolean includeRepresentation) { + }; + + @Override + protected EventSender createEventSender() { + return null; + }; + }; + } } - protected ClientModel getClientInActualOrMasterRealm(KeycloakSession keycloakSession, String clientId) { - logger.debug("Getting actual realm from session's context"); - RealmModel realm = keycloakSession.getContext().getRealm(); - logger.debugf("Trying getting '%s' client in current realm: '%s'", clientId, realm.getName()); - ClientModel client = realm.getClientByClientId(clientId); - if (client == null) { - if (!MASTER_REALM_NAME.equals(realm.getName())) { - logger.debugf("Not found. Now trying getting '%s' in '%s' realm", clientId, MASTER_REALM_NAME); - realm = keycloakSession.realms().getRealmByName(MASTER_REALM_NAME); - client = realm.getClientByClientId(clientId); - if (client == null) { - logger.warnf("Cannot find '%s' client not even in '%s' realm", clientId, realm.getName(), - MASTER_REALM_NAME); - - return null; - } - } else { - logger.trace("Not found."); + public String getOrchestratorEndpoint() { + return ORCHESTRATOR_CLIENT != null ? ORCHESTRATOR_CLIENT.getBaseUrl() : null; + } + + public String getKeycloakClientSecret() { + return KEYCLOAK_CLIENT != null ? KEYCLOAK_CLIENT.getSecret() : null; + } + + public String getKeycloakTokenEndpoint() { + if (KEYCLOAK_CLIENT != null) { + if (KEYCLOAK_CLIENT.getBaseUrl() != null) { + return KEYCLOAK_CLIENT.getBaseUrl(); } } - logger.debugf("Client '%s' found", clientId); + return THIS_KEYCLOAK_TOKEN_ENDPOINT; + } + + protected void checkForConfigChanges(KeycloakSession keycloakSession) { + if (THIS_KEYCLOAK_TOKEN_ENDPOINT == null) { + THIS_KEYCLOAK_TOKEN_ENDPOINT = ((OIDCConfigurationRepresentation) new OIDCWellKnownProviderFactory() + .create(keycloakSession).getConfig()).getTokenEndpoint(); + + logger.debugf("Keycloak (this) token endpoint is: %s", THIS_KEYCLOAK_TOKEN_ENDPOINT); + } + RealmModel realm = keycloakSession.realms().getRealmByName(targetOrchestratorRealm); + if (realm == null) { + logger.errorf("Target orchestrator realm not: %s", targetOrchestratorRealm); + return; + } + ORCHESTRATOR_CLIENT = getClientInRealm(realm, ORCHESTRATOR_CLIENT_ID); + + KEYCLOAK_CLIENT = getClientInRealm(realm, KEYCLOAK_CLIENT_ID); + } + + protected ClientModel getClientInRealm(RealmModel realm, String clientId) { + ClientModel client = realm.getClientByClientId(clientId); + if (client == null) { + logger.errorf("Client '%s' not found in target orchestrator realm: %s", clientId, realm.getName()); + } return client; } @@ -134,7 +162,60 @@ public class OrchestratorEventPublisherProviderFactory implements EventListenerP } @Override - public void init(Scope scope) { + public void init(Scope config) { + this.targetOrchestratorRealm = config.get("target-orchestrator-realm", DEFAULT_TARGET_ORCHESTRATOR_REALM); + logger.infof("Target orchestrator realm is: %s", targetOrchestratorRealm); + + String[] includeRealms = config.getArray("include-realms"); + if (includeRealms != null) { + for (String realm : includeRealms) { + this.interestingRealms.add(realm); + } + } else { + this.interestingRealms.addAll(DEFAULT_REALM_NAMES); + } + + String[] excludeRealms = config.getArray("exclude-realms"); + if (excludeRealms != null) { + for (String realm : excludeRealms) { + this.interestingRealms.remove(realm); + } + } + logger.infof("Interesting realms are: %s", interestingRealms); + + String[] includeTypes = config.getArray("include-admin-types"); + if (includeTypes != null) { + for (String type : includeTypes) { + this.interestingAdminEventResourceTypes.add(ResourceType.valueOf(type.toUpperCase())); + } + } else { + this.interestingAdminEventResourceTypes.addAll(DEFAULT_INTERESTING_ADMIN_EVENT_RESOURCE_TYPES); + } + + String[] excludeTypes = config.getArray("exclude-admin-types"); + if (excludeTypes != null) { + for (String type : excludeTypes) { + this.interestingAdminEventResourceTypes.remove(ResourceType.valueOf(type.toUpperCase())); + } + } + logger.infof("Interesting admin events resource types are: %s", interestingAdminEventResourceTypes); + + String[] includeEvents = config.getArray("include-events"); + if (includeEvents != null) { + for (String type : includeEvents) { + this.interestingEvents.add(EventType.valueOf(type.toUpperCase())); + } + } else { + this.interestingEvents.addAll(DEFAULT_INTERESTING_EVENTS); + } + + String[] excludeEvents = config.getArray("exclude-events"); + if (excludeEvents != null) { + for (String type : excludeEvents) { + this.interestingEvents.remove(EventType.valueOf(type.toUpperCase())); + } + } + logger.infof("Interesting events are: %s", interestingEvents); } @Override