feat: update to keycloak 23 + varrious fixes
This commit is contained in:
parent
7163d2a0f0
commit
182824a3b4
12 changed files with 153 additions and 151 deletions
28
build.gradle
28
build.gradle
|
@ -1,13 +1,13 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.2'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||
}
|
||||
|
||||
group = 'sh.libre.scim'
|
||||
version = '1.0-SNAPSHOT'
|
||||
description = 'keycloak-scim'
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_11
|
||||
java.sourceCompatibility = JavaVersion.VERSION_17
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
|
@ -15,20 +15,12 @@ repositories {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'org.keycloak:keycloak-core:18.0.0'
|
||||
compileOnly 'org.keycloak:keycloak-server-spi:18.0.0'
|
||||
compileOnly 'org.keycloak:keycloak-server-spi-private:18.0.0'
|
||||
compileOnly 'org.keycloak:keycloak-services:18.0.0'
|
||||
compileOnly 'org.keycloak:keycloak-model-jpa:18.0.0'
|
||||
implementation 'io.github.resilience4j:resilience4j-retry:1.7.1'
|
||||
implementation 'de.captaingoldfish:scim-sdk-common:1.15.3'
|
||||
|
||||
implementation files('/home/marcportabella/Documents/totmicro/Repos/keycloak-scim-aws/scim-sdk-client-1.15.4-SNAPSHOT.jar')
|
||||
//implementation 'de.captaingoldfish:scim-sdk-client:1.15.3'
|
||||
implementation('org.wildfly.client:wildfly-client-config:1.0.1.Final') {
|
||||
transitive false
|
||||
}
|
||||
implementation('org.jboss.resteasy:resteasy-client:4.7.6.Final') {
|
||||
transitive false
|
||||
}
|
||||
compileOnly 'org.keycloak:keycloak-core:23.0.4'
|
||||
compileOnly 'org.keycloak:keycloak-server-spi:23.0.4'
|
||||
compileOnly 'org.keycloak:keycloak-server-spi-private:23.0.4'
|
||||
compileOnly 'org.keycloak:keycloak-services:23.0.4'
|
||||
compileOnly 'org.keycloak:keycloak-model-jpa:23.0.4'
|
||||
implementation 'io.github.resilience4j:resilience4j-retry:2.2.0'
|
||||
implementation 'de.captaingoldfish:scim-sdk-common:1.21.1'
|
||||
implementation 'de.captaingoldfish:scim-sdk-client:1.21.1'
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ services:
|
|||
ports:
|
||||
- 5432:5432
|
||||
keycloak:
|
||||
image: quay.io/keycloak/keycloak:18.0.0
|
||||
image: quay.io/keycloak/keycloak:23.0.3
|
||||
build: .
|
||||
command: start-dev
|
||||
volumes:
|
||||
|
@ -23,6 +23,7 @@ services:
|
|||
KC_DB_PASSWORD: keycloak
|
||||
KEYCLOAK_ADMIN: admin
|
||||
KEYCLOAK_ADMIN_PASSWORD: admin
|
||||
KC_LOG_LEVEL: INFO,sh.libre.scim:debug,de.captaingoldfish.scim:debug
|
||||
ports:
|
||||
- 127.0.0.1:8080:8080
|
||||
depends_on:
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.2'
|
||||
}
|
||||
|
||||
group = 'sh.libre.scim'
|
||||
version = '1.0-SNAPSHOT'
|
||||
description = 'keycloak-scim'
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_11
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'org.keycloak:keycloak-core:18.0.0'
|
||||
compileOnly 'org.keycloak:keycloak-server-spi:18.0.0'
|
||||
compileOnly 'org.keycloak:keycloak-server-spi-private:18.0.0'
|
||||
compileOnly 'org.keycloak:keycloak-services:18.0.0'
|
||||
compileOnly 'org.keycloak:keycloak-model-jpa:18.0.0'
|
||||
implementation 'io.github.resilience4j:resilience4j-retry:1.7.1'
|
||||
|
||||
compileOnly 'org.wildfly.client:wildfly-client-config:1.0.1.Final'
|
||||
compileOnly 'org.jboss.resteasy:resteasy-client:4.7.6.Final'
|
||||
compileOnly 'org.jboss.resteasy:resteasy-client-api:4.7.6.Final'
|
||||
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveClassifier.set('all-legacy')
|
||||
}
|
|
@ -2,10 +2,9 @@ package sh.libre.scim.core;
|
|||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.NoResultException;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||
|
@ -61,7 +60,7 @@ public abstract class Adapter<M extends RoleMapperModel, S extends ResourceNode>
|
|||
}
|
||||
|
||||
public String getSCIMEndpoint() {
|
||||
return type + "s";
|
||||
return "/" + type + "s";
|
||||
}
|
||||
|
||||
public ScimResource toMapping() {
|
||||
|
@ -95,12 +94,8 @@ public abstract class Adapter<M extends RoleMapperModel, S extends ResourceNode>
|
|||
if (this.externalId != null) {
|
||||
return this.query("findByExternalId", externalId).getSingleResult();
|
||||
}
|
||||
} catch (NotFoundException e) {
|
||||
} catch (NoResultException e) {
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -124,7 +119,7 @@ public abstract class Adapter<M extends RoleMapperModel, S extends ResourceNode>
|
|||
|
||||
public abstract Class<S> getResourceClass();
|
||||
|
||||
public abstract S toSCIM(Boolean addMeta);
|
||||
public abstract S toSCIM();
|
||||
|
||||
public abstract Boolean entityExists();
|
||||
|
||||
|
|
|
@ -2,16 +2,15 @@ package sh.libre.scim.core;
|
|||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.persistence.NoResultException;
|
||||
import jakarta.persistence.NoResultException;
|
||||
import de.captaingoldfish.scim.sdk.common.resources.Group;
|
||||
import de.captaingoldfish.scim.sdk.common.resources.multicomplex.Member;
|
||||
import de.captaingoldfish.scim.sdk.common.resources.complex.Meta;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -59,44 +58,49 @@ public class GroupAdapter extends Adapter<GroupModel, Group> {
|
|||
if (groupMembers != null && groupMembers.size() > 0) {
|
||||
this.members = new HashSet<String>();
|
||||
for (var groupMember : groupMembers) {
|
||||
try {
|
||||
var userMapping = this.query("findByExternalId", groupMember.getValue().get(), "User")
|
||||
.getSingleResult();
|
||||
this.members.add(userMapping.getId());
|
||||
} catch (NoResultException e) {
|
||||
LOGGER.warnf("member %s not found for scim group %s", groupMember.getValue().get(), group.getId().get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group toSCIM(Boolean addMeta) {
|
||||
public Group toSCIM() {
|
||||
var group = new Group();
|
||||
group.setId(externalId);
|
||||
group.setExternalId(id);
|
||||
group.setDisplayName(displayName);
|
||||
if (members.size() > 0) {
|
||||
var groupMembers = new ArrayList<Member>();
|
||||
for (var member : members) {
|
||||
var groupMember = new Member();
|
||||
try {
|
||||
var userMapping = this.query("findById", member, "User").getSingleResult();
|
||||
LOGGER.debug(userMapping.getExternalId());
|
||||
LOGGER.debug(userMapping.getId());
|
||||
groupMember.setValue(userMapping.getExternalId());
|
||||
var ref = new URI(String.format("Users/%s", userMapping.getExternalId()));
|
||||
groupMember.setRef(ref.toString());
|
||||
groupMembers.add(groupMember);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(e);
|
||||
group.addMember(groupMember);
|
||||
} catch (NoResultException e) {
|
||||
LOGGER.warnf("member %s not found for group %s", member, id);
|
||||
} catch (URISyntaxException e) {
|
||||
LOGGER.warnf("bad ref uri");
|
||||
}
|
||||
}
|
||||
group.setMembers(groupMembers);
|
||||
}
|
||||
if (addMeta) {
|
||||
var meta = new Meta();
|
||||
try {
|
||||
var uri = new URI("Groups/" + externalId);
|
||||
meta.setLocation(uri.toString());
|
||||
} catch (URISyntaxException e) {
|
||||
LOGGER.warn(e);
|
||||
}
|
||||
group.setMeta(meta);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
|
@ -114,7 +118,9 @@ public class GroupAdapter extends Adapter<GroupModel, Group> {
|
|||
|
||||
@Override
|
||||
public Boolean tryToMap() {
|
||||
var group = session.groups().getGroupsStream(realm).filter(x -> x.getName() == displayName).findFirst();
|
||||
var group = session.groups().getGroupsStream(realm).filter(
|
||||
x -> StringUtils.equals(x.getName(), externalId) || StringUtils.equals(x.getName(), displayName))
|
||||
.findFirst();
|
||||
if (group.isPresent()) {
|
||||
setId(group.get().getId());
|
||||
return true;
|
||||
|
|
|
@ -3,9 +3,9 @@ package sh.libre.scim.core;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.ws.rs.ProcessingException;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.NoResultException;
|
||||
import jakarta.ws.rs.ProcessingException;
|
||||
|
||||
import de.captaingoldfish.scim.sdk.client.ScimClientConfig;
|
||||
import de.captaingoldfish.scim.sdk.client.ScimRequestBuilder;
|
||||
|
@ -51,12 +51,11 @@ public class ScimClient {
|
|||
switch (model.get("auth-mode")) {
|
||||
case "BEARER":
|
||||
defaultHeaders.put(HttpHeaders.AUTHORIZATION,
|
||||
BearerAuthentication(model.get("auth-pass")));
|
||||
BearerAuthentication());
|
||||
break;
|
||||
case "BASIC_AUTH":
|
||||
defaultHeaders.put(HttpHeaders.AUTHORIZATION,
|
||||
BasicAuthentication(model.get("auth-user"),
|
||||
model.get("auth-pass")));
|
||||
BasicAuthentication());
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -73,10 +72,10 @@ public class ScimClient {
|
|||
registry = RetryRegistry.of(retryConfig);
|
||||
}
|
||||
|
||||
protected String BasicAuthentication(String username ,String password) {
|
||||
protected String BasicAuthentication() {
|
||||
return BasicAuth.builder()
|
||||
.username(model.get(username))
|
||||
.password(model.get(password))
|
||||
.username(model.get("auth-user"))
|
||||
.password(model.get("auth-pass"))
|
||||
.build()
|
||||
.getAuthorizationHeaderValue();
|
||||
}
|
||||
|
@ -92,17 +91,10 @@ public class ScimClient {
|
|||
.build();
|
||||
}
|
||||
|
||||
protected String BearerAuthentication(String token) {
|
||||
return "Bearer " + token ;
|
||||
protected String BearerAuthentication() {
|
||||
return "Bearer " + model.get("auth-pass") ;
|
||||
}
|
||||
|
||||
protected String genScimUrl(String scimEndpoint,String resourcePath) {
|
||||
return String.format("%s%s/%s", scimApplicationBaseUrl ,
|
||||
scimEndpoint,
|
||||
resourcePath);
|
||||
}
|
||||
|
||||
|
||||
protected EntityManager getEM() {
|
||||
return session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
||||
}
|
||||
|
@ -136,8 +128,8 @@ public class ScimClient {
|
|||
ServerResponse<S> response = retry.executeSupplier(() -> {
|
||||
try {
|
||||
return scimRequestBuilder
|
||||
.create(adapter.getResourceClass(), String.format("/" + adapter.getSCIMEndpoint()))
|
||||
.setResource(adapter.toSCIM(false))
|
||||
.create(adapter.getResourceClass(), adapter.getSCIMEndpoint())
|
||||
.setResource(adapter.toSCIM())
|
||||
.sendRequest();
|
||||
} catch ( ResponseException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -166,9 +158,8 @@ public class ScimClient {
|
|||
ServerResponse<S> response = retry.executeSupplier(() -> {
|
||||
try {
|
||||
return scimRequestBuilder
|
||||
.update(genScimUrl(adapter.getSCIMEndpoint(), adapter.getExternalId()),
|
||||
adapter.getResourceClass())
|
||||
.setResource(adapter.toSCIM(false))
|
||||
.update(adapter.getResourceClass(), adapter.getSCIMEndpoint(), adapter.getExternalId())
|
||||
.setResource(adapter.toSCIM())
|
||||
.sendRequest() ;
|
||||
} catch ( ResponseException e) {
|
||||
|
||||
|
@ -199,8 +190,7 @@ public class ScimClient {
|
|||
|
||||
ServerResponse<S> response = retry.executeSupplier(() -> {
|
||||
try {
|
||||
return scimRequestBuilder.delete(genScimUrl(adapter.getSCIMEndpoint(), adapter.getExternalId()),
|
||||
adapter.getResourceClass())
|
||||
return scimRequestBuilder.delete(adapter.getResourceClass(), adapter.getSCIMEndpoint(), adapter.getExternalId())
|
||||
.sendRequest();
|
||||
} catch (ResponseException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -247,7 +237,7 @@ public class ScimClient {
|
|||
LOGGER.info("Import");
|
||||
try {
|
||||
var adapter = getAdapter(aClass);
|
||||
ServerResponse<ListResponse<S>> response = scimRequestBuilder.list("url",adapter.getResourceClass()).get().sendRequest();
|
||||
ServerResponse<ListResponse<S>> response = scimRequestBuilder.list(adapter.getResourceClass(), adapter.getSCIMEndpoint()).get().sendRequest();
|
||||
ListResponse<S> resourceTypeListResponse = response.getResource();
|
||||
|
||||
for (var resource : resourceTypeListResponse.getListedResources()) {
|
||||
|
@ -287,9 +277,7 @@ public class ScimClient {
|
|||
case "DELETE_REMOTE":
|
||||
LOGGER.info("Delete remote resource");
|
||||
scimRequestBuilder
|
||||
.delete(genScimUrl(adapter.getSCIMEndpoint(),
|
||||
resource.getId().get()),
|
||||
adapter.getResourceClass())
|
||||
.delete(adapter.getResourceClass(), adapter.getSCIMEndpoint(), resource.getId().get())
|
||||
.sendRequest();
|
||||
syncRes.increaseRemoved();
|
||||
break;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package sh.libre.scim.core;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -21,7 +23,8 @@ public class ScimDispatcher {
|
|||
public void run(String scope, Consumer<ScimClient> f) {
|
||||
session.getContext().getRealm().getComponentsStream()
|
||||
.filter((m) -> {
|
||||
return ScimStorageProviderFactory.ID.equals(m.getProviderId()) && m.get("enabled", true)
|
||||
return StringUtils.equals(ScimStorageProviderFactory.ID, m.getProviderId())
|
||||
&& m.get("enabled", true)
|
||||
&& m.get("propagation-" + scope, false);
|
||||
})
|
||||
.forEach(m -> runOne(m, f));
|
||||
|
|
|
@ -3,8 +3,10 @@ package sh.libre.scim.core;
|
|||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import de.captaingoldfish.scim.sdk.common.resources.User;
|
||||
|
@ -13,8 +15,7 @@ import de.captaingoldfish.scim.sdk.common.resources.complex.Name;
|
|||
import de.captaingoldfish.scim.sdk.common.resources.multicomplex.PersonRole;
|
||||
import de.captaingoldfish.scim.sdk.common.resources.complex.Meta;
|
||||
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
@ -26,6 +27,8 @@ public class UserAdapter extends Adapter<UserModel, User> {
|
|||
private String email;
|
||||
private Boolean active;
|
||||
private String[] roles;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
|
||||
public UserAdapter(KeycloakSession session, String componentId) {
|
||||
super(session, componentId, "User", Logger.getLogger(UserAdapter.class));
|
||||
|
@ -79,6 +82,22 @@ public class UserAdapter extends Adapter<UserModel, User> {
|
|||
this.roles = roles;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<User> getResourceClass() {
|
||||
return User.class;
|
||||
|
@ -96,19 +115,17 @@ public class UserAdapter extends Adapter<UserModel, User> {
|
|||
setDisplayName(displayName);
|
||||
setEmail(user.getEmail());
|
||||
setActive(user.isEnabled());
|
||||
setFirstName(user.getFirstName());
|
||||
setLastName(user.getLastName());
|
||||
var rolesSet = new HashSet<String>();
|
||||
user.getGroupsStream().flatMap(g -> g.getRoleMappingsStream())
|
||||
.filter((r) -> r.getFirstAttribute("scim").equals("true")).map((r) -> r.getName())
|
||||
.filter((r) -> StringUtils.equals(r.getFirstAttribute("scim"), "true"))
|
||||
.map((r) -> r.getName())
|
||||
.forEach(r -> rolesSet.add(r));
|
||||
user.getRoleMappingsStream()
|
||||
.filter((r) -> StringUtils.equals(r.getFirstAttribute("scim"), "true"))
|
||||
.map((r) -> r.getName())
|
||||
.forEach(r -> rolesSet.add(r));
|
||||
|
||||
user.getRoleMappingsStream().filter((r) -> {
|
||||
var attr = r.getFirstAttribute("scim");
|
||||
if (attr == null) {
|
||||
return false;
|
||||
}
|
||||
return attr.equals("true");
|
||||
}).map((r) -> r.getName()).forEach(r -> rolesSet.add(r));
|
||||
|
||||
var roles = new String[rolesSet.size()];
|
||||
rolesSet.toArray(roles);
|
||||
setRoles(roles);
|
||||
|
@ -127,13 +144,15 @@ public class UserAdapter extends Adapter<UserModel, User> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public User toSCIM(Boolean addMeta) {
|
||||
public User toSCIM() {
|
||||
var user = new User();
|
||||
user.setExternalId(id);
|
||||
user.setUserName(username);
|
||||
user.setId(externalId);
|
||||
user.setDisplayName(displayName);
|
||||
Name name = new Name();
|
||||
name.setFamilyName(lastName);
|
||||
name.setGivenName(firstName);
|
||||
user.setName(name);
|
||||
var emails = new ArrayList<Email>();
|
||||
if (email != null) {
|
||||
|
@ -142,15 +161,14 @@ public class UserAdapter extends Adapter<UserModel, User> {
|
|||
}
|
||||
user.setEmails(emails);
|
||||
user.setActive(active);
|
||||
if (addMeta) {
|
||||
var meta = new Meta();
|
||||
try {
|
||||
var uri = new URI("Users/" + externalId);
|
||||
meta.setLocation(uri.toString());
|
||||
} catch (URISyntaxException e) {
|
||||
LOGGER.warn(e);
|
||||
}
|
||||
user.setMeta(meta);
|
||||
}
|
||||
List<PersonRole> roles = new ArrayList<PersonRole>();
|
||||
for (var r : this.roles) {
|
||||
var role = new PersonRole();
|
||||
|
@ -212,11 +230,13 @@ public class UserAdapter extends Adapter<UserModel, User> {
|
|||
|
||||
@Override
|
||||
public Stream<UserModel> getResourceStream() {
|
||||
return this.session.users().getUsersStream(this.session.getContext().getRealm());
|
||||
Map<String, String> params = new HashMap<String, String>() {
|
||||
};
|
||||
return this.session.users().searchForUserStream(realm, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean skipRefresh() {
|
||||
return getUsername().equals("admin");
|
||||
return StringUtils.equals(getUsername(), "admin");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package sh.libre.scim.event;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.regex.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventListenerProvider;
|
||||
|
@ -99,7 +100,7 @@ public class ScimEventListenerProvider implements EventListenerProvider {
|
|||
var groupId = matcher.group(2);
|
||||
LOGGER.infof("%s %s from %s", event.getOperationType(), userId, groupId);
|
||||
var group = getGroup(groupId);
|
||||
dispatcher.run(ScimDispatcher.SCOPE_GROUP, (client) -> client.replace(GroupAdapter.class, group));
|
||||
group.setSingleAttribute("scim-dirty", "true");
|
||||
var user = getUser(userId);
|
||||
dispatcher.run(ScimDispatcher.SCOPE_USER, (client) -> client.replace(UserAdapter.class, user));
|
||||
}
|
||||
|
@ -107,10 +108,10 @@ public class ScimEventListenerProvider implements EventListenerProvider {
|
|||
var type = matcher.group(1);
|
||||
var id = matcher.group(2);
|
||||
LOGGER.infof("%s %s %s roles", event.getOperationType(), type, id);
|
||||
if (type.equals("users")) {
|
||||
if (StringUtils.equals(type, "users")) {
|
||||
var user = getUser(id);
|
||||
dispatcher.run(ScimDispatcher.SCOPE_USER, (client) -> client.replace(UserAdapter.class, user));
|
||||
} else if (type.equals("groups")) {
|
||||
} else if (StringUtils.equals(type, "groups")) {
|
||||
var group = getGroup(id);
|
||||
session.users().getGroupMembersStream(session.getContext().getRealm(), group).forEach(user -> {
|
||||
dispatcher.run(ScimDispatcher.SCOPE_USER, (client) -> client.replace(UserAdapter.class, user));
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package sh.libre.scim.jpa;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.Table;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.IdClass;
|
||||
import jakarta.persistence.NamedQuery;
|
||||
import jakarta.persistence.NamedQueries;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@IdClass(ScimResourceId.class)
|
||||
|
|
|
@ -68,6 +68,7 @@ public class ScimResourceId implements Serializable {
|
|||
if (!(other instanceof ScimResourceId))
|
||||
return false;
|
||||
var o = (ScimResourceId) other;
|
||||
// TODO
|
||||
return (o.id == id &&
|
||||
o.realmId == realmId &&
|
||||
o.componentId == componentId &&
|
||||
|
|
|
@ -3,8 +3,9 @@ package sh.libre.scim.storage;
|
|||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -17,6 +18,7 @@ import org.keycloak.storage.UserStorageProviderFactory;
|
|||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
import org.keycloak.storage.user.ImportSynchronization;
|
||||
import org.keycloak.storage.user.SynchronizationResult;
|
||||
import org.keycloak.timer.TimerProvider;
|
||||
|
||||
import sh.libre.scim.core.GroupAdapter;
|
||||
import sh.libre.scim.core.ScimDispatcher;
|
||||
|
@ -34,6 +36,7 @@ public class ScimStorageProviderFactory
|
|||
.property()
|
||||
.name("endpoint")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.required(true)
|
||||
.label("SCIM 2.0 endpoint")
|
||||
.helpText("External SCIM 2.0 base " +
|
||||
"URL (/ServiceProviderConfig /Schemas and /ResourcesTypes should be accessible)")
|
||||
|
@ -70,12 +73,14 @@ public class ScimStorageProviderFactory
|
|||
.name("propagation-user")
|
||||
.type(ProviderConfigProperty.BOOLEAN_TYPE)
|
||||
.label("Enable user propagation")
|
||||
.helpText("Should operation on users be propagated to this provider ?")
|
||||
.defaultValue("true")
|
||||
.add()
|
||||
.property()
|
||||
.name("propagation-group")
|
||||
.type(ProviderConfigProperty.BOOLEAN_TYPE)
|
||||
.label("Enable group propagation")
|
||||
.helpText("Should operation on groups be propagated to this provider ?")
|
||||
.defaultValue("true")
|
||||
.add()
|
||||
.property()
|
||||
|
@ -127,10 +132,10 @@ public class ScimStorageProviderFactory
|
|||
var realm = session.realms().getRealm(realmId);
|
||||
session.getContext().setRealm(realm);
|
||||
var dispatcher = new ScimDispatcher(session);
|
||||
if (model.get("propagation-user").equals("true")) {
|
||||
if (StringUtils.equals(model.get("propagation-user"), "true")) {
|
||||
dispatcher.runOne(model, (client) -> client.sync(UserAdapter.class, result));
|
||||
}
|
||||
if (model.get("propagation-group").equals("true")) {
|
||||
if (StringUtils.equals(model.get("propagation-group"), "true")) {
|
||||
dispatcher.runOne(model, (client) -> client.sync(GroupAdapter.class, result));
|
||||
}
|
||||
}
|
||||
|
@ -147,4 +152,27 @@ public class ScimStorageProviderFactory
|
|||
return this.sync(sessionFactory, realmId, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
var timer = factory.create().getProvider(TimerProvider.class);
|
||||
timer.scheduleTask(taskSession -> {
|
||||
for (var realm : taskSession.realms().getRealmsStream().toList()) {
|
||||
KeycloakModelUtils.runJobInTransaction(factory, new KeycloakSessionTask() {
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
session.getContext().setRealm(realm);
|
||||
var dispatcher = new ScimDispatcher(session);
|
||||
for (var group : session.groups().getGroupsStream(realm)
|
||||
.filter(x -> StringUtils.equals(x.getFirstAttribute("scim-dirty"), "true")).toList()) {
|
||||
LOGGER.debug(group.getName() + " is dirty");
|
||||
dispatcher.run(ScimDispatcher.SCOPE_GROUP,
|
||||
(client) -> client.replace(GroupAdapter.class, group));
|
||||
group.removeAttribute("scim-dirty");
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}, 30000, "scim-background");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue