From ad60363cbdce5eaef9f177f13e26dd8fef37d502 Mon Sep 17 00:00:00 2001 From: Brendan Le Ny <bleny@codelutin.com> Date: Mon, 24 Jun 2024 11:39:32 +0200 Subject: [PATCH] Remove all unsafe Optional.get calls --- .../libre/scim/core/AbstractScimService.java | 86 ++++++++++--------- .../sh/libre/scim/core/GroupScimService.java | 65 +++++++------- .../sh/libre/scim/core/UserScimService.java | 44 ++++------ 3 files changed, 94 insertions(+), 101 deletions(-) diff --git a/src/main/java/sh/libre/scim/core/AbstractScimService.java b/src/main/java/sh/libre/scim/core/AbstractScimService.java index a0939ad..9586a23 100644 --- a/src/main/java/sh/libre/scim/core/AbstractScimService.java +++ b/src/main/java/sh/libre/scim/core/AbstractScimService.java @@ -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); diff --git a/src/main/java/sh/libre/scim/core/GroupScimService.java b/src/main/java/sh/libre/scim/core/GroupScimService.java index 99c8c5c..687d52a 100644 --- a/src/main/java/sh/libre/scim/core/GroupScimService.java +++ b/src/main/java/sh/libre/scim/core/GroupScimService.java @@ -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; diff --git a/src/main/java/sh/libre/scim/core/UserScimService.java b/src/main/java/sh/libre/scim/core/UserScimService.java index 9a65849..3c30023 100644 --- a/src/main/java/sh/libre/scim/core/UserScimService.java +++ b/src/main/java/sh/libre/scim/core/UserScimService.java @@ -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()); } -- GitLab