Remove all unsafe Optional.get calls

This commit is contained in:
Brendan Le Ny 2024-06-24 11:39:32 +02:00 committed by Alex Morel
parent 8e97335de4
commit ad60363cbd
3 changed files with 94 additions and 101 deletions

View file

@ -12,8 +12,9 @@ import sh.libre.scim.jpa.ScimResourceDao;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends ResourceNode> implements AutoCloseable {
@ -76,52 +77,55 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
if (isSkip(roleMapperModel))
return;
KeycloakId id = getId(roleMapperModel);
ScimResource scimResource = findById(id).get();
EntityOnRemoteScimId externalId = scimResource.getExternalIdAsEntityOnRemoteScimId();
S scimForReplace = toScimForReplace(roleMapperModel, externalId);
scimClient.replace(externalId, scimForReplace);
} catch (NoSuchElementException e) {
LOGGER.warnf("failed to replace resource %s, scim mapping not found", getId(roleMapperModel));
Optional<EntityOnRemoteScimId> entityOnRemoteScimId = findById(id)
.map(ScimResource::getExternalIdAsEntityOnRemoteScimId);
entityOnRemoteScimId
.ifPresentOrElse(
externalId -> doReplace(roleMapperModel, externalId),
() -> LOGGER.warnf("failed to replace resource %s, scim mapping not found", id)
);
} catch (Exception e) {
LOGGER.error(e);
throw new ScimPropagationException("[SCIM] Error while replacing SCIM resource", e);
}
}
private void doReplace(RMM roleMapperModel, EntityOnRemoteScimId externalId) {
S scimForReplace = toScimForReplace(roleMapperModel, externalId);
scimClient.replace(externalId, scimForReplace);
}
protected abstract S toScimForReplace(RMM roleMapperModel, EntityOnRemoteScimId externalId);
public void delete(KeycloakId id) throws ScimPropagationException {
try {
ScimResource resource = findById(id).get();
EntityOnRemoteScimId externalId = resource.getExternalIdAsEntityOnRemoteScimId();
scimClient.delete(externalId);
getScimResourceDao().delete(resource);
} catch (NoSuchElementException e) {
throw new ScimPropagationException("Failed to delete resource %s, scim mapping not found: ".formatted(id), e);
}
ScimResource resource = findById(id)
.orElseThrow(() -> new ScimPropagationException("Failed to delete resource %s, scim mapping not found: ".formatted(id)));
EntityOnRemoteScimId externalId = resource.getExternalIdAsEntityOnRemoteScimId();
scimClient.delete(externalId);
getScimResourceDao().delete(resource);
}
public void refreshResources(SynchronizationResult syncRes) throws ScimPropagationException {
LOGGER.info("Refresh resources");
getResourceStream().forEach(resource -> {
KeycloakId id = getId(resource);
LOGGER.infof("Reconciling local resource %s", id);
if (!isSkipRefresh(resource)) {
try {
try {
findById(id).get();
LOGGER.info("Replacing it");
replace(resource);
} catch (NoSuchElementException e) {
LOGGER.info("Creating it");
create(resource);
}
syncRes.increaseUpdated();
} catch (ScimPropagationException e) {
// TODO handle exception
try (Stream<RMM> resourcesStream = getResourceStream()) {
Set<RMM> resources = resourcesStream.collect(Collectors.toUnmodifiableSet());
for (RMM resource : resources) {
KeycloakId id = getId(resource);
LOGGER.infof("Reconciling local resource %s", id);
if (isSkipRefresh(resource)) {
LOGGER.infof("Skip local resource %s", id);
continue;
}
if (findById(id).isPresent()) {
LOGGER.info("Replacing it");
replace(resource);
} else {
LOGGER.info("Creating it");
create(resource);
}
syncRes.increaseUpdated();
}
});
}
}
protected abstract boolean isSkipRefresh(RMM resource);
@ -130,14 +134,16 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
public void importResources(SynchronizationResult syncRes) {
LOGGER.info("Import");
ScimClient<S> scimClient = this.scimClient;
try {
for (S resource : scimClient.listResources()) {
try {
LOGGER.infof("Reconciling remote resource %s", resource);
EntityOnRemoteScimId externalId = resource.getId().map(EntityOnRemoteScimId::new).get();
try {
ScimResource mapping = getScimResourceDao().findByExternalId(externalId, type).get();
EntityOnRemoteScimId externalId = resource.getId()
.map(EntityOnRemoteScimId::new)
.orElseThrow(() -> new ScimPropagationException("remote SCIM resource doesn't have an id"));
Optional<ScimResource> optionalMapping = getScimResourceDao().findByExternalId(externalId, type);
if (optionalMapping.isPresent()) {
ScimResource mapping = optionalMapping.get();
if (entityExists(mapping.getIdAsKeycloakId())) {
LOGGER.info("Valid mapping found, skipping");
continue;
@ -145,8 +151,6 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
LOGGER.info("Delete a dangling mapping");
getScimResourceDao().delete(mapping);
}
} catch (NoSuchElementException e) {
// nothing to do
}
Optional<KeycloakId> mapped = tryToMap(resource);
@ -167,7 +171,7 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
break;
case DELETE_REMOTE:
LOGGER.info("Delete remote resource");
scimClient.delete(externalId);
this.scimClient.delete(externalId);
syncRes.increaseRemoved();
break;
case NOTHING:
@ -186,9 +190,9 @@ public abstract class AbstractScimService<RMM extends RoleMapperModel, S extends
}
}
protected abstract KeycloakId createEntity(S resource);
protected abstract KeycloakId createEntity(S resource) throws ScimPropagationException;
protected abstract Optional<KeycloakId> tryToMap(S resource);
protected abstract Optional<KeycloakId> tryToMap(S resource) throws ScimPropagationException;
protected abstract boolean entityExists(KeycloakId keycloakId);

View file

@ -3,9 +3,9 @@ package sh.libre.scim.core;
import de.captaingoldfish.scim.sdk.common.resources.Group;
import de.captaingoldfish.scim.sdk.common.resources.complex.Meta;
import de.captaingoldfish.scim.sdk.common.resources.multicomplex.Member;
import jakarta.persistence.NoResultException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.jboss.logging.Logger;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
@ -15,9 +15,9 @@ import sh.libre.scim.jpa.ScimResource;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Stream;
public class GroupScimService extends AbstractScimService<GroupModel, Group> {
@ -39,9 +39,9 @@ public class GroupScimService extends AbstractScimService<GroupModel, Group> {
@Override
protected Optional<KeycloakId> tryToMap(Group resource) {
String externalId = resource.getId().get();
String displayName = resource.getDisplayName().get();
Set<String> names = Set.of(externalId, displayName);
Set<String> names = new TreeSet<>();
resource.getId().ifPresent(names::add);
resource.getDisplayName().ifPresent(names::add);
Optional<GroupModel> group = getKeycloakDao().getGroupsStream()
.filter(groupModel -> names.contains(groupModel.getName()))
.findFirst();
@ -49,28 +49,22 @@ public class GroupScimService extends AbstractScimService<GroupModel, Group> {
}
@Override
protected KeycloakId createEntity(Group resource) {
String displayName = resource.getDisplayName().get();
protected KeycloakId createEntity(Group resource) throws ScimPropagationException {
String displayName = resource.getDisplayName()
.filter(StringUtils::isNotBlank)
.orElseThrow(() -> new ScimPropagationException("can't create group without name: " + resource));
GroupModel group = getKeycloakDao().createGroup(displayName);
List<Member> groupMembers = resource.getMembers();
if (CollectionUtils.isNotEmpty(groupMembers)) {
for (Member groupMember : groupMembers) {
try {
EntityOnRemoteScimId externalId = new EntityOnRemoteScimId(groupMember.getValue().get());
ScimResource userMapping = getScimResourceDao().findUserByExternalId(externalId).get();
KeycloakId userId = userMapping.getIdAsKeycloakId();
try {
UserModel user = getKeycloakDao().getUserById(userId);
if (user == null) {
throw new NoResultException();
}
user.joinGroup(group);
} catch (Exception e) {
logger.warn(e);
}
} catch (NoSuchElementException e) {
logger.warnf("member %s not found for scim group %s", groupMember.getValue().get(), resource.getId().get());
}
EntityOnRemoteScimId externalId = groupMember.getValue()
.map(EntityOnRemoteScimId::new)
.orElseThrow(() -> new ScimPropagationException("can't create group member for group '%s' without id: ".formatted(displayName) + resource));
KeycloakId userId = getScimResourceDao().findUserByExternalId(externalId)
.map(ScimResource::getIdAsKeycloakId)
.orElseThrow(() -> new ScimPropagationException("can't find mapping for group member %s".formatted(externalId)));
UserModel userModel = getKeycloakDao().getUserById(userId);
userModel.joinGroup(group);
}
}
return new KeycloakId(group.getId());
@ -94,19 +88,22 @@ public class GroupScimService extends AbstractScimService<GroupModel, Group> {
group.setDisplayName(groupModel.getName());
for (KeycloakId member : members) {
Member groupMember = new Member();
try {
ScimResource userMapping = getScimResourceDao().findUserById(member).get();
logger.debug(userMapping.getExternalIdAsEntityOnRemoteScimId());
logger.debug(userMapping.getIdAsKeycloakId());
groupMember.setValue(userMapping.getExternalIdAsEntityOnRemoteScimId().asString());
String refString = String.format("Users/%s", userMapping.getExternalIdAsEntityOnRemoteScimId().asString());
URI ref = new URI(refString);
groupMember.setRef(ref.toString());
Optional<ScimResource> optionalGroupMemberMapping = getScimResourceDao().findUserById(member);
if (optionalGroupMemberMapping.isPresent()) {
ScimResource groupMemberMapping = optionalGroupMemberMapping.get();
EntityOnRemoteScimId externalIdAsEntityOnRemoteScimId = groupMemberMapping.getExternalIdAsEntityOnRemoteScimId();
logger.debugf("found mapping for group member %s as %s", member, externalIdAsEntityOnRemoteScimId);
groupMember.setValue(externalIdAsEntityOnRemoteScimId.asString());
try {
String refString = String.format("Users/%s", externalIdAsEntityOnRemoteScimId.asString());
URI ref = new URI(refString);
groupMember.setRef(ref.toString());
} catch (URISyntaxException e) {
logger.warnf("bad ref uri for member " + member);
}
group.addMember(groupMember);
} catch (NoSuchElementException e) {
} else {
logger.warnf("member %s not found for group %s", member, groupModel.getId());
} catch (URISyntaxException e) {
logger.warnf("bad ref uri for member " + member);
}
}
return group;

View file

@ -40,45 +40,37 @@ public class UserScimService extends AbstractScimService<UserModel, User> {
@Override
protected Optional<KeycloakId> tryToMap(User resource) {
String username = resource.getUserName().get();
String email = resource.getEmails().stream()
Optional<KeycloakId> matchedByUsername = resource.getUserName()
.map(getKeycloakDao()::getUserByUsername)
.map(this::getId);
Optional<KeycloakId> matchedByEmail = resource.getEmails().stream()
.findFirst()
.flatMap(MultiComplexNode::getValue)
.orElse(null);
UserModel sameUsernameUser = null;
UserModel sameEmailUser = null;
if (username != null) {
sameUsernameUser = getKeycloakDao().getUserByUsername(username);
}
if (email != null) {
sameEmailUser = getKeycloakDao().getUserByEmail(email);
}
if ((sameUsernameUser != null && sameEmailUser != null)
&& (!StringUtils.equals(sameUsernameUser.getId(), sameEmailUser.getId()))) {
logger.warnf("found 2 possible users for remote user %s %s", username, email);
.map(getKeycloakDao()::getUserByEmail)
.map(this::getId);
if (matchedByUsername.isPresent()
&& matchedByEmail.isPresent()
&& !matchedByUsername.equals(matchedByEmail)) {
logger.warnf("found 2 possible users for remote user %s %s", matchedByUsername.get(), matchedByEmail.get());
return Optional.empty();
}
if (sameUsernameUser != null) {
return Optional.of(getId(sameUsernameUser));
if (matchedByUsername.isPresent()) {
return matchedByUsername;
}
if (sameEmailUser != null) {
return Optional.of(getId(sameEmailUser));
}
return Optional.empty();
return matchedByEmail;
}
@Override
protected KeycloakId createEntity(User resource) {
String username = resource.getUserName().get();
if (StringUtils.isEmpty(username)) {
throw new IllegalArgumentException("can't create user with empty username");
}
protected KeycloakId createEntity(User resource) throws ScimPropagationException {
String username = resource.getUserName()
.filter(StringUtils::isNotBlank)
.orElseThrow(() -> new ScimPropagationException("can't create user with empty username, resource id = %s".formatted(resource.getId())));
UserModel user = getKeycloakDao().addUser(username);
resource.getEmails().stream()
.findFirst()
.flatMap(MultiComplexNode::getValue)
.ifPresent(user::setEmail);
user.setEnabled(resource.isActive().get());
user.setEnabled(resource.isActive().orElseThrow(() -> new ScimPropagationException("can't create user with undefined 'active', resource id = %s".formatted(resource.getId()))));
return new KeycloakId(user.getId());
}