LDAP password pull and propagation improvements
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 @@
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 @@
}
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 @@
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 @@
@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);
+ 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);
}
- @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));
- }
- }
-
- 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 @@
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.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 @@
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);
- ldapPasswordPullActions.after(profile, syncDelta, entity, result);
+ 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();
+
+ 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 @@
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 @@
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