diff --git a/src/main/java/sh/libre/scim/core/Adapter.java b/src/main/java/sh/libre/scim/core/Adapter.java new file mode 100644 index 0000000000..6318ebc3af --- /dev/null +++ b/src/main/java/sh/libre/scim/core/Adapter.java @@ -0,0 +1,110 @@ +package sh.libre.scim.core; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import javax.ws.rs.NotFoundException; + +import org.jboss.logging.Logger; +import org.keycloak.models.RoleMapperModel; + +import sh.libre.scim.jpa.ScimResource; + +public abstract class Adapter { + + protected final Logger LOGGER; + protected final String realmId; + protected final String type; + protected final String componentId; + protected final EntityManager em; + + protected String id; + protected String externalId; + + public Adapter(String realmId, String componentId, EntityManager em, String type, Logger logger) { + this.realmId = realmId; + this.componentId = componentId; + this.em = em; + this.type = type; + this.LOGGER = logger; + } + + public String getId() { + return id; + } + + public void setId(String id) { + if (this.id == null) { + this.id = id; + } + } + + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + if (this.externalId == null) { + this.externalId = externalId; + } + } + + public String getSCIMEndpoint() { + return type + "s"; + } + + public ScimResource toMapping() { + var entity = new ScimResource(); + entity.setType(type); + entity.setId(id); + entity.setExternalId(externalId); + entity.setComponentId(componentId); + entity.setRealmId(realmId); + return entity; + } + + public TypedQuery query(String query, String id) { + return this.em + .createNamedQuery(query, ScimResource.class) + .setParameter("type", type) + .setParameter("realmId", realmId) + .setParameter("componentId", componentId) + .setParameter("id", id); + } + + public ScimResource getMapping() { + try { + if (this.id != null) { + return this.query("findById", id).getSingleResult(); + } + if (this.externalId != null) { + return this.query("findByExternalId", externalId).getSingleResult(); + } + } catch (NotFoundException e) { + } catch (Exception e) { + LOGGER.error(e); + } + + return null; + } + + public void saveMapping() { + this.em.persist(toMapping()); + } + + public void deleteMapping() { + this.em.remove(this.toMapping()); + } + + public abstract void apply(M model); + + public abstract void apply(S resource); + + public abstract void apply(ScimResource resource); + + public abstract S toSCIM(Boolean addMeta); + + public abstract Boolean entityExists(); + + public abstract Boolean tryToMap(); + +} diff --git a/src/main/java/sh/libre/scim/core/ScimClient.java b/src/main/java/sh/libre/scim/core/ScimClient.java index 72581278fc..66e71d56cf 100644 --- a/src/main/java/sh/libre/scim/core/ScimClient.java +++ b/src/main/java/sh/libre/scim/core/ScimClient.java @@ -1,42 +1,34 @@ package sh.libre.scim.core; -import com.unboundid.scim2.client.ScimService; -import com.unboundid.scim2.common.exceptions.ScimException; -import com.unboundid.scim2.common.types.Email; -import com.unboundid.scim2.common.types.Meta; -import com.unboundid.scim2.common.types.Name; -import com.unboundid.scim2.common.types.UserResource; -import io.github.resilience4j.core.IntervalFunction; -import io.github.resilience4j.retry.RetryConfig; -import io.github.resilience4j.retry.RetryRegistry; -import java.net.URI; -import java.util.ArrayList; -import java.lang.RuntimeException; import javax.persistence.EntityManager; import javax.persistence.NoResultException; -import javax.persistence.TypedQuery; import javax.ws.rs.client.Client; +import com.unboundid.scim2.client.ScimService; +import com.unboundid.scim2.common.ScimResource; +import com.unboundid.scim2.common.exceptions.ScimException; +import com.unboundid.scim2.common.types.UserResource; + import org.jboss.logging.Logger; import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; import org.keycloak.component.ComponentModel; import org.keycloak.connections.jpa.JpaConnectionProvider; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.UserModel; -import org.keycloak.models.jpa.entities.ComponentEntity; -import org.keycloak.models.jpa.entities.RealmEntity; +import org.keycloak.models.RoleMapperModel; +import org.keycloak.storage.user.SynchronizationResult; -import sh.libre.scim.jpa.ScimResource; +import io.github.resilience4j.core.IntervalFunction; +import io.github.resilience4j.retry.RetryConfig; +import io.github.resilience4j.retry.RetryRegistry; public class ScimClient { - - final private Logger LOGGER = Logger.getLogger(ScimClient.class); - final private Client client = ResteasyClientBuilder.newClient(); - final private ScimService scimService; - final private RetryRegistry registry; - final private KeycloakSession session; - final private String contentType; - final private ComponentModel model; + final protected Logger LOGGER = Logger.getLogger(ScimClient.class); + final protected Client client = ResteasyClientBuilder.newClient(); + final protected ScimService scimService; + final protected RetryRegistry registry; + final protected KeycloakSession session; + final protected String contentType; + final protected ComponentModel model; public ScimClient(ComponentModel model, KeycloakSession session) { this.model = model; @@ -57,71 +49,75 @@ public class ScimClient { registry = RetryRegistry.of(retryConfig); } - private EntityManager getEM() { + protected EntityManager getEM() { return session.getProvider(JpaConnectionProvider.class).getEntityManager(); } - private String getRealmId() { + protected String getRealmId() { return session.getContext().getRealm().getId(); } - private RealmEntity getRealmEntity() { - return getEM().getReference(RealmEntity.class, getRealmId()); + protected > A getAdapter( + Class aClass) { + try { + return aClass.getDeclaredConstructor(String.class, String.class, EntityManager.class) + .newInstance(getRealmId(), this.model.getId(), getEM()); + } catch (Exception e) { + throw new RuntimeException(e); + } } - private ComponentEntity getSPEntity() { - return getEM().getReference(ComponentEntity.class, this.model.getId()); - } - - public void createUser(UserModel kcUser) { - LOGGER.info("Create User"); - var user = toUser(kcUser); - var retry = registry.retry("create-" + kcUser.getId()); + public > void create(Class aClass, + M kcModel) { + var adapter = getAdapter(aClass); + adapter.apply(kcModel); + var retry = registry.retry("create-" + adapter.getId()); var spUser = retry.executeSupplier(() -> { try { - return scimService.createRequest("Users", user).contentType(contentType).invoke(); + return scimService.createRequest(adapter.getSCIMEndpoint(), adapter.toSCIM(false)) + .contentType(contentType).invoke(); } catch (ScimException e) { throw new RuntimeException(e); } }); - var scimUser = toScimUser(spUser); - scimUser.setLocalId(kcUser.getId()); - getEM().persist(scimUser); - } + adapter.apply(spUser); + adapter.saveMapping(); + }; - public void replaceUser(UserModel kcUser) { - LOGGER.info("Replace User"); + public > void replace(Class aClass, + M kcModel) { + var adapter = getAdapter(aClass); try { - var resource = querUserById(kcUser.getId()); - var user = toUser(kcUser); - user.setId(resource.getRemoteId()); - var meta = new Meta(); - var uri = new URI("Users/" + user.getId()); - meta.setLocation(uri); - user.setMeta(meta); - var retry = registry.retry("replace-" + kcUser.getId()); + adapter.apply(kcModel); + var resource = adapter.query("findById", adapter.getId()).getSingleResult(); + adapter.apply(resource); + var retry = registry.retry("replace-" + adapter.getId()); retry.executeSupplier(() -> { try { - return scimService.replaceRequest(user).contentType(contentType).invoke(); + return scimService.replaceRequest(adapter.toSCIM(true)).contentType(contentType).invoke(); } catch (ScimException e) { throw new RuntimeException(e); } }); } catch (NoResultException e) { - LOGGER.warnf("Failed to repalce user %s, scim mapping not found", kcUser.getId()); + LOGGER.warnf("failed to replace resource %s, scim mapping not found", adapter.getId()); } catch (Exception e) { LOGGER.error(e); } } - public void deleteUser(String userId) { - LOGGER.info("Delete User"); + public > void delete(Class aClass, + String id) { + var adapter = getAdapter(aClass); + adapter.setId(id); try { - var resource = querUserById(userId); - var retry = registry.retry("delete-" + userId); + var resource = adapter.query("findById", adapter.getId()).getSingleResult(); + adapter.apply(resource); + var retry = registry.retry("delete-" + id); retry.executeSupplier(() -> { try { - scimService.deleteRequest("Users", resource.getRemoteId()).contentType(contentType).invoke(); + scimService.deleteRequest(adapter.getSCIMEndpoint(), resource.getExternalId()) + .contentType(contentType).invoke(); } catch (ScimException e) { throw new RuntimeException(e); } @@ -129,55 +125,88 @@ public class ScimClient { }); getEM().remove(resource); } catch (NoResultException e) { - LOGGER.warnf("Failde to delete user %s, scim mapping not found", userId); + LOGGER.warnf("Failed to delete resource %s, scim mapping not found", id); } } - private TypedQuery queryUser(String query) { - return getEM() - .createNamedQuery(query, ScimResource.class) - .setParameter("realm", getRealmEntity()) - .setParameter("type", "Users") - .setParameter("serviceProvider", getSPEntity()); + public void refreshUsers(SynchronizationResult syncRes) { + LOGGER.info("Refresh Users"); + this.session.users().getUsersStream(this.session.getContext().getRealm()).forEach(kcUser -> { + LOGGER.infof("Reconciling local user %s", kcUser.getId()); + if (!kcUser.getUsername().equals("admin")) { + var adapter = getAdapter(UserAdapter.class); + adapter.apply(kcUser); + var mapping = adapter.getMapping(); + if (mapping == null) { + LOGGER.info("Creating it"); + this.create(UserAdapter.class, kcUser); + } else { + LOGGER.info("Replacing it"); + this.replace(UserAdapter.class, kcUser); + } + syncRes.increaseUpdated(); + } + }); } - private ScimResource querUserById(String id) { - return queryUser("findByLocalId").setParameter("id", id).getSingleResult(); - } + public void importUsers(SynchronizationResult syncRes) { + LOGGER.info("Import Users"); + try { + var spUsers = scimService.searchRequest("Users").contentType(contentType).invoke(UserResource.class); + for (var spUser : spUsers) { + try { + LOGGER.infof("Reconciling remote user %s", spUser.getId()); + var adapter = getAdapter(UserAdapter.class); + adapter.apply(spUser); - private ScimResource scimUser() { - var resource = new ScimResource(); - resource.setType("Users"); - resource.setRealm(getRealmEntity()); - resource.setServiceProvider(getSPEntity()); - return resource; - } + var mapping = adapter.getMapping(); + if (mapping != null) { + adapter.apply(mapping); + if (adapter.entityExists()) { + LOGGER.info("Valid mapping found, skipping"); + continue; + } else { + LOGGER.info("Delete a dangling mapping"); + adapter.deleteMapping(); + } + } - private ScimResource toScimUser(UserResource user) { - var resource = scimUser(); - resource.setRemoteId(user.getId()); - resource.setLocalId(user.getExternalId()); - return resource; - } - - private UserResource toUser(UserModel kcUser) { - var user = new UserResource(); - user.setExternalId(kcUser.getId()); - user.setUserName(kcUser.getUsername()); - var name = new Name(); - name.setGivenName(kcUser.getFirstName()); - name.setFamilyName(kcUser.getLastName()); - user.setName(name); - user.setDisplayName(kcUser.getFirstName() + " " + kcUser.getLastName()); - - var emails = new ArrayList(); - if (kcUser.getEmail() != "") { - var email = new Email().setPrimary(true).setValue(kcUser.getEmail()); - emails.add(email); + var mapped = adapter.tryToMap(); + if (mapped) { + LOGGER.info("Matched a user"); + adapter.saveMapping(); + } else { + switch (this.model.get("sync-import-action")) { + case "CREATE_LOCAL": + LOGGER.info("Create local user"); + adapter.createEntity(); + adapter.saveMapping(); + syncRes.increaseAdded(); + break; + case "DELETE_REMOTE": + LOGGER.info("Delete remote user"); + scimService.deleteRequest("Users", spUser.getId()).contentType(contentType) + .invoke(); + syncRes.increaseRemoved(); + break; + } + } + } catch (Exception e) { + syncRes.increaseFailed(); + } + } + } catch (ScimException e) { + throw new RuntimeException(e); + } + } + + public void sync(SynchronizationResult syncRes) { + if (this.model.get("sync-import", false)) { + this.importUsers(syncRes); + } + if (this.model.get("sync-refresh", false)) { + this.refreshUsers(syncRes); } - user.setEmails(emails); - user.setActive(kcUser.isEnabled()); - return user; } public void close() { diff --git a/src/main/java/sh/libre/scim/core/UserAdapter.java b/src/main/java/sh/libre/scim/core/UserAdapter.java new file mode 100644 index 0000000000..0ed5328b52 --- /dev/null +++ b/src/main/java/sh/libre/scim/core/UserAdapter.java @@ -0,0 +1,166 @@ +package sh.libre.scim.core; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; + +import javax.persistence.EntityManager; + +import com.unboundid.scim2.common.types.Email; +import com.unboundid.scim2.common.types.Meta; +import com.unboundid.scim2.common.types.UserResource; + +import org.jboss.logging.Logger; +import org.keycloak.models.UserModel; +import org.keycloak.models.jpa.entities.UserEntity; +import org.keycloak.models.utils.KeycloakModelUtils; + +import sh.libre.scim.jpa.ScimResource; + +public class UserAdapter extends Adapter { + + private String username; + private String displayName; + private String email; + private Boolean active; + + public UserAdapter(String realmId, String componentId, EntityManager em) { + super(realmId, componentId, em, "User", Logger.getLogger(UserAdapter.class)); + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + if (this.username == null) { + this.username = username; + } + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + if (this.displayName == null) { + this.displayName = displayName; + } + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + if (this.email == null) { + this.email = email; + } + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + if (this.active == null) { + this.active = active; + } + } + + @Override + public void apply(UserModel user) { + setId(user.getId()); + setUsername(user.getUsername()); + if (user.getFirstName() != null && user.getLastName() != null) { + setDisplayName(String.format("%s %s", user.getFirstName(), user.getLastName())); + } else if (user.getFirstName() != null) { + setDisplayName(user.getFirstName()); + } else if (user.getLastName() != null) { + setDisplayName(user.getLastName()); + } + setEmail(user.getEmail()); + setActive(user.isEnabled()); + } + + @Override + public void apply(UserResource user) { + setExternalId(user.getId()); + setUsername(user.getUserName()); + setDisplayName(user.getDisplayName()); + setActive(user.getActive()); + if (user.getEmails().size() > 0) { + setEmail(user.getEmails().get(0).getValue()); + } + } + + @Override + public void apply(ScimResource mapping) { + setId(mapping.getId()); + setExternalId(mapping.getExternalId()); + } + + @Override + public UserResource toSCIM(Boolean addMeta) { + var user = new UserResource(); + user.setExternalId(id); + user.setUserName(username); + user.setId(externalId); + user.setDisplayName(displayName); + var emails = new ArrayList(); + if (email != null) { + emails.add( + new Email().setPrimary(true).setValue(email)); + } + user.setEmails(emails); + user.setActive(active); + if (addMeta) { + var meta = new Meta(); + try { + var uri = new URI("Users/" + externalId); + meta.setLocation(uri); + } catch (URISyntaxException e) { + } + user.setMeta(meta); + } + return user; + } + + public UserEntity createEntity() { + var kcUser = new UserEntity(); + kcUser.setId(KeycloakModelUtils.generateId()); + kcUser.setRealmId(realmId); + kcUser.setUsername(username); + kcUser.setEmail(email, false); + this.em.persist(kcUser); + this.id = kcUser.getId(); + return kcUser; + } + + public Boolean entityExists() { + if (this.id == null) { + return false; + } + var user = this.em.find(UserEntity.class, this.id); + if (user != null) { + return true; + } + return false; + } + + public Boolean tryToMap() { + try { + var userEntity = this.em + .createQuery("select u from UserEntity u where u.username=:username or u.email=:email", + UserEntity.class) + .setParameter("username", username) + .setParameter("email", email) + .getSingleResult(); + + setId(userEntity.getId()); + return true; + } catch (Exception e) { + } + return false; + } +} diff --git a/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java b/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java index eb568cc019..a64c82330b 100644 --- a/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java +++ b/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java @@ -11,6 +11,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.UserModel; import sh.libre.scim.core.ScimDispatcher; +import sh.libre.scim.core.UserAdapter; public class ScimEventListenerProvider implements EventListenerProvider { final Logger LOGGER = Logger.getLogger(ScimEventListenerProvider.class); @@ -30,14 +31,14 @@ public class ScimEventListenerProvider implements EventListenerProvider { public void onEvent(Event event) { if (event.getType() == EventType.REGISTER) { var user = getUser(event.getUserId()); - dispatcher.run((client) -> client.createUser(user)); + dispatcher.run((client) -> client.create(UserAdapter.class, user)); } if (event.getType() == EventType.UPDATE_EMAIL || event.getType() == EventType.UPDATE_PROFILE) { var user = getUser(event.getUserId()); - dispatcher.run((client) -> client.replaceUser(user)); + dispatcher.run((client) -> client.replace(UserAdapter.class, user)); } if (event.getType() == EventType.DELETE_ACCOUNT) { - dispatcher.run((client) -> client.deleteUser(event.getUserId())); + dispatcher.run((client) -> client.delete(UserAdapter.class, event.getUserId())); } } @@ -49,14 +50,21 @@ public class ScimEventListenerProvider implements EventListenerProvider { if (event.getOperationType() == OperationType.CREATE) { // session.getTransactionManager().rollback(); var user = getUser(userId); - dispatcher.run((client) -> client.createUser(user)); + dispatcher.run((client) -> client.create(UserAdapter.class, user)); } if (event.getOperationType() == OperationType.UPDATE) { var user = getUser(userId); - dispatcher.run((client) -> client.replaceUser(user)); + dispatcher.run((client) -> client.replace(UserAdapter.class, user)); } if (event.getOperationType() == OperationType.DELETE) { - dispatcher.run((client) -> client.deleteUser(userId)); + dispatcher.run((client) -> client.delete(UserAdapter.class, userId)); + } + } + if (event.getResourceType() == ResourceType.COMPONENT) { + if (event.getOperationType() == OperationType.CREATE + || (event.getOperationType() == OperationType.UPDATE)) { + LOGGER.infof("%s %s", event.getResourcePath(), event.getOperationType()); + // dispatcher.run((client) -> client.syncUsers()); } } } diff --git a/src/main/java/sh/libre/scim/jpa/ScimResource.java b/src/main/java/sh/libre/scim/jpa/ScimResource.java index 94d041e206..a9f69581cd 100644 --- a/src/main/java/sh/libre/scim/jpa/ScimResource.java +++ b/src/main/java/sh/libre/scim/jpa/ScimResource.java @@ -4,57 +4,67 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.IdClass; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; import javax.persistence.NamedQuery; import javax.persistence.NamedQueries; import javax.persistence.Table; -import org.keycloak.models.jpa.entities.ComponentEntity; -import org.keycloak.models.jpa.entities.RealmEntity; - @Entity @IdClass(ScimResourceId.class) @Table(name = "SCIM_RESOURCE") @NamedQueries({ - @NamedQuery(name = "findByLocalId", query = "from ScimResource where realm = :realm and type = :type and serviceProvider = :serviceProvider and localId = :id"), - @NamedQuery(name = "findByRemoteId", query = "from ScimResource where realm = :realm and type = :type and serviceProvider = :serviceProvider and remoteId = :id") }) + @NamedQuery(name = "findById", query = "from ScimResource where realmId = :realmId and componentId = :componentId and type = :type and id = :id"), + @NamedQuery(name = "findByExternalId", query = "from ScimResource where realmId = :realmId and componentId = :componentId and type = :type and externalId = :id") }) public class ScimResource { @Id - @ManyToOne - @JoinColumn(name = "REALM_ID", referencedColumnName = "ID") - private RealmEntity realm; + @Column(name = "ID", nullable = false) + private String id; @Id - @ManyToOne - @JoinColumn(name = "SERVICE_PROVIDER", referencedColumnName = "ID") - private ComponentEntity serviceProvider; - + @Column(name = "REALM_ID", nullable = false) + private String realmId; + + @Id + @Column(name = "COMPONENT_ID", nullable = false) + private String componentId; + @Id @Column(name = "TYPE", nullable = false) private String type; @Id - @Column(name = "LOCAL_ID", nullable = false) - private String localId; + @Column(name = "EXTERNAL_ID", nullable = false) + private String externalId; - @Column(name = "REMOTE_ID", nullable = false) - private String remoteId; - - public RealmEntity getRealm() { - return realm; + public String getId() { + return id; } - public void setRealm(RealmEntity realm) { - this.realm = realm; + public void setId(String id) { + this.id = id; } - public ComponentEntity getServiceProvider() { - return serviceProvider; + public String getRealmId() { + return realmId; } - public void setServiceProvider(ComponentEntity serviceProvider) { - this.serviceProvider = serviceProvider; + public void setRealmId(String realmId) { + this.realmId = realmId; + } + + public String getComponentId() { + return componentId; + } + + public void setComponentId(String componentId) { + this.componentId = componentId; + } + + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; } public String getType() { @@ -65,19 +75,4 @@ public class ScimResource { this.type = type; } - public String getRemoteId() { - return remoteId; - } - - public void setRemoteId(String remoteId) { - this.remoteId = remoteId; - } - - public String getLocalId() { - return localId; - } - - public void setLocalId(String localId) { - this.localId = localId; - } } diff --git a/src/main/java/sh/libre/scim/jpa/ScimResourceId.java b/src/main/java/sh/libre/scim/jpa/ScimResourceId.java index e4eb1b3aff..7775543a54 100644 --- a/src/main/java/sh/libre/scim/jpa/ScimResourceId.java +++ b/src/main/java/sh/libre/scim/jpa/ScimResourceId.java @@ -4,35 +4,45 @@ import java.io.Serializable; import java.util.Objects; public class ScimResourceId implements Serializable { - private String realm; - private String serviceProvider; + private String id; + private String realmId; + private String componentId; private String type; - private String remoteId; + private String externalId; public ScimResourceId() { } - public ScimResourceId(String realm, String serviceProvider, String type, String remoteId) { - this.realm = realm; - this.serviceProvider = serviceProvider; - this.type = type; - this.remoteId = remoteId; + public ScimResourceId(String id, String realmId, String componentId, String type, String externalId) { + this.setId(id); + this.setRealmId(realmId); + this.setComponentId(componentId); + this.setType(type); + this.setExternalId(externalId); } - public String getRealm() { - return realm; + public String getId() { + return id; } - public void setRealm(String realm) { - this.realm = realm; + public void setId(String id) { + this.id = id; } - public String getServiceProvider() { - return serviceProvider; + public String getRealmId() { + return realmId; } - public void setServiceProvider(String serviceProvider) { - this.serviceProvider = serviceProvider; + public void setRealmId(String realmId) { + this.realmId = realmId; + } + + public String getComponentId() { + return componentId; + } + + public void setComponentId(String componentId) { + this.componentId = componentId; } public String getType() { @@ -43,12 +53,12 @@ public class ScimResourceId implements Serializable { this.type = type; } - public String getRemoteId() { - return remoteId; + public String getExternalId() { + return externalId; } - public void setRemoteId(String remoteId) { - this.remoteId = remoteId; + public void setExternalId(String externalId) { + this.externalId = externalId; } @Override @@ -58,14 +68,15 @@ public class ScimResourceId implements Serializable { if (!(other instanceof ScimResourceId)) return false; var o = (ScimResourceId) other; - return (o.realm == realm && - o.serviceProvider == serviceProvider && + return (o.id == id && + o.realmId == realmId && + o.componentId == componentId && o.type == type && - o.remoteId == remoteId); + o.externalId == externalId); } @Override public int hashCode() { - return Objects.hash(realm, serviceProvider, type, remoteId); + return Objects.hash(realmId, componentId, type, id, externalId); } } diff --git a/src/main/java/sh/libre/scim/storage/ScimStorageProvider.java b/src/main/java/sh/libre/scim/storage/ScimStorageProvider.java index 882aad58a9..19496069fe 100644 --- a/src/main/java/sh/libre/scim/storage/ScimStorageProvider.java +++ b/src/main/java/sh/libre/scim/storage/ScimStorageProvider.java @@ -1,25 +1,9 @@ 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 { +public class ScimStorageProvider implements UserStorageProvider { @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 index 1a0d953994..f1bc82d502 100644 --- a/src/main/java/sh/libre/scim/storage/ScimStorageProviderFactory.java +++ b/src/main/java/sh/libre/scim/storage/ScimStorageProviderFactory.java @@ -1,18 +1,31 @@ package sh.libre.scim.storage; +import java.util.Date; import java.util.List; import javax.ws.rs.core.MediaType; import com.unboundid.scim2.client.ScimService; +import org.jboss.logging.Logger; import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.KeycloakSessionTask; +import org.keycloak.models.RealmModel; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigurationBuilder; import org.keycloak.storage.UserStorageProviderFactory; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.storage.user.ImportSynchronization; +import org.keycloak.storage.user.SynchronizationResult; -public class ScimStorageProviderFactory implements UserStorageProviderFactory { +import sh.libre.scim.core.ScimClient; + +public class ScimStorageProviderFactory + implements UserStorageProviderFactory, ImportSynchronization { + final private Logger LOGGER = Logger.getLogger(ScimStorageProviderFactory.class); public final static String ID = "scim"; protected static final List configMetadata; static { @@ -46,11 +59,30 @@ public class ScimStorageProviderFactory implements UserStorageProviderFactory - + - + - + - + - + - - + \ No newline at end of file