fix and add config via federation
This commit is contained in:
parent
f2dbb59e0a
commit
fd672971a3
9 changed files with 131 additions and 76 deletions
|
@ -19,23 +19,24 @@ import javax.ws.rs.client.Client;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||||
|
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import sh.libre.scim.jpa.ScimResource;
|
import sh.libre.scim.jpa.ScimResource;
|
||||||
|
|
||||||
public class ScimClient {
|
public class ScimClient {
|
||||||
|
|
||||||
final Logger LOGGER = Logger.getLogger(ScimClient.class);
|
final private Logger LOGGER = Logger.getLogger(ScimClient.class);
|
||||||
final Client client = ResteasyClientBuilder.newClient();
|
final private Client client = ResteasyClientBuilder.newClient();
|
||||||
final ScimService scimService;
|
final private ScimService scimService;
|
||||||
final RetryRegistry registry;
|
final private RetryRegistry registry;
|
||||||
final String name;
|
final private String name;
|
||||||
final String realmId;
|
final private KeycloakSession session;
|
||||||
final EntityManager entityManager;
|
|
||||||
|
|
||||||
public ScimClient(String name, String url, String realmId, EntityManager entityManager) {
|
public ScimClient(String name, String url, KeycloakSession session) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.realmId = realmId;
|
|
||||||
this.entityManager = entityManager;
|
this.session = session;
|
||||||
var target = client.target(url);
|
var target = client.target(url);
|
||||||
scimService = new ScimService(target);
|
scimService = new ScimService(target);
|
||||||
|
|
||||||
|
@ -46,6 +47,14 @@ public class ScimClient {
|
||||||
registry = RetryRegistry.of(retryConfig);
|
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) {
|
public void createUser(UserModel kcUser) {
|
||||||
LOGGER.info("Create User");
|
LOGGER.info("Create User");
|
||||||
var user = toUser(kcUser);
|
var user = toUser(kcUser);
|
||||||
|
@ -58,7 +67,7 @@ public class ScimClient {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var scimUser = toScimUser(spUser);
|
var scimUser = toScimUser(spUser);
|
||||||
entityManager.persist(scimUser);
|
getEM().persist(scimUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void replaceUser(UserModel kcUser) {
|
public void replaceUser(UserModel kcUser) {
|
||||||
|
@ -80,7 +89,7 @@ public class ScimClient {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (NoResultException e) {
|
} 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) {
|
} catch (Exception e) {
|
||||||
LOGGER.error(e);
|
LOGGER.error(e);
|
||||||
}
|
}
|
||||||
|
@ -99,16 +108,16 @@ public class ScimClient {
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
});
|
});
|
||||||
entityManager.remove(resource);
|
getEM().remove(resource);
|
||||||
} catch (NoResultException e) {
|
} 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<ScimResource> queryUser(String query) {
|
private TypedQuery<ScimResource> queryUser(String query) {
|
||||||
return entityManager
|
return getEM()
|
||||||
.createNamedQuery(query, ScimResource.class)
|
.createNamedQuery(query, ScimResource.class)
|
||||||
.setParameter("realmId", realmId)
|
.setParameter("realmId", getRealmId())
|
||||||
.setParameter("type", "Users")
|
.setParameter("type", "Users")
|
||||||
.setParameter("serviceProvider", name);
|
.setParameter("serviceProvider", name);
|
||||||
}
|
}
|
||||||
|
@ -120,7 +129,7 @@ public class ScimClient {
|
||||||
private ScimResource scimUser() {
|
private ScimResource scimUser() {
|
||||||
var resource = new ScimResource();
|
var resource = new ScimResource();
|
||||||
resource.setType("Users");
|
resource.setType("Users");
|
||||||
resource.setRealmId(realmId);
|
resource.setRealmId(getRealmId());
|
||||||
resource.setServiceProvider(name);
|
resource.setServiceProvider(name);
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +1,32 @@
|
||||||
package sh.libre.scim.core;
|
package sh.libre.scim.core;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
|
|
||||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
|
import sh.libre.scim.storage.ScimStorageProviderFactory;
|
||||||
|
|
||||||
public class ScimDispatcher {
|
public class ScimDispatcher {
|
||||||
final KeycloakSession session;
|
final private KeycloakSession session;
|
||||||
final EntityManager entityManager;
|
final private Logger LOGGER = Logger.getLogger(ScimDispatcher.class);
|
||||||
final Logger LOGGER = Logger.getLogger(ScimDispatcher.class);
|
|
||||||
ArrayList<ScimClient> clients = new ArrayList<ScimClient>();
|
|
||||||
|
|
||||||
public ScimDispatcher(KeycloakSession session) {
|
public ScimDispatcher(KeycloakSession session) {
|
||||||
this.session = 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<ScimClient>();
|
|
||||||
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<ScimClient> f) {
|
public void run(Consumer<ScimClient> f) {
|
||||||
for (var client : clients) {
|
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);
|
f.accept(client);
|
||||||
}
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,10 @@ public class ScimEventListenerProvider implements EventListenerProvider {
|
||||||
public ScimEventListenerProvider(KeycloakSession session) {
|
public ScimEventListenerProvider(KeycloakSession session) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
dispatcher = new ScimDispatcher(session);
|
dispatcher = new ScimDispatcher(session);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
dispatcher.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -45,9 +43,6 @@ public class ScimEventListenerProvider implements EventListenerProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(AdminEvent event, boolean includeRepresentation) {
|
public void onEvent(AdminEvent event, boolean includeRepresentation) {
|
||||||
if (event.getResourceType() == ResourceType.CLIENT) {
|
|
||||||
dispatcher.reloadClients();
|
|
||||||
}
|
|
||||||
if (event.getResourceType() == ResourceType.USER) {
|
if (event.getResourceType() == ResourceType.USER) {
|
||||||
var userId = event.getResourcePath().replace("users/", "");
|
var userId = event.getResourcePath().replace("users/", "");
|
||||||
LOGGER.infof("%s %s", userId, event.getOperationType());
|
LOGGER.infof("%s %s", userId, event.getOperationType());
|
||||||
|
|
|
@ -35,16 +35,16 @@ public class ScimResource {
|
||||||
@Column(name = "LOCAL_ID", nullable = false)
|
@Column(name = "LOCAL_ID", nullable = false)
|
||||||
private String localId;
|
private String localId;
|
||||||
|
|
||||||
public ScimResource() {
|
// public ScimResource() {
|
||||||
}
|
// }
|
||||||
|
|
||||||
public ScimResource(String realmId, String serviceProvider, String type, String remoteId, String localId) {
|
// public ScimResource(String realmId, String serviceProvider, String type, String remoteId, String localId) {
|
||||||
this.realmId = realmId;
|
// this.realmId = realmId;
|
||||||
this.serviceProvider = serviceProvider;
|
// this.serviceProvider = serviceProvider;
|
||||||
this.type = type;
|
// this.type = type;
|
||||||
this.remoteId = remoteId;
|
// this.remoteId = remoteId;
|
||||||
this.localId = localId;
|
// this.localId = localId;
|
||||||
}
|
// }
|
||||||
|
|
||||||
public String getRealmId() {
|
public String getRealmId() {
|
||||||
return realmId;
|
return realmId;
|
||||||
|
|
|
@ -4,13 +4,13 @@ import java.util.List;
|
||||||
|
|
||||||
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
|
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Collections;
|
||||||
|
|
||||||
public class ScimResourceProvider implements JpaEntityProvider {
|
public class ScimResourceProvider implements JpaEntityProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Class<?>> getEntities() {
|
public List<Class<?>> getEntities() {
|
||||||
return Arrays.asList(ScimResource.class);
|
return Collections.singletonList(ScimResource.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
25
src/main/java/sh/libre/scim/storage/ScimStorageProvider.java
Normal file
25
src/main/java/sh/libre/scim/storage/ScimStorageProvider.java
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<ScimStorageProvider> {
|
||||||
|
public final static String ID = "scim";
|
||||||
|
protected static final List<ProviderConfigProperty> 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<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return configMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
10
src/main/resources/META-INF/jboss-deployment-structure.xml
Normal file
10
src/main/resources/META-INF/jboss-deployment-structure.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<jboss-deployment-structure>
|
||||||
|
<deployment>
|
||||||
|
<dependencies>
|
||||||
|
<module name="org.keycloak.keycloak-services" />
|
||||||
|
<module name="org.keycloak.keycloak-model-jpa" />
|
||||||
|
<module name="org.hibernate" />
|
||||||
|
</dependencies>
|
||||||
|
</deployment>
|
||||||
|
</jboss-deployment-structure>
|
|
@ -0,0 +1 @@
|
||||||
|
sh.libre.scim.storage.ScimStorageProviderFactory
|
Loading…
Reference in a new issue