package org.gcube.keycloak.event; 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 * @author Mauro Mugnaini */ public class OrchestratorEventPublisherProviderFactory implements EventListenerProviderFactory { private static final Logger logger = Logger.getLogger(OrchestratorEventPublisherProviderFactory.class); 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 = 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); private static String THIS_KEYCLOAK_TOKEN_ENDPOINT; private static ClientModel ORCHESTRATOR_CLIENT; private static ClientModel KEYCLOAK_CLIENT; private String targetOrchestratorRealm; private Set interestingRealms = new HashSet<>(); private Set interestingEvents = new HashSet<>(); private Set interestingAdminEventResourceTypes = new HashSet<>(); 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 public void close() { } @Override 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); } } 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; }; }; } } 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(); } } 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; } @Override public String getId() { return "orchestrator-event-publisher"; } @Override 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 public void postInit(KeycloakSessionFactory keycloakSessionFactory) { } }