diff --git a/src/main/java/sh/libre/scim/core/ScimClient.java b/src/main/java/sh/libre/scim/core/ScimClient.java index 1e1533b593..daaa4b00a3 100644 --- a/src/main/java/sh/libre/scim/core/ScimClient.java +++ b/src/main/java/sh/libre/scim/core/ScimClient.java @@ -19,23 +19,24 @@ import javax.ws.rs.client.Client; import org.jboss.logging.Logger; import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; +import org.keycloak.connections.jpa.JpaConnectionProvider; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.UserModel; import sh.libre.scim.jpa.ScimResource; public class ScimClient { - final Logger LOGGER = Logger.getLogger(ScimClient.class); - final Client client = ResteasyClientBuilder.newClient(); - final ScimService scimService; - final RetryRegistry registry; - final String name; - final String realmId; - final EntityManager entityManager; + final private Logger LOGGER = Logger.getLogger(ScimClient.class); + final private Client client = ResteasyClientBuilder.newClient(); + final private ScimService scimService; + final private RetryRegistry registry; + final private String name; + final private KeycloakSession session; - public ScimClient(String name, String url, String realmId, EntityManager entityManager) { + public ScimClient(String name, String url, KeycloakSession session) { this.name = name; - this.realmId = realmId; - this.entityManager = entityManager; + + this.session = session; var target = client.target(url); scimService = new ScimService(target); @@ -46,6 +47,14 @@ public class ScimClient { registry = RetryRegistry.of(retryConfig); } + private EntityManager getEM() { + return session.getProvider(JpaConnectionProvider.class).getEntityManager(); + } + + private String getRealmId() { + return session.getContext().getRealm().getId(); + } + public void createUser(UserModel kcUser) { LOGGER.info("Create User"); var user = toUser(kcUser); @@ -58,7 +67,7 @@ public class ScimClient { } }); var scimUser = toScimUser(spUser); - entityManager.persist(scimUser); + getEM().persist(scimUser); } public void replaceUser(UserModel kcUser) { @@ -80,7 +89,7 @@ public class ScimClient { } }); } catch (NoResultException e) { - LOGGER.warnf("Failde to replce user %s, scim mapping not found", kcUser.getId()); + LOGGER.warnf("Failde to repalce user %s, scim mapping not found", kcUser.getId()); } catch (Exception e) { LOGGER.error(e); } @@ -99,16 +108,16 @@ public class ScimClient { } return ""; }); - entityManager.remove(resource); + getEM().remove(resource); } catch (NoResultException e) { - LOGGER.warnf("Failde to replce user %s, scim mapping not found", userId); + LOGGER.warnf("Failde to delete user %s, scim mapping not found", userId); } } private TypedQuery queryUser(String query) { - return entityManager + return getEM() .createNamedQuery(query, ScimResource.class) - .setParameter("realmId", realmId) + .setParameter("realmId", getRealmId()) .setParameter("type", "Users") .setParameter("serviceProvider", name); } @@ -120,7 +129,7 @@ public class ScimClient { private ScimResource scimUser() { var resource = new ScimResource(); resource.setType("Users"); - resource.setRealmId(realmId); + resource.setRealmId(getRealmId()); resource.setServiceProvider(name); return resource; } diff --git a/src/main/java/sh/libre/scim/core/ScimDispatcher.java b/src/main/java/sh/libre/scim/core/ScimDispatcher.java index 40bef841e5..178976ab84 100644 --- a/src/main/java/sh/libre/scim/core/ScimDispatcher.java +++ b/src/main/java/sh/libre/scim/core/ScimDispatcher.java @@ -1,58 +1,32 @@ package sh.libre.scim.core; -import java.util.ArrayList; import java.util.function.Consumer; import org.jboss.logging.Logger; -import javax.persistence.EntityManager; - -import org.keycloak.connections.jpa.JpaConnectionProvider; import org.keycloak.models.KeycloakSession; +import sh.libre.scim.storage.ScimStorageProviderFactory; + public class ScimDispatcher { - final KeycloakSession session; - final EntityManager entityManager; - final Logger LOGGER = Logger.getLogger(ScimDispatcher.class); - ArrayList clients = new ArrayList(); + final private KeycloakSession session; + final private Logger LOGGER = Logger.getLogger(ScimDispatcher.class); public ScimDispatcher(KeycloakSession session) { this.session = session; - entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager(); - reloadClients(); - } - - public void reloadClients() { - close(); - LOGGER.info("Cleared SCIM Clients"); - var realm = session.getContext().getRealm(); - clients = new ArrayList(); - var kcClients = session.clients().getClientsStream(realm); - for (var kcClient : kcClients.toList()) { - var endpoint = kcClient.getAttribute("scim-endpoint"); - var name = kcClient.getAttribute("scim-name"); - if (endpoint != "") { - if (name == "") { - name = kcClient.getName(); - } - clients.add(new ScimClient( - name, - endpoint, - realm.getId(), - entityManager)); - LOGGER.infof("Added %s SCIM Client (%s)", name, endpoint); - } - } - - } - - public void close() { - for (var client : clients) { - client.close(); - } } public void run(Consumer f) { - for (var client : clients) { - f.accept(client); - } + session.getContext().getRealm().getComponentsStream() + .filter((m) -> { + return ScimStorageProviderFactory.ID.equals(m.getProviderId()); + }) + .forEach(m -> { + LOGGER.infof("%s %s %s %s", m.getId(), m.getName(), m.getProviderId(), m.getProviderType()); + var client = new ScimClient(m.getName(), m.get("endpoint"), session); + try { + f.accept(client); + } finally { + client.close(); + } + }); } } diff --git a/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java b/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java index ff23030ed2..eb568cc019 100644 --- a/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java +++ b/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java @@ -20,12 +20,10 @@ public class ScimEventListenerProvider implements EventListenerProvider { public ScimEventListenerProvider(KeycloakSession session) { this.session = session; dispatcher = new ScimDispatcher(session); - } @Override public void close() { - dispatcher.close(); } @Override @@ -45,9 +43,6 @@ public class ScimEventListenerProvider implements EventListenerProvider { @Override public void onEvent(AdminEvent event, boolean includeRepresentation) { - if (event.getResourceType() == ResourceType.CLIENT) { - dispatcher.reloadClients(); - } if (event.getResourceType() == ResourceType.USER) { var userId = event.getResourcePath().replace("users/", ""); LOGGER.infof("%s %s", userId, event.getOperationType()); diff --git a/src/main/java/sh/libre/scim/jpa/ScimResource.java b/src/main/java/sh/libre/scim/jpa/ScimResource.java index 0034e257eb..a0e99225ff 100644 --- a/src/main/java/sh/libre/scim/jpa/ScimResource.java +++ b/src/main/java/sh/libre/scim/jpa/ScimResource.java @@ -35,16 +35,16 @@ public class ScimResource { @Column(name = "LOCAL_ID", nullable = false) private String localId; - public ScimResource() { - } + // public ScimResource() { + // } - public ScimResource(String realmId, String serviceProvider, String type, String remoteId, String localId) { - this.realmId = realmId; - this.serviceProvider = serviceProvider; - this.type = type; - this.remoteId = remoteId; - this.localId = localId; - } + // public ScimResource(String realmId, String serviceProvider, String type, String remoteId, String localId) { + // this.realmId = realmId; + // this.serviceProvider = serviceProvider; + // this.type = type; + // this.remoteId = remoteId; + // this.localId = localId; + // } public String getRealmId() { return realmId; diff --git a/src/main/java/sh/libre/scim/jpa/ScimResourceProvider.java b/src/main/java/sh/libre/scim/jpa/ScimResourceProvider.java index 3fcd97bd3c..5da5880085 100644 --- a/src/main/java/sh/libre/scim/jpa/ScimResourceProvider.java +++ b/src/main/java/sh/libre/scim/jpa/ScimResourceProvider.java @@ -4,13 +4,13 @@ import java.util.List; import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider; -import java.util.Arrays; +import java.util.Collections; public class ScimResourceProvider implements JpaEntityProvider { @Override public List> getEntities() { - return Arrays.asList(ScimResource.class); + return Collections.singletonList(ScimResource.class); } @Override diff --git a/src/main/java/sh/libre/scim/storage/ScimStorageProvider.java b/src/main/java/sh/libre/scim/storage/ScimStorageProvider.java new file mode 100644 index 0000000000..882aad58a9 --- /dev/null +++ b/src/main/java/sh/libre/scim/storage/ScimStorageProvider.java @@ -0,0 +1,25 @@ +package sh.libre.scim.storage; + +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.user.UserRegistrationProvider; + +public class ScimStorageProvider implements UserStorageProvider, UserRegistrationProvider { + @Override + public void close() { + } + + @Override + public UserModel addUser(RealmModel realm, String username) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean removeUser(RealmModel realm, UserModel user) { + // TODO Auto-generated method stub + return false; + } + +} diff --git a/src/main/java/sh/libre/scim/storage/ScimStorageProviderFactory.java b/src/main/java/sh/libre/scim/storage/ScimStorageProviderFactory.java new file mode 100644 index 0000000000..d9a9b57f52 --- /dev/null +++ b/src/main/java/sh/libre/scim/storage/ScimStorageProviderFactory.java @@ -0,0 +1,41 @@ +package sh.libre.scim.storage; + +import java.util.List; + +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderConfigurationBuilder; +import org.keycloak.storage.UserStorageProviderFactory; + +public class ScimStorageProviderFactory implements UserStorageProviderFactory { + public final static String ID = "scim"; + protected static final List configMetadata; + static { + configMetadata = ProviderConfigurationBuilder.create() + .property() + .name("endpoint") + .type(ProviderConfigProperty.STRING_TYPE) + .label("SCIM 2.0 endpoint") + .helpText("External SCIM 2.0 base " + + "URL (/ServiceProviderConfig /Schemas and /ResourcesTypes should be accessible)") + .add() + .build(); + } + + @Override + public ScimStorageProvider create(KeycloakSession session, ComponentModel model) { + return new ScimStorageProvider(); + } + + @Override + public String getId() { + return ID; + } + + @Override + public List getConfigProperties() { + return configMetadata; + } + +} diff --git a/src/main/resources/META-INF/jboss-deployment-structure.xml b/src/main/resources/META-INF/jboss-deployment-structure.xml new file mode 100644 index 0000000000..42007fe638 --- /dev/null +++ b/src/main/resources/META-INF/jboss-deployment-structure.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory b/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory new file mode 100644 index 0000000000..255bddacd6 --- /dev/null +++ b/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory @@ -0,0 +1 @@ +sh.libre.scim.storage.ScimStorageProviderFactory