From c877b2952cdd03ab05bb26947e83a27d86fe5f89 Mon Sep 17 00:00:00 2001 From: Andrea Patricelli Date: Wed, 9 Oct 2024 14:23:35 +0200 Subject: [PATCH] [AD-78] managed password not required flag on create and update (#29) (#30) --- pom.xml | 2 +- .../connid/bundles/ad/ADConfiguration.java | 2 + .../connid/bundles/ad/crud/ADCreate.java | 16 ++ .../connid/bundles/ad/crud/ADUpdate.java | 20 ++- .../bundles/ad/schema/ADSchemaBuilder.java | 1 + .../connid/bundles/ad/util/ADUtilities.java | 19 +++ .../bundles/ad/crud/SchemaTestITCase.java | 8 +- .../bundles/ad/crud/UserCrudTestITCase.java | 161 +++++++++++++++--- 8 files changed, 203 insertions(+), 26 deletions(-) diff --git a/pom.xml b/pom.xml index acd2c2b..1362aef 100644 --- a/pom.xml +++ b/pom.xml @@ -369,7 +369,7 @@ - samba version 4.19.5 started + samba version ((\d)+.(\d)+.(\d)+) started diff --git a/src/main/java/net/tirasa/connid/bundles/ad/ADConfiguration.java b/src/main/java/net/tirasa/connid/bundles/ad/ADConfiguration.java index 98359f5..d2521de 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/ADConfiguration.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/ADConfiguration.java @@ -49,6 +49,8 @@ public final class ADConfiguration extends LdapConfiguration { public static final String UCCP_FLAG = "userCannotChangePassword"; public static final String PNE_FLAG = "passwordNeverExpires"; + + public static final String PNR_FLAG = "passwordNotRequired"; public static final String CN_NAME = "CN"; diff --git a/src/main/java/net/tirasa/connid/bundles/ad/crud/ADCreate.java b/src/main/java/net/tirasa/connid/bundles/ad/crud/ADCreate.java index e994336..96a3ebb 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/crud/ADCreate.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/crud/ADCreate.java @@ -132,6 +132,7 @@ protected Uid executeImpl() throws NamingException { Boolean uccp = null; Boolean pne = null; + Boolean pnr = null; Boolean status = null; for (Attribute attr : attrs) { @@ -148,6 +149,11 @@ protected Uid executeImpl() throws NamingException { if (value != null && !value.isEmpty()) { pne = (Boolean) value.get(0); } + } else if (attr.is(ADConfiguration.PNR_FLAG)) { + final List value = attr.getValue(); + if (value != null && !value.isEmpty()) { + pnr = (Boolean) value.get(0); + } } else if (attr.is(ADConfiguration.PRIMARY_GROUP_DN_NAME)) { final List value = attr.getValue(); primaryGroupDN = value == null || value.isEmpty() ? null : String.class.cast(value.get(0)); @@ -204,6 +210,16 @@ protected Uid executeImpl() throws NamingException { } } + if (pnr != null) { + if ((uacValue & ADConnector.UF_PASSWD_NOTREQD) == ADConnector.UF_PASSWD_NOTREQD && !pnr) { + uacValue -= ADConnector.UF_PASSWD_NOTREQD; + } else if ((uacValue & ADConnector.UF_PASSWD_NOTREQD) != ADConnector.UF_PASSWD_NOTREQD && pnr) { + uacValue = uacValue == -1 + ? ADConnector.UF_PASSWD_NOTREQD + : uacValue + ADConnector.UF_PASSWD_NOTREQD; + } + } + if (status != null) { if ((uacValue & UF_ACCOUNTDISABLE) == UF_ACCOUNTDISABLE && status) { uacValue -= UF_ACCOUNTDISABLE; 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 f15d46a..c5c78c9 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 @@ -228,6 +228,7 @@ protected Pair getAttributesToModify( int newUACValue = -1; Boolean pne = null; + Boolean pnr = null; Boolean status = null; for (Attribute attr : attrs) { @@ -255,6 +256,11 @@ protected Pair getAttributesToModify( if (value != null && !value.isEmpty()) { pne = (Boolean) value.get(0); } + } else if (attr.is(ADConfiguration.PNR_FLAG)) { + final List value = attr.getValue(); + if (value != null && !value.isEmpty()) { + pnr = (Boolean) value.get(0); + } } else if (attr.is(ADConfiguration.PROMPT_USER_FLAG)) { final List value = attr.getValue(); if (value != null && !value.isEmpty()) { @@ -307,11 +313,21 @@ protected Pair getAttributesToModify( } } + if (pnr != null) { + if ((currentUACValue & ADConnector.UF_PASSWD_NOTREQD) + == ADConnector.UF_PASSWD_NOTREQD && !pnr) { + newUACValue = (newUACValue == -1 ? currentUACValue : newUACValue) - ADConnector.UF_PASSWD_NOTREQD; + } else if ((currentUACValue & ADConnector.UF_PASSWD_NOTREQD) + != ADConnector.UF_PASSWD_NOTREQD && pnr) { + newUACValue = (newUACValue == -1 ? currentUACValue : newUACValue) + ADConnector.UF_PASSWD_NOTREQD; + } + } + if (status != null) { if ((currentUACValue & UF_ACCOUNTDISABLE) == UF_ACCOUNTDISABLE && status) { - newUACValue = currentUACValue - UF_ACCOUNTDISABLE; + newUACValue = (newUACValue == -1 ? currentUACValue : newUACValue) - UF_ACCOUNTDISABLE; } else if ((currentUACValue & UF_ACCOUNTDISABLE) != UF_ACCOUNTDISABLE && !status) { - newUACValue = currentUACValue + UF_ACCOUNTDISABLE; + newUACValue = (newUACValue == -1 ? currentUACValue : newUACValue) + UF_ACCOUNTDISABLE; } } diff --git a/src/main/java/net/tirasa/connid/bundles/ad/schema/ADSchemaBuilder.java b/src/main/java/net/tirasa/connid/bundles/ad/schema/ADSchemaBuilder.java index b065375..25a3837 100644 --- a/src/main/java/net/tirasa/connid/bundles/ad/schema/ADSchemaBuilder.java +++ b/src/main/java/net/tirasa/connid/bundles/ad/schema/ADSchemaBuilder.java @@ -149,6 +149,7 @@ private void build(final String oname, final SchemaBuilder schemaBld) { objClassBld.addAttributeInfo(AttributeInfoBuilder.build(ADConfiguration.LOCK_OUT_FLAG, Boolean.class)); objClassBld.addAttributeInfo(AttributeInfoBuilder.build(ADConfiguration.PROMPT_USER_FLAG, Boolean.class)); objClassBld.addAttributeInfo(AttributeInfoBuilder.build(ADConfiguration.PNE_FLAG, Boolean.class)); + objClassBld.addAttributeInfo(AttributeInfoBuilder.build(ADConfiguration.PNR_FLAG, Boolean.class)); objClassBld.addAttributeInfo(AttributeInfoBuilder.build(ADConfiguration.PRIMARY_GROUP_DN_NAME, String.class)); final ObjectClassInfo oci = objClassBld.build(); 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 f9c24ca..b808d17 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 @@ -16,6 +16,7 @@ package net.tirasa.connid.bundles.ad.util; import static net.tirasa.connid.bundles.ad.ADConfiguration.PNE_FLAG; +import static net.tirasa.connid.bundles.ad.ADConfiguration.PNR_FLAG; import static net.tirasa.connid.bundles.ad.ADConfiguration.PRIMARY_GROUP_DN_NAME; import static net.tirasa.connid.bundles.ad.ADConfiguration.UCCP_FLAG; import static net.tirasa.connid.bundles.ad.ADConnector.OBJECTGUID; @@ -26,6 +27,7 @@ import static net.tirasa.connid.bundles.ad.ADConnector.UF_ACCOUNTDISABLE; import static net.tirasa.connid.bundles.ad.ADConnector.ADDS2012_ATTRIBUTES_TO_BE_REMOVED; import static net.tirasa.connid.bundles.ad.ADConnector.UF_DONT_EXPIRE_PASSWD; +import static net.tirasa.connid.bundles.ad.ADConnector.UF_PASSWD_NOTREQD; import static net.tirasa.connid.bundles.ldap.commons.LdapUtil.escapeAttrValue; import static org.identityconnectors.common.CollectionUtil.newCaseInsensitiveSet; import static org.identityconnectors.common.CollectionUtil.newSet; @@ -341,6 +343,23 @@ public ConnectorObject createConnectorObject( } catch (NamingException e) { LOG.error(e, "While fetching " + UACCONTROL_ATTR); } + } else if (PNR_FLAG.equalsIgnoreCase(attributeName) && oclass.is(ObjectClass.ACCOUNT_NAME)) { + try { + + final String uac = + 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}", uac); + } + + // disabled if PNR_FLAG is not included (0x0020) + attribute = uac == null || (Integer.parseInt(uac) & UF_PASSWD_NOTREQD) != UF_PASSWD_NOTREQD + ? AttributeBuilder.build(PNR_FLAG, false) : AttributeBuilder.build(PNR_FLAG, true); + } catch (NamingException e) { + LOG.error(e, "While fetching " + UACCONTROL_ATTR); + } } else if (UACCONTROL_ATTR.equalsIgnoreCase(attributeName) && oclass.is(ObjectClass.ACCOUNT_NAME)) { attribute = manageUACAttribute(profile, oclass, entry, builder, attributeName); } else if (OBJECTGUID.equalsIgnoreCase(attributeName)) { diff --git a/src/test/java/net/tirasa/connid/bundles/ad/crud/SchemaTestITCase.java b/src/test/java/net/tirasa/connid/bundles/ad/crud/SchemaTestITCase.java index 2ac7b34..46fcfe4 100644 --- a/src/test/java/net/tirasa/connid/bundles/ad/crud/SchemaTestITCase.java +++ b/src/test/java/net/tirasa/connid/bundles/ad/crud/SchemaTestITCase.java @@ -47,6 +47,7 @@ public void schema() { boolean sddl = false; boolean pne = false; + boolean pnr = false; boolean givenname = false; for (AttributeInfo attrInfo : info.getAttributeInfo()) { @@ -59,6 +60,11 @@ public void schema() { pne = true; assertEquals(Boolean.class, attrInfo.getType()); } + + if (ADConfiguration.PNR_FLAG.equals(attrInfo.getName())) { + pnr = true; + assertEquals(Boolean.class, attrInfo.getType()); + } if ("givenName".equalsIgnoreCase(attrInfo.getName())) { givenname = true; @@ -66,6 +72,6 @@ public void schema() { } } - assertTrue(sddl && givenname && pne); + assertTrue(sddl && givenname && pne && pnr); } } 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 a6f95c8..8609b34 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 @@ -164,6 +164,8 @@ public void create() { assertNull(connector.getObject(ObjectClass.ACCOUNT, new Uid(ids.getValue()), null)); final Set attributes = util.getSimpleProfile(ids); + attributes.add(AttributeBuilder.build(ADConfiguration.PNE_FLAG, true)); + attributes.add(AttributeBuilder.build(ADConfiguration.PNR_FLAG, true)); final Uid uid = connector.create(ObjectClass.ACCOUNT, attributes, null); assertNotNull(uid); @@ -1768,40 +1770,155 @@ public void issueAD58WithoutDN() { assertNull(connector.getObject(ObjectClass.ACCOUNT, uid, null)); } + @Test public void issueAD61() { assertNotNull(connector); assertNotNull(conf); - final Map.Entry ids = util.getEntryIDs("pne"); - final Set attributes = util.getSimpleProfile(ids, false); - attributes.add(AttributeBuilder.build(ADConfiguration.PNE_FLAG, true)); + final Map.Entry ids = util.getEntryIDs("AD61NN"); + Uid uid = new Uid(ids.getValue()); + try { + assertNull(connector.getObject(ObjectClass.ACCOUNT, uid, null)); - final Uid uid = connector.create(ObjectClass.ACCOUNT, attributes, null); + final Set attributes = util.getSimpleProfile(ids); + attributes.add(AttributeBuilder.build(ADConfiguration.PNE_FLAG, true)); - // Ask just for memberOf - final OperationOptionsBuilder oob = new OperationOptionsBuilder(); - oob.setAttributesToGet(UACCONTROL_ATTR, ADConfiguration.PNE_FLAG); + uid = connector.create(ObjectClass.ACCOUNT, attributes, null); - // retrieve created object - ConnectorObject object = connector.getObject(ObjectClass.ACCOUNT, uid, oob.build()); - assertTrue(Boolean.class.cast(object.getAttributeByName(ADConfiguration.PNE_FLAG).getValue().get(0))); - int uac_before = Integer.parseInt(object.getAttributeByName(UACCONTROL_ATTR).getValue().get(0).toString()); + // Ask just for memberOf + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet(UACCONTROL_ATTR, ADConfiguration.PNE_FLAG); - // remove password never expire flag - List attrToReplace = Arrays.asList(new Attribute[] { - AttributeBuilder.build(ADConfiguration.PNE_FLAG, false) }); + // retrieve created object + ConnectorObject object = connector.getObject(ObjectClass.ACCOUNT, uid, oob.build()); + assertTrue(Boolean.class.cast(object.getAttributeByName(ADConfiguration.PNE_FLAG).getValue().get(0))); + int uac_before = Integer.parseInt(object.getAttributeByName(UACCONTROL_ATTR).getValue().get(0).toString()); - connector.update(ObjectClass.ACCOUNT, uid, new HashSet<>(attrToReplace), null); + // remove password never expire flag and add password not required one + List attrToReplace = Arrays.asList( + new Attribute[] { AttributeBuilder.build(ADConfiguration.PNE_FLAG, false) }); - object = connector.getObject(ObjectClass.ACCOUNT, uid, oob.build()); - assertFalse(Boolean.class.cast(object.getAttributeByName(ADConfiguration.PNE_FLAG).getValue().get(0))); - int uac_after = Integer.parseInt(object.getAttributeByName(UACCONTROL_ATTR).getValue().get(0).toString()); + connector.update(ObjectClass.ACCOUNT, uid, new HashSet<>(attrToReplace), null); - assertNotEquals(uac_before, uac_after); + object = connector.getObject(ObjectClass.ACCOUNT, uid, oob.build()); + assertFalse(Boolean.class.cast(object.getAttributeByName(ADConfiguration.PNE_FLAG).getValue().get(0))); + int uac_after = Integer.parseInt(object.getAttributeByName(UACCONTROL_ATTR).getValue().get(0).toString()); - // remove user - connector.delete(ObjectClass.ACCOUNT, uid, null); - assertNull(connector.getObject(ObjectClass.ACCOUNT, uid, null)); + assertNotEquals(uac_before, uac_after); + } finally { + // remove user + connector.delete(ObjectClass.ACCOUNT, uid, null); + assertNull(connector.getObject(ObjectClass.ACCOUNT, uid, null)); + } + } + + @Test + public void issueAD78() { + // test update of both PNR and PNE + assertNotNull(connector); + assertNotNull(conf); + + final Map.Entry ids = util.getEntryIDs("AD78UPD"); + Uid uid = new Uid(ids.getValue()); + try { + assertNull(connector.getObject(ObjectClass.ACCOUNT, uid, null)); + + final Set attributes = util.getSimpleProfile(ids); + attributes.add(AttributeBuilder.build(ADConfiguration.PNE_FLAG, true)); + attributes.add(AttributeBuilder.build(ADConfiguration.PNR_FLAG, false)); + + uid = connector.create(ObjectClass.ACCOUNT, attributes, null); + + // Ask just for uac, pne and pnr + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet(UACCONTROL_ATTR, ADConfiguration.PNE_FLAG, ADConfiguration.PNR_FLAG); + + // retrieve created object + ConnectorObject object = connector.getObject(ObjectClass.ACCOUNT, uid, oob.build()); + assertTrue(Boolean.class.cast(object.getAttributeByName(ADConfiguration.PNE_FLAG).getValue().get(0))); + assertFalse(Boolean.class.cast(object.getAttributeByName(ADConfiguration.PNR_FLAG).getValue().get(0))); + int uac_before = Integer.parseInt(object.getAttributeByName(UACCONTROL_ATTR).getValue().get(0).toString()); + + // remove password never expire flag and add password not required one + List attrToReplace = Arrays.asList( + new Attribute[] { AttributeBuilder.build(ADConfiguration.PNE_FLAG, false), + AttributeBuilder.build(ADConfiguration.PNR_FLAG, true) }); + + connector.update(ObjectClass.ACCOUNT, uid, new HashSet<>(attrToReplace), null); + + object = connector.getObject(ObjectClass.ACCOUNT, uid, oob.build()); + assertFalse(Boolean.class.cast(object.getAttributeByName(ADConfiguration.PNE_FLAG).getValue().get(0))); + assertTrue(Boolean.class.cast(object.getAttributeByName(ADConfiguration.PNR_FLAG).getValue().get(0))); + int uac_after = Integer.parseInt(object.getAttributeByName(UACCONTROL_ATTR).getValue().get(0).toString()); + + assertNotEquals(uac_before, uac_after); + + uac_before = uac_after; + + // remove password not required + attrToReplace = Arrays.asList(new Attribute[] { AttributeBuilder.build(ADConfiguration.PNR_FLAG, false) }); + + connector.update(ObjectClass.ACCOUNT, uid, new HashSet<>(attrToReplace), null); + + object = connector.getObject(ObjectClass.ACCOUNT, uid, oob.build()); + assertFalse(Boolean.class.cast(object.getAttributeByName(ADConfiguration.PNE_FLAG).getValue().get(0))); + assertFalse(Boolean.class.cast(object.getAttributeByName(ADConfiguration.PNR_FLAG).getValue().get(0))); + uac_after = Integer.parseInt(object.getAttributeByName(UACCONTROL_ATTR).getValue().get(0).toString()); + + assertNotEquals(uac_before, uac_after); + + uac_before = uac_after; + + // add password never expires + attrToReplace = Arrays.asList(new Attribute[] { AttributeBuilder.build(ADConfiguration.PNE_FLAG, true) }); + + connector.update(ObjectClass.ACCOUNT, uid, new HashSet<>(attrToReplace), null); + + object = connector.getObject(ObjectClass.ACCOUNT, uid, oob.build()); + assertTrue(Boolean.class.cast(object.getAttributeByName(ADConfiguration.PNE_FLAG).getValue().get(0))); + assertFalse(Boolean.class.cast(object.getAttributeByName(ADConfiguration.PNR_FLAG).getValue().get(0))); + uac_after = Integer.parseInt(object.getAttributeByName(UACCONTROL_ATTR).getValue().get(0).toString()); + + assertNotEquals(uac_before, uac_after); + } finally { + // remove user + connector.delete(ObjectClass.ACCOUNT, uid, null); + assertNull(connector.getObject(ObjectClass.ACCOUNT, uid, null)); + } + } + + @Test + public void issueAD78_pne_pnr() { + // check whether both pne and pnr to true are supported + assertNotNull(connector); + assertNotNull(conf); + + final Map.Entry ids = util.getEntryIDs("AD78CR"); + Uid uid = new Uid(ids.getValue()); + try { + assertNull(connector.getObject(ObjectClass.ACCOUNT, uid, null)); + + final Set attributes = util.getSimpleProfile(ids); + attributes.add(AttributeBuilder.build(ADConfiguration.PNE_FLAG, true)); + attributes.add(AttributeBuilder.build(ADConfiguration.PNR_FLAG, true)); + + uid = connector.create(ObjectClass.ACCOUNT, attributes, null); + assertNotNull(uid); + assertEquals(ids.getValue(), uid.getUidValue()); + + // Ask just for uac, pne and pnr + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet(UACCONTROL_ATTR, ADConfiguration.PNE_FLAG, ADConfiguration.PNR_FLAG); + + // retrieve created object + ConnectorObject object = connector.getObject(ObjectClass.ACCOUNT, uid, oob.build()); + assertTrue(Boolean.class.cast(object.getAttributeByName(ADConfiguration.PNE_FLAG).getValue().get(0))); + assertTrue(Boolean.class.cast(object.getAttributeByName(ADConfiguration.PNR_FLAG).getValue().get(0))); + } finally { + // remove user + connector.delete(ObjectClass.ACCOUNT, uid, null); + assertNull(connector.getObject(ObjectClass.ACCOUNT, uid, null)); + } } @Test