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

View file

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

View file

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

View file

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

View file

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

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