fix and add config via federation

This commit is contained in:
Hugo Renard 2022-02-07 15:43:09 +01:00
parent f2dbb59e0a
commit fd672971a3
Signed by: hougo
GPG key ID: 3A285FD470209C59
9 changed files with 131 additions and 76 deletions

View file

@ -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<ScimResource> 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;
}

View file

@ -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<ScimClient> clients = new ArrayList<ScimClient>();
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<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) {
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();
}
});
}
}

View file

@ -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());

View file

@ -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;

View file

@ -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<Class<?>> getEntities() {
return Arrays.asList(ScimResource.class);
return Collections.singletonList(ScimResource.class);
}
@Override

View 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;
}
}

View file

@ -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;
}
}

View 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>

View file

@ -0,0 +1 @@
sh.libre.scim.storage.ScimStorageProviderFactory