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`
This commit is contained in:
parent
b9ae2f9b6b
commit
fad5715a16
|
@ -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).
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <a href="mailto:mauro.mugnaini@nubisware.com">Mauro Mugnaini</a>
|
||||
*
|
||||
*/
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <a href="mailto:marco.lettere@nubisware.com">Marco Lettere</a>
|
||||
|
@ -22,69 +27,140 @@ public class OrchestratorEventPublisherProvider extends AbstractEventPublisher
|
|||
|
||||
public static final Logger logger = Logger.getLogger(OrchestratorEventPublisherProvider.class);
|
||||
|
||||
private static final Set<EventType> 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<ResourceType> interestingAdminEventResourceTypes;
|
||||
private final Set<EventType> 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<ResourceType> interestingAdminEventResourceTypes, Set<EventType> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 <a href="mailto:marco.lettere@nubisware.com">Marco Lettere</a>
|
||||
|
@ -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<String> DEFAULT_REALM_NAMES;
|
||||
private static final Set<EventType> DEFAULT_INTERESTING_EVENTS;
|
||||
private static final Set<ResourceType> 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<String> interestingRealms = new HashSet<>();
|
||||
private Set<EventType> interestingEvents = new HashSet<>();
|
||||
private Set<ResourceType> 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
|
||||
|
|
Loading…
Reference in New Issue