diff --git a/.gitignore b/.gitignore index c507849..612c5bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target .idea +*.iml diff --git a/src/main/java/net/tirasa/connid/bundles/ad/crud/ADUpdate.java b/src/main/java/net/tirasa/connid/bundles/ad/crud/ADUpdate.java index a2f7922..345beaf 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/crud/ADUpdate.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/crud/ADUpdate.java @@ -42,6 +42,7 @@ import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; import net.tirasa.adsddl.ntsd.SID; +import net.tirasa.adsddl.ntsd.utils.GUID; import net.tirasa.adsddl.ntsd.utils.Hex; import net.tirasa.adsddl.ntsd.utils.NumberFacility; import net.tirasa.connid.bundles.ad.ADConfiguration; @@ -177,7 +178,18 @@ public Uid update(final Set attrs) { modifyMemberships(entryDN, attrsToBeUpdated); modifyPrimaryGroupID(entryDN, attrsToBeUpdated); - return conn.getSchemaMapping().createUid(oclass, entryDN); + if (OBJECTGUID.equals(conn.getSchemaMapping().getLdapUidAttribute(oclass))) { + final Attributes profile; + try { + profile = conn.getInitialContext().getAttributes(entryDN, new String[] { OBJECTGUID }); + return new Uid(GUID.getGuidAsString((byte[]) profile.get(OBJECTGUID).get())); + } catch (NamingException e) { + LOG.error("Error managing objectGUID after update", e); + throw new ConnectorException("Error managing objectGUID after update", e); + } + } else { + return conn.getSchemaMapping().createUid(oclass, entryDN); + } } public Uid addAttributeValues(final Set attrs) { diff --git a/src/main/java/net/tirasa/connid/bundles/ad/search/ADSearch.java b/src/main/java/net/tirasa/connid/bundles/ad/search/ADSearch.java index a05a71c..4832ada 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/search/ADSearch.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/search/ADSearch.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.function.BiFunction; import java.util.stream.Collectors; import javax.naming.InvalidNameException; import javax.naming.NamingException; @@ -44,6 +45,8 @@ import net.tirasa.connid.bundles.ldap.search.LdapSearchStrategy; import net.tirasa.connid.bundles.ldap.search.LdapSearches; import org.identityconnectors.common.logging.Log; +import org.identityconnectors.framework.common.exceptions.ConnectorException; +import org.identityconnectors.framework.common.objects.ConnectorObject; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.OperationOptions; import org.identityconnectors.framework.common.objects.QualifiedUid; @@ -102,21 +105,28 @@ public ADSearch( } public final void executeADQuery(final ResultsHandler handler) { + executeADQuery(handler, (result, attrsToGet) -> { + try { + return utils.createConnectorObject(result.getNameInNamespace(), + result, + attrsToGet, + oclass); + } catch (NamingException e) { + throw new ConnectorException(e); + } + }); + } + + public final void executeADQuery(final ResultsHandler handler, + final BiFunction, ConnectorObject> connObjSupplier) { final String[] attrsToGetOption = options.getAttributesToGet(); final Set attrsToGet = utils.getAttributesToGet(attrsToGetOption, oclass); - final LdapInternalSearch search = getInternalSearch(attrsToGet); - - search.execute(new LdapSearchResultsHandler() { + getInternalSearch(attrsToGet).execute(new LdapSearchResultsHandler() { @Override - public boolean handle(final String baseDN, final SearchResult result) - throws NamingException { - return handler.handle(utils.createConnectorObject( - result.getNameInNamespace(), - result, - attrsToGet, - oclass)); + public boolean handle(final String baseDN, final SearchResult result) { + return handler.handle(connObjSupplier.apply(result, attrsToGet)); } }); } diff --git a/src/main/java/net/tirasa/connid/bundles/ad/util/ADUtilities.java b/src/main/java/net/tirasa/connid/bundles/ad/util/ADUtilities.java index 200e902..9bfe19a 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/util/ADUtilities.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/util/ADUtilities.java @@ -34,6 +34,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.TreeSet; import javax.naming.InvalidNameException; @@ -54,6 +55,7 @@ import net.tirasa.connid.bundles.ad.ADConfiguration; import net.tirasa.connid.bundles.ad.ADConnection; import net.tirasa.connid.bundles.ad.ADConnector; +import net.tirasa.connid.bundles.ad.search.ADSearch; import net.tirasa.connid.bundles.ldap.LdapConnection; import net.tirasa.connid.bundles.ldap.commons.GroupHelper; import net.tirasa.connid.bundles.ldap.commons.LdapConstants; @@ -76,7 +78,9 @@ import org.identityconnectors.framework.common.objects.Name; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.ObjectClassInfo; +import org.identityconnectors.framework.common.objects.OperationOptionsBuilder; import org.identityconnectors.framework.common.objects.OperationalAttributes; +import org.identityconnectors.framework.common.objects.ResultsHandler; import org.identityconnectors.framework.common.objects.Uid; public class ADUtilities { @@ -270,7 +274,13 @@ public ConnectorObject createConnectorObject( return createConnectorObject(baseDN, result.getAttributes(), attrsToGet, oclass); } - public ConnectorObject createConnectorObject( + /** + * This utility method is meant to build a minimal connector object without any other parsed attribute but + * userPrincipalName and objectGUID. + * This implementation is useful when searching for entries to be updated in getEntryToBeUpdated that require + * original values in the {@link org.identityconnectors.framework.common.objects.ConnectorObject} + */ + private ConnectorObject createMinimalConnectorObject( final String baseDN, final Attributes profile, final Collection attrsToGet, @@ -280,15 +290,40 @@ public ConnectorObject createConnectorObject( final LdapEntry entry = LdapEntry.create(baseDN, profile); final ConnectorObjectBuilder builder = new ConnectorObjectBuilder(); - builder.setObjectClass(oclass); - if (OBJECTGUID.equals(connection.getSchemaMapping().getLdapUidAttribute(oclass))) { - builder.setUid(GUID.getGuidAsString((byte[]) entry.getAttributes().get(OBJECTGUID).get())); - } else { - builder.setUid(connection.getSchemaMapping().createUid(oclass, entry)); + initConnectorObjectBuilder(entry, oclass, builder); + + for (String attributeName : attrsToGet) { + Attribute attribute = null; + + if (UACCONTROL_ATTR.equalsIgnoreCase(attributeName) && oclass.is(ObjectClass.ACCOUNT_NAME)) { + attribute = manageUACAttribute(profile, oclass, entry, builder, attributeName); + } else if (OBJECTGUID.equalsIgnoreCase(attributeName)) { + attribute = AttributeBuilder.build( + attributeName, GUID.getGuidAsString((byte[]) profile.get(OBJECTGUID).get())); + } else if (profile.get(attributeName) != null) { + attribute = connection.getSchemaMapping().createAttribute(oclass, attributeName, entry, false); + } + + // Avoid attribute adding in case of attribute name not found + Optional.ofNullable(attribute).ifPresent(builder::addAttribute); } - builder.setName(connection.getSchemaMapping().createName(oclass, entry)); + return builder.build(); + } + + public ConnectorObject createConnectorObject( + final String baseDN, + final Attributes profile, + final Collection attrsToGet, + final ObjectClass oclass) + throws NamingException { + + final LdapEntry entry = LdapEntry.create(baseDN, profile); + + final ConnectorObjectBuilder builder = new ConnectorObjectBuilder(); + + initConnectorObjectBuilder(entry, oclass, builder); String pgDN = null; @@ -332,28 +367,7 @@ public ConnectorObject createConnectorObject( LOG.error(e, "While fetching " + UACCONTROL_ATTR); } } else if (UACCONTROL_ATTR.equalsIgnoreCase(attributeName) && oclass.is(ObjectClass.ACCOUNT_NAME)) { - try { - - final String status = profile.get(UACCONTROL_ATTR) == null - || profile.get(UACCONTROL_ATTR).get() == null - ? null : profile.get(UACCONTROL_ATTR).get().toString(); - - if (LOG.isOk()) { - LOG.ok("User Account Control: {0}", status); - } - - // enabled if UF_ACCOUNTDISABLE is not included (0x00002) - builder.addAttribute( - status == null || Integer.parseInt( - profile.get(UACCONTROL_ATTR).get().toString()) - % 16 != UF_ACCOUNTDISABLE - ? AttributeBuilder.buildEnabled(true) - : AttributeBuilder.buildEnabled(false)); - - attribute = connection.getSchemaMapping().createAttribute(oclass, attributeName, entry, false); - } catch (NamingException e) { - LOG.error(e, "While fetching " + UACCONTROL_ATTR); - } + attribute = manageUACAttribute(profile, oclass, entry, builder, attributeName); } else if (OBJECTGUID.equalsIgnoreCase(attributeName)) { attribute = AttributeBuilder.build( attributeName, GUID.getGuidAsString((byte[]) profile.get(OBJECTGUID).get())); @@ -516,15 +530,40 @@ public LdapEntry getEntryToBeUpdated(final String entryDN) { } public ConnectorObject getEntryToBeUpdated(final Uid uid, final ObjectClass oclass) { + OperationOptionsBuilder builder = new OperationOptionsBuilder(); + builder.setAttributesToGet(Arrays.asList(UACCONTROL_ATTR, SDDL_ATTR, OBJECTSID, PRIMARYGROUPID)); + final String filter = connection.getSchemaMapping().getLdapUidAttribute(oclass) + "=" + uid.getUidValue(); - final ConnectorObject obj = LdapSearches.findObject( - connection, oclass, + LOG.ok("Searching for object of class {0} with filter {1}", oclass.getObjectClassValue(), filter); + + final ConnectorObject[] results = new ConnectorObject[] { null }; + ResultsHandler handler = new ResultsHandler() { + + @Override + public boolean handle(final ConnectorObject connectorObject) { + results[0] = connectorObject; + return false; + } + }; + new ADSearch(connection, + oclass, LdapFilter.forNativeFilter(filter), - UACCONTROL_ATTR, - SDDL_ATTR, - OBJECTSID, - PRIMARYGROUPID); + handler, + builder.build()).executeADQuery( + handler, + (result, attrsToGet) -> { + try { + return createMinimalConnectorObject(result.getNameInNamespace(), + result.getAttributes(), + attrsToGet, + oclass); + } catch (NamingException e) { + throw new ConnectorException(e); + } + }); + + ConnectorObject obj = results[0]; if (obj == null) { throw new ConnectorException("Entry not found"); @@ -643,4 +682,51 @@ private String filterInOr(final String attr, final String... values) { } return builder.toString(); } + + private void initConnectorObjectBuilder( + final LdapEntry entry, + final ObjectClass oclass, + final ConnectorObjectBuilder builder) throws NamingException { + + builder.setObjectClass(oclass); + + if (OBJECTGUID.equals(connection.getSchemaMapping().getLdapUidAttribute(oclass))) { + builder.setUid(GUID.getGuidAsString((byte[]) entry.getAttributes().get(OBJECTGUID).get())); + } else { + builder.setUid(connection.getSchemaMapping().createUid(oclass, entry)); + } + + builder.setName(connection.getSchemaMapping().createName(oclass, entry)); + } + + private Attribute manageUACAttribute(final Attributes profile, + final ObjectClass oclass, + final LdapEntry entry, + final ConnectorObjectBuilder builder, + final String attributeName) { + + Attribute attribute = null; + try { + final String status = profile.get(UACCONTROL_ATTR) == null + || profile.get(UACCONTROL_ATTR).get() == null + ? null : profile.get(UACCONTROL_ATTR).get().toString(); + + if (LOG.isOk()) { + LOG.ok("User Account Control: {0}", status); + } + + // enabled if UF_ACCOUNTDISABLE is not included (0x00002) + builder.addAttribute( + status == null || Integer.parseInt( + profile.get(UACCONTROL_ATTR).get().toString()) + % 16 != UF_ACCOUNTDISABLE + ? AttributeBuilder.buildEnabled(true) + : AttributeBuilder.buildEnabled(false)); + + attribute = connection.getSchemaMapping().createAttribute(oclass, attributeName, entry, false); + } catch (NamingException e) { + LOG.error(e, "While fetching " + UACCONTROL_ATTR); + } + return attribute; + } } diff --git a/src/test/java/net/tirasa/connid/bundles/ad/crud/UserCrudTestITCase.java b/src/test/java/net/tirasa/connid/bundles/ad/crud/UserCrudTestITCase.java index 7ded139..b695c5f 100644 --- a/src/test/java/net/tirasa/connid/bundles/ad/crud/UserCrudTestITCase.java +++ b/src/test/java/net/tirasa/connid/bundles/ad/crud/UserCrudTestITCase.java @@ -1103,7 +1103,7 @@ public void searchByObjectGUID() { // create options for returning attributes OperationOptionsBuilder oob = new OperationOptionsBuilder(); - oob.setAttributesToGet("sAMAccountName", ADConnector.OBJECTGUID); + oob.setAttributesToGet("sAMAccountName", ADConnector.OBJECTGUID, "givenName", "sn"); // ----------------------------------------------------- // Create new user @@ -1127,7 +1127,7 @@ public void searchByObjectGUID() { attributes.add(AttributeBuilder.build("givenName", Collections.singletonList("gntest"))); attributes.add(AttributeBuilder.build("displayName", Collections.singletonList("dntest"))); - final Uid uid = newConnector.create(ObjectClass.ACCOUNT, attributes, null); + Uid uid = newConnector.create(ObjectClass.ACCOUNT, attributes, null); assertNotNull(uid); // ----------------------------------------------------- @@ -1155,6 +1155,33 @@ public void searchByObjectGUID() { assertEquals(objectguid.getValue(), results.get(0).getUid().getValue()); // ----------------------------------------------------- + + // ----------------------------------------------------- + // ----------------------------------------------------- + // Update user + // ----------------------------------------------------- + List attrToReplace = Arrays.asList(new Attribute[] { + AttributeBuilder.build("givenName", "gnupdate"), + AttributeBuilder.build("sn", "snupdate"), + AttributeBuilder.buildPassword( + new GuardedString("Password321".toCharArray())) }); + + uid = newConnector.update( + ObjectClass.ACCOUNT, + uid, + new HashSet<>(attrToReplace), + null); + + assertNotNull(uid); + + final ConnectorObject object = newConnector.getObject(ObjectClass.ACCOUNT, uid, oob.build()); + + assertNotNull(object); + assertNotNull(object.getAttributes()); + assertNotNull(object.getAttributeByName("givenName")); + assertEquals(Collections.singletonList("gnupdate"), object.getAttributeByName("givenName").getValue()); + assertNotNull(object.getAttributeByName("sn")); + assertEquals(Collections.singletonList("snupdate"), object.getAttributeByName("sn").getValue()); } finally { newConnector.delete(ObjectClass.ACCOUNT, uid, null); }