This is an automated email from the ASF dual-hosted git repository. ilgrosso pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/master by this push: new 9ae236d LDAP password pull and propagation improvements 9ae236d is described below commit 9ae236d46f719b5b87df2d158548f29c989fb3b3 Author: Francesco Chicchiriccò <ilgro...@apache.org> AuthorDate: Tue Jul 13 15:37:25 2021 +0200 LDAP password pull and propagation improvements --- .../LDAPPasswordPropagationActions.java | 29 ++---- .../java/pushpull/LDAPPasswordPullActions.java | 74 ++++++--------- .../java/pushpull/LDAPPasswordPullActionsTest.java | 102 +++++++-------------- .../wa/src/main/resources/application.properties | 2 +- .../src/main/resources/application.properties | 2 +- 5 files changed, 75 insertions(+), 134 deletions(-) diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java index 50bb663..6852268 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java @@ -20,12 +20,10 @@ package org.apache.syncope.core.provisioning.java.propagation; import java.util.Base64; import java.util.HashSet; -import java.util.Optional; import java.util.Set; import javax.xml.bind.DatatypeConverter; import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.syncope.common.lib.types.CipherAlgorithm; -import org.apache.syncope.common.lib.types.ConnConfProperty; import org.apache.syncope.core.persistence.api.dao.UserDAO; import org.apache.syncope.core.persistence.api.entity.ConnInstance; import org.apache.syncope.core.persistence.api.entity.task.PropagationTask; @@ -90,33 +88,24 @@ public class LDAPPasswordPropagationActions implements PropagationActions { } private static String getCipherAlgorithm(final ConnInstance connInstance) { - Optional<ConnConfProperty> cipherAlgorithm = connInstance.getConf().stream(). + return connInstance.getConf().stream(). filter(property -> "passwordHashAlgorithm".equals(property.getSchema().getName()) - && property.getValues() != null && !property.getValues().isEmpty()).findFirst(); - - return cipherAlgorithm.isPresent() - ? (String) cipherAlgorithm.get().getValues().get(0) - : CLEARTEXT; + && property.getValues() != null && !property.getValues().isEmpty()).findFirst(). + map(cipherAlgorithm -> (String) cipherAlgorithm.getValues().get(0)). + orElse(CLEARTEXT); } - private static boolean cipherAlgorithmMatches(final String connectorAlgorithm, - final CipherAlgorithm userAlgorithm) { - if (userAlgorithm == null) { + private static boolean cipherAlgorithmMatches(final String connectorAlgo, final CipherAlgorithm userAlgo) { + if (userAlgo == null) { return false; } - if (connectorAlgorithm.equals(userAlgorithm.name())) { + if (connectorAlgo.equals(userAlgo.name())) { return true; } // Special check for "SHA" and "SSHA" (user pulled from LDAP) - if ("SHA".equals(connectorAlgorithm) && userAlgorithm.name().startsWith("SHA") - || "SSHA".equals(connectorAlgorithm) && userAlgorithm.name().startsWith("SSHA")) { - - return true; - } - - return false; + return ("SHA".equals(connectorAlgo) && userAlgo.name().startsWith("SHA")) + || ("SSHA".equals(connectorAlgo) && userAlgo.name().startsWith("SSHA")); } - } diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActions.java index a408fb9..1796334 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActions.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActions.java @@ -20,21 +20,23 @@ package org.apache.syncope.core.provisioning.java.pushpull; import java.util.Base64; import java.util.Optional; +import java.util.Set; import javax.xml.bind.DatatypeConverter; -import org.apache.syncope.common.lib.request.AbstractPatchItem; -import org.apache.syncope.common.lib.request.AnyCR; -import org.apache.syncope.common.lib.request.AnyUR; -import org.apache.syncope.common.lib.request.PasswordPatch; -import org.apache.syncope.common.lib.request.UserCR; -import org.apache.syncope.common.lib.request.UserUR; +import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.common.lib.to.EntityTO; -import org.apache.syncope.common.lib.to.ProvisioningReport; import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.common.lib.types.CipherAlgorithm; import org.apache.syncope.core.persistence.api.dao.UserDAO; import org.apache.syncope.core.persistence.api.entity.user.User; import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile; +import org.apache.syncope.common.lib.to.ProvisioningReport; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.core.persistence.api.entity.resource.Provision; import org.apache.syncope.core.provisioning.api.pushpull.PullActions; +import org.identityconnectors.common.security.GuardedString; +import org.identityconnectors.common.security.SecurityUtil; +import org.identityconnectors.framework.common.objects.AttributeUtil; +import org.identityconnectors.framework.common.objects.OperationalAttributes; import org.identityconnectors.framework.common.objects.SyncDelta; import org.quartz.JobExecutionException; import org.slf4j.Logger; @@ -53,49 +55,28 @@ public class LDAPPasswordPullActions implements PullActions { @Autowired protected UserDAO userDAO; - protected String encodedPassword; - - protected CipherAlgorithm cipher; - - @Transactional(readOnly = true) - @Override - public void beforeProvision( - final ProvisioningProfile<?, ?> profile, - final SyncDelta delta, - final AnyCR anyCR) throws JobExecutionException { - - if (anyCR instanceof UserCR) { - String password = ((UserCR) anyCR).getPassword(); - parseEncodedPassword(password); - } - } - - @Transactional(readOnly = true) @Override - public void beforeUpdate( - final ProvisioningProfile<?, ?> profile, - final SyncDelta delta, - final EntityTO entityTO, - final AnyUR anyUR) throws JobExecutionException { - - if (anyUR instanceof UserUR) { - PasswordPatch modPassword = ((UserUR) anyUR).getPassword(); - parseEncodedPassword(Optional.ofNullable(modPassword).map(AbstractPatchItem::getValue).orElse(null)); + public Set<String> moreAttrsToGet(final ProvisioningProfile<?, ?> profile, final Provision provision) { + if (AnyTypeKind.USER == provision.getAnyType().getKind()) { + return Set.of(OperationalAttributes.PASSWORD_NAME); } + return PullActions.super.moreAttrsToGet(profile, provision); } - protected void parseEncodedPassword(final String password) { + private static Optional<Pair<String, CipherAlgorithm>> parseEncodedPassword(final String password) { if (password != null && password.startsWith("{")) { + String digest = Optional.ofNullable( + password.substring(1, password.indexOf('}'))).map(String::toUpperCase). + orElse(null); int closingBracketIndex = password.indexOf('}'); - String digest = password.substring(1, password.indexOf('}')).toUpperCase(); try { - encodedPassword = password.substring(closingBracketIndex + 1); - cipher = CipherAlgorithm.valueOf(digest); + return Optional.of( + Pair.of(password.substring(closingBracketIndex + 1), CipherAlgorithm.valueOf(digest))); } catch (IllegalArgumentException e) { LOG.error("Cipher algorithm not allowed: {}", digest, e); - encodedPassword = null; } } + return Optional.empty(); } @Transactional @@ -106,16 +87,19 @@ public class LDAPPasswordPullActions implements PullActions { final EntityTO entity, final ProvisioningReport result) throws JobExecutionException { - if (entity instanceof UserTO && encodedPassword != null && cipher != null) { + if (entity instanceof UserTO) { User user = userDAO.find(entity.getKey()); if (user != null) { - byte[] encodedPasswordBytes = Base64.getDecoder().decode(encodedPassword.getBytes()); - String encodedHexStr = DatatypeConverter.printHexBinary(encodedPasswordBytes).toUpperCase(); + GuardedString passwordAttr = AttributeUtil.getPasswordValue(delta.getObject().getAttributes()); + if (passwordAttr != null) { + parseEncodedPassword(SecurityUtil.decrypt(passwordAttr)).ifPresent(encoded -> { + byte[] encodedPasswordBytes = Base64.getDecoder().decode(encoded.getLeft().getBytes()); + String encodedHexStr = DatatypeConverter.printHexBinary(encodedPasswordBytes).toUpperCase(); - user.setEncodedPassword(encodedHexStr, cipher); + user.setEncodedPassword(encodedHexStr, encoded.getRight()); + }); + } } - encodedPassword = null; - cipher = null; } } } diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActionsTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActionsTest.java index 1b833eb..736b678 100644 --- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActionsTest.java +++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActionsTest.java @@ -18,20 +18,15 @@ */ package org.apache.syncope.core.provisioning.java.pushpull; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import static org.mockito.Mockito.verify; -import org.apache.syncope.common.lib.SyncopeConstants; -import org.apache.syncope.common.lib.request.AnyCR; -import org.apache.syncope.common.lib.request.AnyUR; -import org.apache.syncope.common.lib.request.PasswordPatch; -import org.apache.syncope.common.lib.request.UserCR; -import org.apache.syncope.common.lib.request.UserUR; -import org.apache.syncope.common.lib.to.EntityTO; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; import org.apache.syncope.common.lib.to.ProvisioningReport; import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.common.lib.types.CipherAlgorithm; @@ -39,20 +34,25 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO; import org.apache.syncope.core.persistence.api.entity.user.User; import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile; import org.apache.syncope.core.provisioning.java.AbstractTest; +import org.identityconnectors.common.security.GuardedString; +import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.AttributeBuilder; +import org.identityconnectors.framework.common.objects.ConnectorObject; +import org.identityconnectors.framework.common.objects.Name; +import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.SyncDelta; -import org.junit.jupiter.api.BeforeEach; +import org.identityconnectors.framework.common.objects.SyncDeltaBuilder; +import org.identityconnectors.framework.common.objects.SyncDeltaType; +import org.identityconnectors.framework.common.objects.SyncToken; +import org.identityconnectors.framework.common.objects.Uid; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.quartz.JobExecutionException; -import org.springframework.test.util.ReflectionTestUtils; public class LDAPPasswordPullActionsTest extends AbstractTest { @Mock - private SyncDelta syncDelta; - - @Mock private ProvisioningProfile<?, ?> profile; @Mock @@ -62,70 +62,38 @@ public class LDAPPasswordPullActionsTest extends AbstractTest { private ProvisioningReport result; @InjectMocks - private LDAPPasswordPullActions ldapPasswordPullActions; - - private AnyCR anyCR; - - private AnyUR anyUR; - - private EntityTO entity; - - private String encodedPassword; - - private CipherAlgorithm cipher; - - @BeforeEach - public void initTest() { - entity = new UserTO(); - encodedPassword = "s3cureP4ssw0rd"; - cipher = CipherAlgorithm.SHA512; - - ReflectionTestUtils.setField(ldapPasswordPullActions, "encodedPassword", encodedPassword); - ReflectionTestUtils.setField(ldapPasswordPullActions, "cipher", cipher); - } - - @Test - public void beforeProvision() throws JobExecutionException { - String digest = "SHA256"; - String password = "t3stPassw0rd"; - anyCR = new UserCR.Builder(SyncopeConstants.ROOT_REALM, "username"). - password(String.format("{%s}%s", digest, password)).build(); - - ldapPasswordPullActions.beforeProvision(profile, syncDelta, anyCR); - assertEquals(CipherAlgorithm.valueOf(digest), ReflectionTestUtils.getField(ldapPasswordPullActions, "cipher")); - assertEquals(password, ReflectionTestUtils.getField(ldapPasswordPullActions, "encodedPassword")); - } - - @Test - public void beforeUpdate() throws JobExecutionException { - anyUR = new UserUR.Builder(null). - password(new PasswordPatch.Builder().value("{MD5}an0therTestP4ss").build()). - build(); - - ldapPasswordPullActions.beforeUpdate(profile, syncDelta, entity, anyUR); - - assertNull(ReflectionTestUtils.getField(ldapPasswordPullActions, "encodedPassword")); - } + private LDAPPasswordPullActions actions; @Test public void afterWithNullUser() throws JobExecutionException { - when(userDAO.find(entity.getKey())).thenReturn(null); + UserTO userTO = new UserTO(); + userTO.setKey(UUID.randomUUID().toString()); + when(userDAO.find(userTO.getKey())).thenReturn(null); - ldapPasswordPullActions.after(profile, syncDelta, entity, result); - - assertNull(ReflectionTestUtils.getField(ldapPasswordPullActions, "encodedPassword")); - assertNull(ReflectionTestUtils.getField(ldapPasswordPullActions, "cipher")); + assertDoesNotThrow(() -> actions.after(profile, null, userTO, result)); } @Test public void after(@Mock User user) throws JobExecutionException { - when(userDAO.find(entity.getKey())).thenReturn(user); + UserTO userTO = new UserTO(); + userTO.setKey(UUID.randomUUID().toString()); + when(userDAO.find(userTO.getKey())).thenReturn(user); + + Set<Attribute> attributes = new HashSet<>(); + attributes.add(new Uid(UUID.randomUUID().toString())); + attributes.add(new Name(UUID.randomUUID().toString())); + attributes.add(AttributeBuilder.buildPassword( + new GuardedString("{SSHA}4AwQq1UVDwubSXmR4pnmLsoVR6U2Z7R55kwxRA==".toCharArray()))); + SyncDelta delta = new SyncDeltaBuilder(). + setToken(new SyncToken("sample-token")). + setDeltaType(SyncDeltaType.CREATE_OR_UPDATE). + setUid(new Uid(UUID.randomUUID().toString())). + setObject(new ConnectorObject(ObjectClass.ACCOUNT, attributes)). + build(); - ldapPasswordPullActions.after(profile, syncDelta, entity, result); + actions.after(profile, delta, userTO, result); verify(user).setEncodedPassword(anyString(), any(CipherAlgorithm.class)); - assertNull(ReflectionTestUtils.getField(ldapPasswordPullActions, "encodedPassword")); - assertNull(ReflectionTestUtils.getField(ldapPasswordPullActions, "cipher")); } } diff --git a/docker/wa/src/main/resources/application.properties b/docker/wa/src/main/resources/application.properties index 8446992..b8b2171 100644 --- a/docker/wa/src/main/resources/application.properties +++ b/docker/wa/src/main/resources/application.properties @@ -34,7 +34,7 @@ spring.web.resources.static-locations=classpath:/thymeleaf/static,classpath:/syn cas.monitor.endpoints.endpoint.defaults.access=AUTHENTICATED management.endpoints.enabled-by-default=true -management.endpoints.web.exposure.include=info,health,loggers,ssoSessions +management.endpoints.web.exposure.include=info,health,loggers,ssoSessions,registeredServices management.endpoint.health.show-details=ALWAYS spring.cloud.discovery.client.health-indicator.enabled=false diff --git a/wa/starter/src/main/resources/application.properties b/wa/starter/src/main/resources/application.properties index 8a714d9..4e1b1c2 100644 --- a/wa/starter/src/main/resources/application.properties +++ b/wa/starter/src/main/resources/application.properties @@ -34,7 +34,7 @@ spring.web.resources.static-locations=classpath:/thymeleaf/static,classpath:/syn cas.monitor.endpoints.endpoint.defaults.access=AUTHENTICATED management.endpoints.enabled-by-default=true -management.endpoints.web.exposure.include=info,health,loggers,ssoSessions +management.endpoints.web.exposure.include=info,health,loggers,ssoSessions,registeredServices management.endpoint.health.show-details=ALWAYS spring.cloud.discovery.client.health-indicator.enabled=false