This is an automated email from the ASF dual-hosted git repository. andreapatricelli pushed a commit to branch 2_1_X in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/2_1_X by this push: new 111cd77 [SYNCOPE-1656] remediation now is generated also on pull update failure (#302) 111cd77 is described below commit 111cd779dc400ff567e3362e0e982b64354bf3f5 Author: Andrea Patricelli <andreapatrice...@apache.org> AuthorDate: Fri Jan 14 17:02:28 2022 +0100 [SYNCOPE-1656] remediation now is generated also on pull update failure (#302) * [SYNCOPE-1656] remediation now is generated also on pull update failure, made some refactoring --- .../java/pushpull/AbstractPullResultHandler.java | 127 +++++++++++++-------- .../pushpull/DefaultUserPullResultHandler.java | 56 +++------ .../org/apache/syncope/fit/AbstractITCase.java | 32 ++++++ .../apache/syncope/fit/core/PullTaskITCase.java | 115 ++++++++++++++++++- 4 files changed, 243 insertions(+), 87 deletions(-) diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java index 2d84f06..3416c3e 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java @@ -18,57 +18,60 @@ */ package org.apache.syncope.core.provisioning.java.pushpull; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.syncope.common.lib.AnyOperations; import org.apache.syncope.common.lib.patch.AnyPatch; import org.apache.syncope.common.lib.patch.StringPatchItem; import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.common.lib.to.ProvisioningReport; import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.syncope.common.lib.types.AuditElements; import org.apache.syncope.common.lib.types.AuditElements.Result; import org.apache.syncope.common.lib.types.MatchType; import org.apache.syncope.common.lib.types.MatchingRule; import org.apache.syncope.common.lib.types.PatchOperation; -import org.apache.syncope.core.provisioning.api.PropagationByResource; import org.apache.syncope.common.lib.types.PullMode; import org.apache.syncope.common.lib.types.ResourceOperation; import org.apache.syncope.common.lib.types.UnmatchingRule; +import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO; import org.apache.syncope.core.persistence.api.dao.NotFoundException; +import org.apache.syncope.core.persistence.api.dao.PullMatch; import org.apache.syncope.core.persistence.api.dao.RemediationDAO; +import org.apache.syncope.core.persistence.api.dao.TaskDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; -import org.apache.syncope.core.provisioning.api.propagation.PropagationException; -import org.apache.syncope.core.spring.security.DelegatedAdministrationException; import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO; +import org.apache.syncope.core.persistence.api.entity.AnyType; import org.apache.syncope.core.persistence.api.entity.EntityFactory; import org.apache.syncope.core.persistence.api.entity.Remediation; import org.apache.syncope.core.persistence.api.entity.resource.Provision; import org.apache.syncope.core.persistence.api.entity.task.PullTask; import org.apache.syncope.core.provisioning.api.AuditManager; +import org.apache.syncope.core.provisioning.api.PropagationByResource; import org.apache.syncope.core.provisioning.api.ProvisioningManager; import org.apache.syncope.core.provisioning.api.cache.VirAttrCache; +import org.apache.syncope.core.provisioning.api.cache.VirAttrCacheKey; import org.apache.syncope.core.provisioning.api.cache.VirAttrCacheValue; import org.apache.syncope.core.provisioning.api.notification.NotificationManager; +import org.apache.syncope.core.provisioning.api.propagation.PropagationException; import org.apache.syncope.core.provisioning.api.pushpull.IgnoreProvisionException; -import org.apache.syncope.common.lib.to.ProvisioningReport; import org.apache.syncope.core.provisioning.api.pushpull.PullActions; -import org.apache.syncope.core.persistence.api.dao.PullMatch; -import org.apache.syncope.core.persistence.api.dao.TaskDAO; -import org.apache.syncope.core.provisioning.api.cache.VirAttrCacheKey; import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullExecutor; import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullResultHandler; import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils; import org.apache.syncope.core.spring.security.AuthContextUtils; +import org.apache.syncope.core.spring.security.DelegatedAdministrationException; import org.identityconnectors.framework.common.objects.Attribute; import org.identityconnectors.framework.common.objects.SyncDelta; import org.identityconnectors.framework.common.objects.SyncDeltaType; import org.quartz.JobExecutionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; @Transactional(rollbackFor = Throwable.class) public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHandler<PullTask, PullActions> @@ -90,6 +93,9 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan protected UserDAO userDAO; @Autowired + protected AnyTypeDAO anyTypeDAO; + + @Autowired protected TaskDAO taskDAO; @Autowired @@ -250,18 +256,12 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan if (profile.getTask().isRemediation()) { // set to SUCCESS to let the incremental flow go on in case of errors resultStatus = Result.SUCCESS; - Remediation entity = entityFactory.newEntity(Remediation.class); - entity.setAnyType(provision.getAnyType()); - entity.setOperation(ResourceOperation.CREATE); - entity.setPayload(anyTO); - entity.setError(result.getMessage()); - entity.setInstant(new Date()); - entity.setRemoteName(delta.getObject().getName().getNameValue()); - if (taskDAO.find(profile.getTask().getKey()) != null) { - entity.setPullTask(profile.getTask()); - } - - remediationDAO.save(entity); + createRemediation( + provision.getAnyType(), + anyTO, + taskDAO.find(profile.getTask().getKey()) != null ? profile.getTask() : null, + result, + delta); } else { resultStatus = Result.FAILURE; } @@ -378,17 +378,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan if (profile.getTask().isRemediation()) { // set to SUCCESS to let the incremental flow go on in case of errors resultStatus = Result.SUCCESS; - - Remediation entity = entityFactory.newEntity(Remediation.class); - entity.setAnyType(provision.getAnyType()); - entity.setOperation(ResourceOperation.UPDATE); - entity.setPayload(anyPatch); - entity.setError(result.getMessage()); - entity.setInstant(new Date()); - entity.setRemoteName(delta.getObject().getName().getNameValue()); - entity.setPullTask(profile.getTask()); - - remediationDAO.save(entity); + createRemediation(provision.getAnyType(), anyPatch, profile.getTask(), result, delta); } else { resultStatus = Result.FAILURE; } @@ -682,16 +672,8 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan if (profile.getTask().isRemediation()) { // set to SUCCESS to let the incremental flow go on in case of errors resultStatus = Result.SUCCESS; - Remediation entity = entityFactory.newEntity(Remediation.class); - entity.setAnyType(provision.getAnyType()); - entity.setOperation(ResourceOperation.DELETE); - entity.setPayload(match.getAny().getKey()); - entity.setError(result.getMessage()); - entity.setInstant(new Date()); - entity.setRemoteName(delta.getObject().getName().getNameValue()); - entity.setPullTask(profile.getTask()); - - remediationDAO.save(entity); + createRemediation( + provision.getAnyType(), match.getAny().getKey(), profile.getTask(), result, delta); } } @@ -948,4 +930,59 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan delta, furtherInput); } + + protected Remediation createRemediation( + final AnyType anyType, + final String anyKey, + final PullTask pullTask, + final ProvisioningReport result, + final SyncDelta delta) { + return createRemediation(anyType, anyKey, null, null, pullTask, result, delta); + } + + protected Remediation createRemediation( + final AnyType anyType, + final AnyTO anyTO, + final PullTask pullTask, + final ProvisioningReport result, + final SyncDelta delta) { + return createRemediation(anyType, null, anyTO, null, pullTask, result, delta); + } + + protected Remediation createRemediation( + final AnyType anyType, + final AnyPatch anyPatch, + final PullTask pullTask, + final ProvisioningReport result, + final SyncDelta delta) { + return createRemediation(anyType, null, null, anyPatch, pullTask, result, delta); + } + + protected Remediation createRemediation( + final AnyType anyType, + final String anyKey, + final AnyTO anyTO, + final AnyPatch anyPatch, + final PullTask pullTask, + final ProvisioningReport result, + final SyncDelta delta) { + Remediation entity = entityFactory.newEntity(Remediation.class); + + entity.setAnyType(anyType); + entity.setOperation(anyPatch == null ? ResourceOperation.CREATE : ResourceOperation.UPDATE); + if (StringUtils.isNotBlank(anyKey)) { + entity.setPayload(anyKey); + } else if (anyTO != null) { + entity.setPayload(anyTO); + } else if (anyPatch != null) { + entity.setPayload(anyPatch); + } + entity.setError(result.getMessage()); + entity.setInstant(new Date()); + entity.setRemoteName(delta.getObject().getName().getNameValue()); + entity.setPullTask(pullTask); + + return remediationDAO.save(entity); + } + } diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java index d54e9d3..6194e28 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java @@ -18,13 +18,6 @@ */ package org.apache.syncope.core.provisioning.java.pushpull; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.tuple.Pair; @@ -35,6 +28,7 @@ import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.AttrTO; import org.apache.syncope.common.lib.to.LinkedAccountTO; import org.apache.syncope.common.lib.to.PropagationStatus; +import org.apache.syncope.common.lib.to.ProvisioningReport; import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.syncope.common.lib.types.AuditElements; @@ -47,7 +41,6 @@ import org.apache.syncope.common.lib.types.UnmatchingRule; import org.apache.syncope.core.persistence.api.dao.PullMatch; import org.apache.syncope.core.persistence.api.entity.Any; import org.apache.syncope.core.persistence.api.entity.AnyUtils; -import org.apache.syncope.core.persistence.api.entity.Remediation; import org.apache.syncope.core.persistence.api.entity.resource.Provision; import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount; import org.apache.syncope.core.persistence.api.entity.user.User; @@ -56,14 +49,19 @@ import org.apache.syncope.core.provisioning.api.ProvisioningManager; import org.apache.syncope.core.provisioning.api.UserProvisioningManager; import org.apache.syncope.core.provisioning.api.WorkflowResult; import org.apache.syncope.core.provisioning.api.propagation.PropagationException; -import org.apache.syncope.common.lib.to.ProvisioningReport; import org.apache.syncope.core.provisioning.api.pushpull.PullActions; -import org.identityconnectors.framework.common.objects.SyncDelta; import org.apache.syncope.core.provisioning.api.pushpull.UserPullResultHandler; import org.identityconnectors.framework.common.objects.AttributeUtil; +import org.identityconnectors.framework.common.objects.SyncDelta; import org.identityconnectors.framework.common.objects.SyncDeltaType; import org.quartz.JobExecutionException; import org.springframework.beans.factory.annotation.Autowired; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; public class DefaultUserPullResultHandler extends AbstractPullResultHandler implements UserPullResultHandler { @@ -127,6 +125,11 @@ public class DefaultUserPullResultHandler extends AbstractPullResultHandler impl Collections.singleton(profile.getTask().getResource().getKey()), true); + if (ProvisioningReport.Status.FAILURE == result.getStatus() && profile.getTask().isRemediation()) { + createRemediation(anyTypeDAO.find(result.getAnyType()), anyPatch, profile.getTask(), result, + delta); + } + return updated.getLeft(); } @@ -395,16 +398,7 @@ public class DefaultUserPullResultHandler extends AbstractPullResultHandler impl resultStatus = Result.FAILURE; if (profile.getTask().isRemediation()) { - Remediation entity = entityFactory.newEntity(Remediation.class); - entity.setAnyType(provision.getAnyType()); - entity.setOperation(ResourceOperation.UPDATE); - entity.setPayload(patch); - entity.setError(report.getMessage()); - entity.setInstant(new Date()); - entity.setRemoteName(delta.getObject().getName().getNameValue()); - entity.setPullTask(profile.getTask()); - - remediationDAO.save(entity); + createRemediation(provision.getAnyType(), patch, profile.getTask(), report, delta); } } @@ -520,16 +514,7 @@ public class DefaultUserPullResultHandler extends AbstractPullResultHandler impl resultStatus = Result.FAILURE; if (profile.getTask().isRemediation()) { - Remediation entity = entityFactory.newEntity(Remediation.class); - entity.setAnyType(provision.getAnyType()); - entity.setOperation(ResourceOperation.UPDATE); - entity.setPayload(patch); - entity.setError(report.getMessage()); - entity.setInstant(new Date()); - entity.setRemoteName(delta.getObject().getName().getNameValue()); - entity.setPullTask(profile.getTask()); - - remediationDAO.save(entity); + createRemediation(provision.getAnyType(), patch, profile.getTask(), report, delta); } } @@ -601,16 +586,7 @@ public class DefaultUserPullResultHandler extends AbstractPullResultHandler impl output = e; if (profile.getTask().isRemediation()) { - Remediation entity = entityFactory.newEntity(Remediation.class); - entity.setAnyType(provision.getAnyType()); - entity.setOperation(ResourceOperation.UPDATE); - entity.setPayload(patch); - entity.setError(report.getMessage()); - entity.setInstant(new Date()); - entity.setRemoteName(delta.getObject().getName().getNameValue()); - entity.setPullTask(profile.getTask()); - - remediationDAO.save(entity); + createRemediation(provision.getAnyType(), patch, profile.getTask(), report, delta); } } diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java index f54f968..a79d315 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java @@ -34,10 +34,13 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.UUID; import javax.naming.Context; import javax.naming.NamingException; +import javax.naming.directory.Attribute; import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import javax.naming.directory.ModificationItem; @@ -606,6 +609,34 @@ public abstract class AbstractITCase { } } + protected void createLdapRemoteObject( + final String bindDn, + final String bindPwd, + final Pair<String, Set<Attribute>> entryAttrs) throws NamingException { + + InitialDirContext ctx = null; + try { + ctx = getLdapResourceDirContext(bindDn, bindPwd); + + BasicAttributes entry = new BasicAttributes(); + entryAttrs.getRight().forEach(item -> entry.put(item)); + + ctx.createSubcontext(entryAttrs.getLeft(), entry); + + } catch (NamingException e) { + LOG.error("While creating {} with {}", entryAttrs.getLeft(), entryAttrs.getRight(), e); + throw e; + } finally { + if (ctx != null) { + try { + ctx.close(); + } catch (NamingException e) { + // ignore + } + } + } + } + protected void updateLdapRemoteObject( final String bindDn, final String bindPwd, @@ -702,4 +733,5 @@ public abstract class AbstractITCase { } while (results.isEmpty() && i < maxWaitSeconds); return results; } + } diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java index f66f8ba..bb2e545 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java @@ -42,10 +42,15 @@ import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.UUID; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.BasicAttribute; import javax.sql.DataSource; import javax.ws.rs.core.Response; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.SerializationUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; import org.apache.syncope.client.lib.SyncopeClient; import org.apache.syncope.client.lib.SyncopeClientFactoryBean; @@ -867,8 +872,7 @@ public class PullTaskITCase extends AbstractTaskITCase { "SyncopeClientCompositeException: {[RequiredValuesMissing [userId]]}")); } finally { resourceService.delete(ldap.getKey()); - remediationService.list(new RemediationQuery.Builder().page(1).size(10).build()).getResult().forEach( - r -> remediationService.delete(r.getKey())); + cleanUpRemediations(); } } @@ -1394,4 +1398,111 @@ public class PullTaskITCase extends AbstractTaskITCase { } } } + + @Test + public void issueSYNCOPE1656() throws NamingException { + // preliminary create a new LDAP object + createLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, prepareLdapAttributes( + "pullFromLDAP_issue1656", + "pullfromldap_issue1...@syncope.apache.org", + "Active", + "pullFromLDAP_issue1656", + "Surname", + "5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8", + "odd", + "password")); + try { + // 1. Pull from resource-ldap + PullTaskTO pullTask = new PullTaskTO(); + pullTask.setDestinationRealm(SyncopeConstants.ROOT_REALM); + pullTask.setName("SYNCOPE1656"); + pullTask.setActive(true); + pullTask.setPerformCreate(true); + pullTask.setPerformUpdate(true); + pullTask.setRemediation(true); + pullTask.setPullMode(PullMode.FULL_RECONCILIATION); + pullTask.setResource(RESOURCE_NAME_LDAP); + + Response taskResponse = taskService.create(TaskType.PULL, pullTask); + pullTask = getObject(taskResponse.getLocation(), TaskService.class, PullTaskTO.class); + assertNotNull(pullTask); + + ExecTO execution = execProvisioningTask( + taskService, TaskType.PULL, pullTask.getKey(), MAX_WAIT_SECONDS, false); + assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus())); + + UserTO pullFromLDAP_issue1656 = userService.read("pullFromLDAP_issue1656"); + assertEquals("pullfromldap_issue1...@syncope.apache.org", + pullFromLDAP_issue1656.getPlainAttr("email").get().getValues().get(0)); + // 2. Edit mail attribute directly on the resource in order to have a not valid email + ConnObjectTO connObject = + resourceService.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), + pullFromLDAP_issue1656.getKey()); + assertNotNull(connObject); + assertEquals("pullfromldap_issue1...@syncope.apache.org", + connObject.getAttr("mail").get().getValues().get(0)); + AttrTO userDn = connObject.getAttr(Name.NAME).get(); + assertNotNull(userDn); + assertEquals(1, userDn.getValues().size()); + updateLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, + userDn.getValues().get(0), Collections.singletonMap("mail", "pullFromLDAP_issue1656@")); + // 3. Pull again from resource-ldap + execution = execProvisioningTask(taskService, TaskType.PULL, pullTask.getKey(), MAX_WAIT_SECONDS, false); + assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus())); + assertTrue(execution.getMessage().contains("UPDATE FAILURE")); + pullFromLDAP_issue1656 = userService.read("pullFromLDAP_issue1656"); + assertEquals("pullfromldap_issue1...@syncope.apache.org", + pullFromLDAP_issue1656.getPlainAttr("email").get().getValues().get(0)); + final String pullFromLDAP_issue1656_key = pullFromLDAP_issue1656.getKey(); + // a remediation for pullFromLDAP_issue1656 email should have been created + PagedResult<RemediationTO> remediations = + remediationService.list(new RemediationQuery.Builder().page(1).size(10).build()); + assertTrue(remediations.getResult().stream().anyMatch( + r -> pullFromLDAP_issue1656_key.equals(r.getAnyPatchPayload().getKey()))); + assertTrue(remediations.getResult().stream().anyMatch(r -> StringUtils.contains(r.getError(), + "\"pullFromLDAP_issue1656@\" is not a valid email address"))); + } finally { + // remove test entity + removeLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, + "uid=pullFromLDAP_issue1656,ou=People,o=isp"); + cleanUpRemediations(); + } + } + + private void cleanUpRemediations() { + remediationService.list(new RemediationQuery.Builder().page(1).size(100).build()).getResult().forEach( + r -> remediationService.delete(r.getKey())); + } + + private Pair<String, Set<Attribute>> prepareLdapAttributes( + final String uid, + final String email, + final String description, + final String givenName, + final String sn, + final String registeredAddress, + final String title, + final String password) { + String entryDn = "uid=" + uid + ",ou=People,o=isp"; + Set<Attribute> attributes = new HashSet<>(); + + attributes.add(new BasicAttribute("description", description)); + attributes.add(new BasicAttribute("givenName", givenName)); + attributes.add(new BasicAttribute("mail", email)); + attributes.add(new BasicAttribute("sn", sn)); + attributes.add(new BasicAttribute("cn", uid)); + attributes.add(new BasicAttribute("uid", uid)); + attributes.add(new BasicAttribute("registeredaddress", registeredAddress)); + attributes.add(new BasicAttribute("title", title)); + attributes.add(new BasicAttribute("userpassword", password)); + + Attribute oc = new BasicAttribute("objectClass"); + oc.add("top"); + oc.add("person"); + oc.add("inetOrgPerson"); + oc.add("organizationalPerson"); + attributes.add(oc); + + return Pair.of(entryDn, attributes); + } }