This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch 3_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/3_0_X by this push:
new 48c4f70a6e [SYNCOPE-1759] New REST endpoint: /users/self/compliance
(#462)
48c4f70a6e is described below
commit 48c4f70a6e4de1aac71295db6c82ba0d288abef9
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Fri May 19 13:27:12 2023 +0200
[SYNCOPE-1759] New REST endpoint: /users/self/compliance (#462)
---
.../client/console/rest/UserSelfRestClient.java | 4 +-
.../console/implementations/MyAccountRule.groovy | 3 +
.../console/implementations/MyPasswordRule.groovy | 3 +
.../client/enduser/rest/UserSelfRestClient.java | 3 +-
.../syncope/common/lib/request/PasswordPatch.java | 1 -
.../common/rest/api/beans/ComplianceQuery.java | 155 +++++++++++++++++++++
.../common/rest/api/service/UserSelfService.java | 21 ++-
.../syncope/core/logic/IdRepoLogicContext.java | 9 +-
.../org/apache/syncope/core/logic/UserLogic.java | 102 +++++++++++---
.../core/rest/cxf/service/UserSelfServiceImpl.java | 9 +-
.../core/provisioning/api/rules/AccountRule.java | 2 +
.../core/provisioning/api/rules/PasswordRule.java | 2 +
.../rules/{PasswordRule.java => RuleEnforcer.java} | 25 ++--
.../core/spring/policy/DefaultAccountRule.java | 6 +
.../core/spring/policy/DefaultPasswordRule.java | 8 ++
.../core/spring/policy/DefaultRuleEnforcer.java | 136 ++++++++++++++++++
.../spring/policy/HaveIBeenPwnedPasswordRule.java | 5 +
.../core/spring/security/SecurityContext.java | 9 ++
.../core/spring/security/TestPasswordRule.java | 5 +
.../workflow/java/AbstractUserWorkflowAdapter.java | 102 ++------------
.../workflow/java/DefaultUserWorkflowAdapter.java | 4 +-
.../core/workflow/java/WorkflowContext.java | 3 +
.../core/flowable/FlowableWorkflowContext.java | 3 +
.../flowable/impl/FlowableUserWorkflowAdapter.java | 4 +-
.../fit/core/reference/TestAccountRule.java | 11 +-
.../fit/core/reference/TestPasswordRule.java | 7 +
.../org/apache/syncope/fit/core/UserITCase.java | 26 ++++
.../apache/syncope/fit/core/UserSelfITCase.java | 3 +-
28 files changed, 535 insertions(+), 136 deletions(-)
diff --git
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/UserSelfRestClient.java
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/UserSelfRestClient.java
index 0d8f3b6aa1..fa18cf51ba 100644
---
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/UserSelfRestClient.java
+++
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/UserSelfRestClient.java
@@ -18,6 +18,7 @@
*/
package org.apache.syncope.client.console.rest;
+import org.apache.syncope.common.lib.request.PasswordPatch;
import org.apache.syncope.common.rest.api.service.UserSelfService;
public class UserSelfRestClient extends BaseRestClient {
@@ -25,7 +26,6 @@ public class UserSelfRestClient extends BaseRestClient {
private static final long serialVersionUID = 100731599744900931L;
public static void changePassword(final String password) {
- getService(UserSelfService.class).mustChangePassword(password);
+ getService(UserSelfService.class).mustChangePassword(new
PasswordPatch.Builder().value(password).build());
}
-
}
diff --git
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/implementations/MyAccountRule.groovy
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/implementations/MyAccountRule.groovy
index 8926d21273..1826559aa4 100644
---
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/implementations/MyAccountRule.groovy
+++
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/implementations/MyAccountRule.groovy
@@ -24,6 +24,9 @@ import
org.apache.syncope.core.persistence.api.entity.user.User
@CompileStatic
class MyAccountRule implements AccountRule {
+ void enforce(String username) {
+ }
+
void enforce(User user) {
}
diff --git
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPasswordRule.groovy
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPasswordRule.groovy
index 03a1595a9a..a54f8ceb3c 100644
---
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPasswordRule.groovy
+++
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/implementations/MyPasswordRule.groovy
@@ -24,6 +24,9 @@ import
org.apache.syncope.core.persistence.api.entity.user.User
@CompileStatic
class MyPasswordRule implements PasswordRule {
+ void enforce(String username, String clearPassword) {
+ }
+
void enforce(User user, String clearPassword) {
}
diff --git
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserSelfRestClient.java
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserSelfRestClient.java
index 14c78f6fd9..559b9212e0 100644
---
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserSelfRestClient.java
+++
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserSelfRestClient.java
@@ -19,6 +19,7 @@
package org.apache.syncope.client.enduser.rest;
import javax.ws.rs.core.GenericType;
+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.ProvisioningResult;
@@ -30,7 +31,7 @@ public class UserSelfRestClient extends BaseRestClient {
private static final long serialVersionUID = -1575748964398293968L;
public static void mustChangePassword(final String password) {
- getService(UserSelfService.class).mustChangePassword(password);
+ getService(UserSelfService.class).mustChangePassword(new
PasswordPatch.Builder().value(password).build());
}
public static void requestPasswordReset(final String username, final
String securityAnswer) {
diff --git
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/PasswordPatch.java
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/PasswordPatch.java
index c681ede524..6202c05ec1 100644
---
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/PasswordPatch.java
+++
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/PasswordPatch.java
@@ -60,7 +60,6 @@ public class PasswordPatch extends StringReplacePatchItem {
}
return this;
}
-
}
/**
diff --git
a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ComplianceQuery.java
b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ComplianceQuery.java
new file mode 100644
index 0000000000..15bf953482
--- /dev/null
+++
b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ComplianceQuery.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.rest.api.beans;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+public class ComplianceQuery implements Serializable {
+
+ private static final long serialVersionUID = -7324275079761880426L;
+
+ public static class Builder {
+
+ private final ComplianceQuery instance = new ComplianceQuery();
+
+ public Builder username(final String username) {
+ instance.setUsername(username);
+ return this;
+ }
+
+ public Builder password(final String password) {
+ instance.setPassword(password);
+ return this;
+ }
+
+ public Builder realm(final String realm) {
+ instance.setRealm(realm);
+ return this;
+ }
+
+ public ComplianceQuery build() {
+ return instance;
+ }
+
+ public Builder resource(final String resource) {
+ if (resource != null) {
+ instance.getResources().add(resource);
+ }
+ return this;
+ }
+
+ public Builder resources(final String... resources) {
+ instance.getResources().addAll(List.of(resources));
+ return this;
+ }
+
+ public Builder resources(final Collection<String> resources) {
+ if (resources != null) {
+ instance.getResources().addAll(resources);
+ }
+ return this;
+ }
+ }
+
+ private String username;
+
+ private String password;
+
+ private String realm;
+
+ private Set<String> resources = new HashSet<>();
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(final String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(final String password) {
+ this.password = password;
+ }
+
+ public String getRealm() {
+ return realm;
+ }
+
+ public void setRealm(final String realm) {
+ this.realm = realm;
+ }
+
+ public Set<String> getResources() {
+ return resources;
+ }
+
+ public void setResources(final Set<String> resources) {
+ this.resources = resources;
+ }
+
+ @JsonIgnore
+ public boolean isEmpty() {
+ if (StringUtils.isBlank(username) && StringUtils.isBlank(password)) {
+ return true;
+ }
+ return StringUtils.isEmpty(realm) && resources.isEmpty();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ComplianceQuery other = (ComplianceQuery) obj;
+ return new EqualsBuilder().
+ append(username, other.username).
+ append(password, other.password).
+ append(realm, other.realm).
+ append(resources, other.resources).
+ build();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder().
+ append(username).
+ append(password).
+ append(realm).
+ append(resources).
+ build();
+ }
+}
diff --git
a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
index 8dc4e5448a..a3312b1c69 100644
---
a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
+++
b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserSelfService.java
@@ -41,12 +41,14 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.request.PasswordPatch;
import org.apache.syncope.common.lib.request.StatusR;
import org.apache.syncope.common.lib.request.UserCR;
import org.apache.syncope.common.lib.request.UserUR;
import org.apache.syncope.common.lib.to.ProvisioningResult;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.beans.ComplianceQuery;
/**
* REST operations for user self-management.
@@ -236,7 +238,24 @@ public interface UserSelfService extends JAXRSService {
@POST
@Path("mustChangePassword")
@Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML,
MediaType.APPLICATION_XML })
- Response mustChangePassword(String password);
+ @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML,
MediaType.APPLICATION_XML })
+ Response mustChangePassword(@NotNull PasswordPatch password);
+
+ /**
+ * Checks compliance of the given username and / or password with
applicable policies.
+ *
+ * @param query compliance query
+ */
+ @ApiResponses(
+ @ApiResponse(responseCode = "204", description = "Operation was
successful"))
+ @Operation(security = {
+ @SecurityRequirement(name = "BasicAuthentication"),
+ @SecurityRequirement(name = "Bearer") })
+ @POST
+ @Path("compliance")
+ @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML,
MediaType.APPLICATION_XML })
+ @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML,
MediaType.APPLICATION_XML })
+ void compliance(@NotNull ComplianceQuery query);
/**
* Provides answer for the security question configured for user matching
the given username, if any.
diff --git
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
index 9dc3d19977..dcc594a2a7 100644
---
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
+++
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
@@ -103,6 +103,7 @@ import
org.apache.syncope.core.provisioning.api.notification.NotificationJobDele
import
org.apache.syncope.core.provisioning.api.notification.NotificationManager;
import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
import
org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.core.provisioning.api.rules.RuleEnforcer;
import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
import org.apache.syncope.core.spring.security.SecurityProperties;
import org.apache.syncope.core.workflow.api.AnyObjectWorkflowAdapter;
@@ -546,11 +547,13 @@ public class IdRepoLogicContext {
final UserDAO userDAO,
final GroupDAO groupDAO,
final AnySearchDAO anySearchDAO,
+ final ExternalResourceDAO resourceDAO,
final AccessTokenDAO accessTokenDAO,
final DelegationDAO delegationDAO,
final ConfParamOps confParamOps,
final UserProvisioningManager provisioningManager,
- final SyncopeLogic syncopeLogic) {
+ final SyncopeLogic syncopeLogic,
+ final RuleEnforcer ruleEnforcer) {
return new UserLogic(
realmDAO,
@@ -559,11 +562,13 @@ public class IdRepoLogicContext {
userDAO,
groupDAO,
anySearchDAO,
+ resourceDAO,
accessTokenDAO,
delegationDAO,
confParamOps,
binder,
provisioningManager,
- syncopeLogic);
+ syncopeLogic,
+ ruleEnforcer);
}
}
diff --git
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
index 023d5e21fc..2d02fc9fbc 100644
---
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
+++
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
@@ -26,6 +26,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
@@ -43,13 +44,17 @@ import org.apache.syncope.common.lib.to.ProvisioningResult;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.EntityViolationType;
import org.apache.syncope.common.lib.types.IdRepoEntitlement;
import org.apache.syncope.common.lib.types.PatchOperation;
+import org.apache.syncope.common.rest.api.beans.ComplianceQuery;
import org.apache.syncope.core.logic.api.LogicActions;
+import
org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
+import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
@@ -57,14 +62,21 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.persistence.api.entity.AccessToken;
+import org.apache.syncope.core.persistence.api.entity.Entity;
+import org.apache.syncope.core.persistence.api.entity.ExternalResource;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.group.Group;
+import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
+import org.apache.syncope.core.provisioning.api.rules.RuleEnforcer;
import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
+import org.apache.syncope.core.spring.policy.AccountPolicyException;
+import org.apache.syncope.core.spring.policy.PasswordPolicyException;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.apache.syncope.core.spring.security.Encryptor;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -82,6 +94,8 @@ public class UserLogic extends AbstractAnyLogic<UserTO,
UserCR, UserUR> {
protected final AnySearchDAO searchDAO;
+ protected final ExternalResourceDAO resourceDAO;
+
protected final AccessTokenDAO accessTokenDAO;
protected final DelegationDAO delegationDAO;
@@ -94,6 +108,8 @@ public class UserLogic extends AbstractAnyLogic<UserTO,
UserCR, UserUR> {
protected final SyncopeLogic syncopeLogic;
+ protected final RuleEnforcer ruleEnforcer;
+
public UserLogic(
final RealmDAO realmDAO,
final AnyTypeDAO anyTypeDAO,
@@ -101,24 +117,28 @@ public class UserLogic extends AbstractAnyLogic<UserTO,
UserCR, UserUR> {
final UserDAO userDAO,
final GroupDAO groupDAO,
final AnySearchDAO searchDAO,
+ final ExternalResourceDAO resourceDAO,
final AccessTokenDAO accessTokenDAO,
final DelegationDAO delegationDAO,
final ConfParamOps confParamOps,
final UserDataBinder binder,
final UserProvisioningManager provisioningManager,
- final SyncopeLogic syncopeLogic) {
+ final SyncopeLogic syncopeLogic,
+ final RuleEnforcer ruleEnforcer) {
super(realmDAO, anyTypeDAO, templateUtils);
this.userDAO = userDAO;
this.groupDAO = groupDAO;
this.searchDAO = searchDAO;
+ this.resourceDAO = resourceDAO;
this.accessTokenDAO = accessTokenDAO;
this.delegationDAO = delegationDAO;
this.confParamOps = confParamOps;
this.binder = binder;
this.provisioningManager = provisioningManager;
this.syncopeLogic = syncopeLogic;
+ this.ruleEnforcer = ruleEnforcer;
}
@PreAuthorize("isAuthenticated() and not(hasRole('" +
IdRepoEntitlement.MUST_CHANGE_PASSWORD + "'))")
@@ -337,15 +357,17 @@ public class UserLogic extends AbstractAnyLogic<UserTO,
UserCR, UserUR> {
}
@PreAuthorize("hasRole('" + IdRepoEntitlement.MUST_CHANGE_PASSWORD + "')")
- public ProvisioningResult<UserTO> mustChangePassword(final String
password, final boolean nullPriorityAsync) {
+ public ProvisioningResult<UserTO> mustChangePassword(
+ final PasswordPatch password, final boolean nullPriorityAsync) {
+
UserTO userTO = binder.getAuthenticatedUserTO();
+ password.setOnSyncope(true);
+ password.getResources().clear();
+
password.getResources().addAll(userDAO.findAllResourceKeys(userTO.getKey()));
+
UserUR userUR = new UserUR.Builder(userTO.getKey()).
- password(new PasswordPatch.Builder().
- value(password).
- onSyncope(true).
-
resources(userDAO.findAllResourceKeys(userTO.getKey())).
- build()).
+ password(password).
mustChangePassword(new
BooleanReplacePatchItem.Builder().value(false).build()).
build();
ProvisioningResult<UserTO> result = selfUpdate(userUR,
nullPriorityAsync);
@@ -357,16 +379,61 @@ public class UserLogic extends AbstractAnyLogic<UserTO,
UserCR, UserUR> {
}
@PreAuthorize("isAnonymous() or hasRole('" + IdRepoEntitlement.ANONYMOUS +
"')")
- @Transactional
- public void requestPasswordReset(final String username, final String
securityAnswer) {
- if (username == null) {
- throw new NotFoundException("Null username");
+ @Transactional(readOnly = true)
+ public void compliance(final ComplianceQuery query) {
+ SyncopeClientException sce =
SyncopeClientException.build(ClientExceptionType.RESTValidation);
+
+ if (query.isEmpty()) {
+ sce.getElements().add("Nothing to check");
+ throw sce;
+ }
+
+ Realm realm = null;
+ if (StringUtils.isNotBlank(query.getRealm())) {
+ realm =
Optional.ofNullable(realmDAO.findByFullPath(query.getRealm())).
+ orElseThrow(() -> new NotFoundException("Realm " +
query.getRealm()));
+ }
+ Set<ExternalResource> resources = query.getResources().stream().
+
map(resourceDAO::find).filter(Objects::nonNull).collect(Collectors.toSet());
+ if (realm == null && resources.isEmpty()) {
+ sce.getElements().add("Nothing to check");
+ throw sce;
+ }
+
+ if (StringUtils.isNotBlank(query.getUsername())) {
+ List<AccountPolicy> accountPolicies =
ruleEnforcer.getAccountPolicies(realm, resources);
+ try {
+ if (accountPolicies.isEmpty()) {
+ if
(!Entity.ID_PATTERN.matcher(query.getUsername()).matches()) {
+ throw new AccountPolicyException("Character(s) not
allowed: " + query.getUsername());
+ }
+ } else {
+ for (AccountPolicy policy : accountPolicies) {
+ ruleEnforcer.getAccountRules(policy).forEach(rule ->
rule.enforce(query.getUsername()));
+ }
+ }
+ } catch (AccountPolicyException e) {
+ throw new InvalidEntityException(User.class,
EntityViolationType.InvalidUsername, e.getMessage());
+ }
}
- User user = userDAO.findByUsername(username);
- if (user == null) {
- throw new NotFoundException("User " + username);
+ if (StringUtils.isNotBlank(query.getPassword())) {
+ try {
+ for (PasswordPolicy policy :
ruleEnforcer.getPasswordPolicies(realm, resources)) {
+ ruleEnforcer.getPasswordRules(policy).
+ forEach(rule -> rule.enforce(query.getUsername(),
query.getPassword()));
+ }
+ } catch (PasswordPolicyException e) {
+ throw new InvalidEntityException(User.class,
EntityViolationType.InvalidPassword, e.getMessage());
+ }
}
+ }
+
+ @PreAuthorize("isAnonymous() or hasRole('" + IdRepoEntitlement.ANONYMOUS +
"')")
+ @Transactional
+ public void requestPasswordReset(final String username, final String
securityAnswer) {
+ User user = Optional.ofNullable(userDAO.findByUsername(username)).
+ orElseThrow(() -> new NotFoundException("User " + username));
if (syncopeLogic.isPwdResetRequiringSecurityQuestions()
&& (securityAnswer == null || !Encryptor.getInstance().
@@ -381,10 +448,9 @@ public class UserLogic extends AbstractAnyLogic<UserTO,
UserCR, UserUR> {
@PreAuthorize("isAnonymous() or hasRole('" + IdRepoEntitlement.ANONYMOUS +
"')")
@Transactional
public void confirmPasswordReset(final String token, final String
password) {
- User user = userDAO.findByToken(token);
- if (user == null) {
- throw new NotFoundException("User with token " + token);
- }
+ User user = Optional.ofNullable(userDAO.findByToken(token)).
+ orElseThrow(() -> new NotFoundException("User with token " +
token));
+
provisioningManager.confirmPasswordReset(
user.getKey(), token, password,
AuthContextUtils.getUsername(), REST_CONTEXT);
}
diff --git
a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserSelfServiceImpl.java
b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserSelfServiceImpl.java
index 61b0f9bb16..76a281f2d4 100644
---
a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserSelfServiceImpl.java
+++
b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserSelfServiceImpl.java
@@ -22,6 +22,7 @@ import javax.ws.rs.core.Response;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.syncope.common.lib.AnyOperations;
import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.request.PasswordPatch;
import org.apache.syncope.common.lib.request.StatusR;
import org.apache.syncope.common.lib.request.UserCR;
import org.apache.syncope.common.lib.request.UserUR;
@@ -29,6 +30,7 @@ import org.apache.syncope.common.lib.to.ProvisioningResult;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.beans.ComplianceQuery;
import org.apache.syncope.common.rest.api.service.UserSelfService;
import org.apache.syncope.core.logic.SyncopeLogic;
import org.apache.syncope.core.logic.UserLogic;
@@ -94,11 +96,16 @@ public class UserSelfServiceImpl extends AbstractService
implements UserSelfServ
}
@Override
- public Response mustChangePassword(final String password) {
+ public Response mustChangePassword(final PasswordPatch password) {
ProvisioningResult<UserTO> updated =
logic.mustChangePassword(password, isNullPriorityAsync());
return modificationResponse(updated);
}
+ @Override
+ public void compliance(final ComplianceQuery query) {
+ logic.compliance(query);
+ }
+
@Override
public void requestPasswordReset(final String username, final String
securityAnswer) {
if (!syncopeLogic.isPwdResetAllowed()) {
diff --git
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/rules/AccountRule.java
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/rules/AccountRule.java
index 4dfb32562e..24721d1b0b 100644
---
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/rules/AccountRule.java
+++
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/rules/AccountRule.java
@@ -30,6 +30,8 @@ public interface AccountRule {
default void setConf(AccountRuleConf conf) {
}
+ void enforce(String username);
+
void enforce(User user);
void enforce(LinkedAccount accout);
diff --git
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/rules/PasswordRule.java
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/rules/PasswordRule.java
index 19ef16e79c..763243e462 100644
---
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/rules/PasswordRule.java
+++
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/rules/PasswordRule.java
@@ -34,6 +34,8 @@ public interface PasswordRule {
default void setConf(PasswordRuleConf conf) {
}
+ void enforce(String username, String clearPassword);
+
void enforce(User user, String clearPassword);
void enforce(LinkedAccount account);
diff --git
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/rules/PasswordRule.java
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/rules/RuleEnforcer.java
similarity index 55%
copy from
core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/rules/PasswordRule.java
copy to
core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/rules/RuleEnforcer.java
index 19ef16e79c..e9a98ec444 100644
---
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/rules/PasswordRule.java
+++
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/rules/RuleEnforcer.java
@@ -18,23 +18,20 @@
*/
package org.apache.syncope.core.provisioning.api.rules;
-import org.apache.syncope.common.lib.policy.PasswordRuleConf;
-import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
-import org.apache.syncope.core.persistence.api.entity.user.User;
+import java.util.Collection;
+import java.util.List;
+import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
-/**
- * Interface for enforcing a given password rule to user.
- */
-public interface PasswordRule {
+public interface RuleEnforcer {
- default PasswordRuleConf getConf() {
- return null;
- }
+ List<AccountPolicy> getAccountPolicies(Realm realm,
Collection<ExternalResource> resources);
- default void setConf(PasswordRuleConf conf) {
- }
+ List<AccountRule> getAccountRules(AccountPolicy policy);
- void enforce(User user, String clearPassword);
+ List<PasswordPolicy> getPasswordPolicies(Realm realm,
Collection<ExternalResource> resources);
- void enforce(LinkedAccount account);
+ List<PasswordRule> getPasswordRules(PasswordPolicy policy);
}
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultAccountRule.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultAccountRule.java
index dc0bf2c995..0cb4b1e7b9 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultAccountRule.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultAccountRule.java
@@ -98,6 +98,12 @@ public class DefaultAccountRule implements AccountRule {
});
}
+ @Override
+ public void enforce(final String username) {
+ Set<String> wordsNotPermitted = new
HashSet<>(conf.getWordsNotPermitted());
+ enforce(username, wordsNotPermitted);
+ }
+
@Transactional(readOnly = true)
@Override
public void enforce(final User user) {
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultPasswordRule.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultPasswordRule.java
index ae63f4df25..e9a63c2af7 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultPasswordRule.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultPasswordRule.java
@@ -163,6 +163,14 @@ public class DefaultPasswordRule implements PasswordRule {
});
}
+ @Override
+ public void enforce(final String username, final String clearPassword) {
+ if (clearPassword != null) {
+ Set<String> wordsNotPermitted = new
HashSet<>(conf.getWordsNotPermitted());
+ enforce(clearPassword, username, wordsNotPermitted);
+ }
+ }
+
@Transactional(readOnly = true)
@Override
public void enforce(final User user, final String clearPassword) {
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultRuleEnforcer.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultRuleEnforcer.java
new file mode 100644
index 0000000000..4a8e925de5
--- /dev/null
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultRuleEnforcer.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.spring.policy;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
+import org.apache.syncope.core.provisioning.api.rules.AccountRule;
+import org.apache.syncope.core.provisioning.api.rules.PasswordRule;
+import org.apache.syncope.core.provisioning.api.rules.RuleEnforcer;
+import org.apache.syncope.core.spring.implementation.ImplementationManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.transaction.annotation.Transactional;
+
+public class DefaultRuleEnforcer implements RuleEnforcer {
+
+ protected static final Logger LOG =
LoggerFactory.getLogger(RuleEnforcer.class);
+
+ protected final RealmDAO realmDAO;
+
+ protected final Map<String, AccountRule> perContextAccountRules = new
ConcurrentHashMap<>();
+
+ protected final Map<String, PasswordRule> perContextPasswordRules = new
ConcurrentHashMap<>();
+
+ public DefaultRuleEnforcer(final RealmDAO realmDAO) {
+ this.realmDAO = realmDAO;
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public List<AccountPolicy> getAccountPolicies(final Realm realm, final
Collection<ExternalResource> resources) {
+ List<AccountPolicy> policies = new ArrayList<>();
+
+ // add resource policies
+ resources.forEach(resource ->
Optional.ofNullable(resource.getAccountPolicy()).
+ filter(p -> !policies.contains(p)).
+ ifPresent(policies::add));
+
+ // add realm policies
+ if (realm != null) {
+ realmDAO.findAncestors(realm).
+ forEach(r -> Optional.ofNullable(r.getAccountPolicy()).
+ filter(p -> !policies.contains(p)).
+ ifPresent(policies::add));
+ }
+
+ return policies;
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public List<AccountRule> getAccountRules(final AccountPolicy policy) {
+ List<AccountRule> result = new ArrayList<>();
+
+ for (Implementation impl : policy.getRules()) {
+ try {
+ ImplementationManager.buildAccountRule(
+ impl,
+ () -> perContextAccountRules.get(impl.getKey()),
+ instance -> perContextAccountRules.put(impl.getKey(),
instance)).
+ ifPresent(result::add);
+ } catch (Exception e) {
+ LOG.warn("While building {}", impl, e);
+ }
+ }
+
+ return result;
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public List<PasswordPolicy> getPasswordPolicies(final Realm realm, final
Collection<ExternalResource> resources) {
+ List<PasswordPolicy> policies = new ArrayList<>();
+
+ // add resource policies
+ resources.forEach(resource ->
Optional.ofNullable(resource.getPasswordPolicy()).
+ filter(p -> !policies.contains(p)).
+ ifPresent(policies::add));
+
+ // add realm policies
+ if (realm != null) {
+ realmDAO.findAncestors(realm).
+ forEach(r -> Optional.ofNullable(r.getPasswordPolicy()).
+ filter(p -> !policies.contains(p)).
+ ifPresent(policies::add));
+ }
+
+ return policies;
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public List<PasswordRule> getPasswordRules(final PasswordPolicy policy) {
+ List<PasswordRule> result = new ArrayList<>();
+
+ for (Implementation impl : policy.getRules()) {
+ try {
+ ImplementationManager.buildPasswordRule(
+ impl,
+ () -> perContextPasswordRules.get(impl.getKey()),
+ instance -> perContextPasswordRules.put(impl.getKey(),
instance)).
+ ifPresent(result::add);
+ } catch (Exception e) {
+ LOG.warn("While building {}", impl, e);
+ }
+ }
+
+ return result;
+ }
+}
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/policy/HaveIBeenPwnedPasswordRule.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/policy/HaveIBeenPwnedPasswordRule.java
index e66eb215e2..11b71dec4b 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/policy/HaveIBeenPwnedPasswordRule.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/policy/HaveIBeenPwnedPasswordRule.java
@@ -97,6 +97,11 @@ public class HaveIBeenPwnedPasswordRule implements
PasswordRule {
}
}
+ @Override
+ public void enforce(final String username, final String clearPassword) {
+ Optional.ofNullable(clearPassword).ifPresent(this::enforce);
+ }
+
@Transactional(readOnly = true)
@Override
public void enforce(final User user, final String clearPassword) {
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecurityContext.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecurityContext.java
index 486ab1612d..7a5624837a 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecurityContext.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecurityContext.java
@@ -24,7 +24,10 @@ import com.nimbusds.jose.KeyLengthException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+import org.apache.syncope.core.provisioning.api.rules.RuleEnforcer;
import org.apache.syncope.core.spring.ApplicationContextProvider;
+import org.apache.syncope.core.spring.policy.DefaultRuleEnforcer;
import org.apache.syncope.core.spring.security.jws.AccessTokenJWSSigner;
import org.apache.syncope.core.spring.security.jws.AccessTokenJWSVerifier;
import org.slf4j.Logger;
@@ -112,6 +115,12 @@ public class SecurityContext {
return new DefaultPasswordGenerator();
}
+ @ConditionalOnMissingBean
+ @Bean
+ public RuleEnforcer ruleEnforcer(final RealmDAO realmDAO) {
+ return new DefaultRuleEnforcer(realmDAO);
+ }
+
@Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults(""); // Remove the ROLE_ prefix
diff --git
a/core/spring/src/test/java/org/apache/syncope/core/spring/security/TestPasswordRule.java
b/core/spring/src/test/java/org/apache/syncope/core/spring/security/TestPasswordRule.java
index 61448f0e0e..bbc33d1f4f 100644
---
a/core/spring/src/test/java/org/apache/syncope/core/spring/security/TestPasswordRule.java
+++
b/core/spring/src/test/java/org/apache/syncope/core/spring/security/TestPasswordRule.java
@@ -45,6 +45,11 @@ public class TestPasswordRule implements PasswordRule {
}
}
+ @Override
+ public void enforce(final String username, final String clearPassword) {
+ // nothing to do
+ }
+
@Override
public void enforce(final User user, final String clearPassword) {
// nothing to do
diff --git
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
index 129adb5ec2..f6e9e52f2c 100644
---
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
+++
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
@@ -18,14 +18,10 @@
*/
package org.apache.syncope.core.workflow.java;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
-import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.lib.request.PasswordPatch;
import org.apache.syncope.common.lib.request.UserCR;
@@ -38,18 +34,13 @@ import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.Entity;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
-import org.apache.syncope.core.persistence.api.entity.ExternalResource;
-import org.apache.syncope.core.persistence.api.entity.Implementation;
-import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.provisioning.api.PropagationByResource;
import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
-import org.apache.syncope.core.provisioning.api.rules.AccountRule;
-import org.apache.syncope.core.provisioning.api.rules.PasswordRule;
-import org.apache.syncope.core.spring.implementation.ImplementationManager;
+import org.apache.syncope.core.provisioning.api.rules.RuleEnforcer;
import org.apache.syncope.core.spring.policy.AccountPolicyException;
import org.apache.syncope.core.spring.policy.PasswordPolicyException;
import org.apache.syncope.core.spring.security.AuthContextUtils;
@@ -76,22 +67,22 @@ public abstract class AbstractUserWorkflowAdapter extends
AbstractWorkflowAdapte
protected final SecurityProperties securityProperties;
- protected final Map<String, AccountRule> perContextAccountRules = new
ConcurrentHashMap<>();
-
- protected final Map<String, PasswordRule> perContextPasswordRules = new
ConcurrentHashMap<>();
+ protected final RuleEnforcer ruleEnforcer;
public AbstractUserWorkflowAdapter(
final UserDataBinder dataBinder,
final UserDAO userDAO,
final RealmDAO realmDAO,
final EntityFactory entityFactory,
- final SecurityProperties securityProperties) {
+ final SecurityProperties securityProperties,
+ final RuleEnforcer ruleEnforcer) {
this.dataBinder = dataBinder;
this.userDAO = userDAO;
this.realmDAO = realmDAO;
this.entityFactory = entityFactory;
this.securityProperties = securityProperties;
+ this.ruleEnforcer = ruleEnforcer;
}
@Override
@@ -99,78 +90,6 @@ public abstract class AbstractUserWorkflowAdapter extends
AbstractWorkflowAdapte
return null;
}
- protected List<AccountPolicy> getAccountPolicies(final User user) {
- List<AccountPolicy> policies = new ArrayList<>();
-
- // add resource policies
- userDAO.findAllResources(user).stream().
- map(ExternalResource::getAccountPolicy).
- filter(Objects::nonNull).
- forEach(policies::add);
-
- // add realm policies
- realmDAO.findAncestors(user.getRealm()).stream().
- map(Realm::getAccountPolicy).
- filter(Objects::nonNull).
- forEach(policies::add);
-
- return policies;
- }
-
- protected List<AccountRule> getAccountRules(final AccountPolicy policy) {
- List<AccountRule> result = new ArrayList<>();
-
- for (Implementation impl : policy.getRules()) {
- try {
- ImplementationManager.buildAccountRule(
- impl,
- () -> perContextAccountRules.get(impl.getKey()),
- instance -> perContextAccountRules.put(impl.getKey(),
instance)).
- ifPresent(result::add);
- } catch (Exception e) {
- LOG.warn("While building {}", impl, e);
- }
- }
-
- return result;
- }
-
- protected List<PasswordPolicy> getPasswordPolicies(final User user) {
- List<PasswordPolicy> policies = new ArrayList<>();
-
- // add resource policies
- userDAO.findAllResources(user).
- forEach(resource ->
Optional.ofNullable(resource.getPasswordPolicy()).
- filter(p -> !policies.contains(p)).
- ifPresent(policies::add));
-
- // add realm policies
- realmDAO.findAncestors(user.getRealm()).
- forEach(realm ->
Optional.ofNullable(realm.getPasswordPolicy()).
- filter(p -> !policies.contains(p)).
- ifPresent(policies::add));
-
- return policies;
- }
-
- protected List<PasswordRule> getPasswordRules(final PasswordPolicy policy)
{
- List<PasswordRule> result = new ArrayList<>();
-
- for (Implementation impl : policy.getRules()) {
- try {
- ImplementationManager.buildPasswordRule(
- impl,
- () -> perContextPasswordRules.get(impl.getKey()),
- instance -> perContextPasswordRules.put(impl.getKey(),
instance)).
- ifPresent(result::add);
- } catch (Exception e) {
- LOG.warn("While building {}", impl, e);
- }
- }
-
- return result;
- }
-
protected Pair<Boolean, Boolean> enforcePolicies(
final User user,
final boolean disablePwdPolicyCheck,
@@ -184,12 +103,14 @@ public abstract class AbstractUserWorkflowAdapter extends
AbstractWorkflowAdapte
try {
int maxPPSpecHistory = 0;
- for (PasswordPolicy policy : getPasswordPolicies(user)) {
+ for (PasswordPolicy policy : ruleEnforcer.getPasswordPolicies(
+ user.getRealm(), userDAO.findAllResources(user))) {
+
if (clearPassword == null &&
!policy.isAllowNullPassword()) {
throw new PasswordPolicyException("Password
mandatory");
}
- getPasswordRules(policy).forEach(rule -> {
+ ruleEnforcer.getPasswordRules(policy).forEach(rule -> {
rule.enforce(user, clearPassword);
user.getLinkedAccounts().stream().
@@ -253,7 +174,8 @@ public abstract class AbstractUserWorkflowAdapter extends
AbstractWorkflowAdapte
throw new AccountPolicyException("Not allowed: " +
user.getUsername());
}
- List<AccountPolicy> accountPolicies = getAccountPolicies(user);
+ List<AccountPolicy> accountPolicies =
+ ruleEnforcer.getAccountPolicies(user.getRealm(),
userDAO.findAllResources(user));
if (accountPolicies.isEmpty()) {
if (!Entity.ID_PATTERN.matcher(user.getUsername()).matches()) {
throw new AccountPolicyException("Character(s) not
allowed: " + user.getUsername());
@@ -267,7 +189,7 @@ public abstract class AbstractUserWorkflowAdapter extends
AbstractWorkflowAdapte
});
} else {
for (AccountPolicy policy : accountPolicies) {
- getAccountRules(policy).forEach(rule -> {
+ ruleEnforcer.getAccountRules(policy).forEach(rule -> {
rule.enforce(user);
user.getLinkedAccounts().stream().
diff --git
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
index a616640263..00029ed236 100644
---
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
+++
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
@@ -32,6 +32,7 @@ import
org.apache.syncope.core.provisioning.api.PropagationByResource;
import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.provisioning.api.rules.RuleEnforcer;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.apache.syncope.core.spring.security.SecurityProperties;
import org.apache.syncope.core.workflow.api.WorkflowException;
@@ -53,10 +54,11 @@ public class DefaultUserWorkflowAdapter extends
AbstractUserWorkflowAdapter {
final RealmDAO realmDAO,
final EntityFactory entityFactory,
final SecurityProperties securityProperties,
+ final RuleEnforcer ruleEnforcer,
final ConfParamOps confParamOps,
final ApplicationEventPublisher publisher) {
- super(dataBinder, userDAO, realmDAO, entityFactory,
securityProperties);
+ super(dataBinder, userDAO, realmDAO, entityFactory,
securityProperties, ruleEnforcer);
this.confParamOps = confParamOps;
this.publisher = publisher;
}
diff --git
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/WorkflowContext.java
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/WorkflowContext.java
index bd4de35dc7..7dd7a4ee8a 100644
---
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/WorkflowContext.java
+++
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/WorkflowContext.java
@@ -27,6 +27,7 @@ import
org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.provisioning.api.data.AnyObjectDataBinder;
import org.apache.syncope.core.provisioning.api.data.GroupDataBinder;
import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
+import org.apache.syncope.core.provisioning.api.rules.RuleEnforcer;
import org.apache.syncope.core.spring.security.SecurityProperties;
import org.apache.syncope.core.workflow.api.AnyObjectWorkflowAdapter;
import org.apache.syncope.core.workflow.api.GroupWorkflowAdapter;
@@ -47,6 +48,7 @@ public class WorkflowContext {
final RealmDAO realmDAO,
final EntityFactory entityFactory,
final SecurityProperties securityProperties,
+ final RuleEnforcer ruleEnforcer,
final ConfParamOps confParamOps,
final ApplicationEventPublisher publisher) {
@@ -56,6 +58,7 @@ public class WorkflowContext {
realmDAO,
entityFactory,
securityProperties,
+ ruleEnforcer,
confParamOps,
publisher);
}
diff --git
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
index 4882119fe5..9d0335fb53 100644
---
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
+++
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
@@ -45,6 +45,7 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
import
org.apache.syncope.core.provisioning.api.notification.NotificationManager;
+import org.apache.syncope.core.provisioning.api.rules.RuleEnforcer;
import org.apache.syncope.core.spring.security.SecurityProperties;
import org.apache.syncope.core.workflow.api.UserWorkflowAdapter;
import org.flowable.common.engine.impl.AbstractEngineConfiguration;
@@ -173,6 +174,7 @@ public class FlowableWorkflowContext {
final RealmDAO realmDAO,
final EntityFactory entityFactory,
final SecurityProperties securityProperties,
+ final RuleEnforcer ruleEnforcer,
final DomainProcessEngine engine,
final UserRequestHandler userRequestHandler,
final ApplicationEventPublisher publisher) {
@@ -183,6 +185,7 @@ public class FlowableWorkflowContext {
realmDAO,
entityFactory,
securityProperties,
+ ruleEnforcer,
engine,
userRequestHandler,
publisher);
diff --git
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
index 81777294ec..b97780db1e 100644
---
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
+++
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
@@ -43,6 +43,7 @@ import
org.apache.syncope.core.provisioning.api.PropagationByResource;
import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.provisioning.api.rules.RuleEnforcer;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.apache.syncope.core.spring.security.SecurityProperties;
import org.apache.syncope.core.workflow.api.WorkflowException;
@@ -72,11 +73,12 @@ public class FlowableUserWorkflowAdapter extends
AbstractUserWorkflowAdapter imp
final RealmDAO realmDAO,
final EntityFactory entityFactory,
final SecurityProperties securityProperties,
+ final RuleEnforcer ruleEnforcer,
final DomainProcessEngine engine,
final UserRequestHandler userRequestHandler,
final ApplicationEventPublisher publisher) {
- super(dataBinder, userDAO, realmDAO, entityFactory,
securityProperties);
+ super(dataBinder, userDAO, realmDAO, entityFactory,
securityProperties, ruleEnforcer);
this.engine = engine;
this.userRequestHandler = userRequestHandler;
this.publisher = publisher;
diff --git
a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestAccountRule.java
b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestAccountRule.java
index 30188323c2..4430b1fc03 100644
---
a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestAccountRule.java
+++
b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestAccountRule.java
@@ -41,14 +41,19 @@ public class TestAccountRule implements AccountRule {
}
}
- @Transactional(readOnly = true)
@Override
- public void enforce(final User user) {
- if (!user.getUsername().contains(conf.getMustContainSubstring())) {
+ public void enforce(final String username) {
+ if (!username.contains(conf.getMustContainSubstring())) {
throw new AccountPolicyException("Username not containing " +
conf.getMustContainSubstring());
}
}
+ @Transactional(readOnly = true)
+ @Override
+ public void enforce(final User user) {
+ enforce(user.getUsername());
+ }
+
@Transactional(readOnly = true)
@Override
public void enforce(final LinkedAccount accout) {
diff --git
a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestPasswordRule.java
b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestPasswordRule.java
index 866a28df8e..311c788ea9 100644
---
a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestPasswordRule.java
+++
b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestPasswordRule.java
@@ -53,6 +53,13 @@ public class TestPasswordRule implements PasswordRule {
}
}
+ @Override
+ public void enforce(final String username, final String clearPassword) {
+ if (clearPassword != null &&
!clearPassword.endsWith(conf.getMustEndWith())) {
+ throw new PasswordPolicyException("Password not ending with " +
conf.getMustEndWith());
+ }
+ }
+
@Transactional(readOnly = true)
@Override
public void enforce(final User user, final String clearPassword) {
diff --git
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
index 80593f0f95..14b94ce4e6 100644
---
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
+++
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
@@ -18,6 +18,7 @@
*/
package org.apache.syncope.fit.core;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -85,6 +86,7 @@ import org.apache.syncope.common.lib.types.TaskType;
import org.apache.syncope.common.rest.api.RESTHeaders;
import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
import org.apache.syncope.common.rest.api.beans.AnyQuery;
+import org.apache.syncope.common.rest.api.beans.ComplianceQuery;
import org.apache.syncope.common.rest.api.beans.RealmQuery;
import org.apache.syncope.common.rest.api.beans.TaskQuery;
import org.apache.syncope.common.rest.api.service.ResourceService;
@@ -973,6 +975,15 @@ public class UserITCase extends AbstractITCase {
try {
UserCR userCR =
getUniqueSample("[email protected]");
userCR.setRealm(realm.getFullPath());
+
+ try {
+ ANONYMOUS_CLIENT.getService(UserSelfService.class).compliance(
+ new
ComplianceQuery.Builder().password(userCR.getPassword()).realm(userCR.getRealm()).build());
+ } catch (SyncopeClientException e) {
+ assertEquals(ClientExceptionType.InvalidUser, e.getType());
+
assertTrue(e.getElements().iterator().next().startsWith("InvalidPassword"));
+ }
+
try {
createUser(userCR);
fail("This should not happen");
@@ -981,7 +992,16 @@ public class UserITCase extends AbstractITCase {
assertTrue(e.getElements().iterator().next().startsWith("InvalidPassword"));
}
+ try {
+ ANONYMOUS_CLIENT.getService(UserSelfService.class).compliance(
+ new
ComplianceQuery.Builder().username(userCR.getUsername()).realm(userCR.getRealm()).build());
+ } catch (SyncopeClientException e) {
+ assertEquals(ClientExceptionType.InvalidUser, e.getType());
+
assertTrue(e.getElements().iterator().next().startsWith("InvalidUsername"));
+ }
+
userCR.setPassword(userCR.getPassword() + "XXX");
+
try {
createUser(userCR);
fail("This should not happen");
@@ -991,6 +1011,12 @@ public class UserITCase extends AbstractITCase {
}
userCR.setUsername("YYY" + userCR.getUsername());
+
+ assertDoesNotThrow(() ->
ANONYMOUS_CLIENT.getService(UserSelfService.class).compliance(
+ new
ComplianceQuery.Builder().password(userCR.getPassword()).realm(userCR.getRealm()).build()));
+ assertDoesNotThrow(() ->
ANONYMOUS_CLIENT.getService(UserSelfService.class).compliance(
+ new
ComplianceQuery.Builder().username(userCR.getUsername()).realm(userCR.getRealm()).build()));
+
UserTO userTO = createUser(userCR).getEntity();
assertNotNull(userTO);
} finally {
diff --git
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
index c4596e7dad..031df76845 100644
---
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
+++
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
@@ -444,7 +444,8 @@ public class UserSelfITCase extends AbstractITCase {
}
// 3. change password
-
vivaldiClient.getService(UserSelfService.class).mustChangePassword("password123");
+ vivaldiClient.getService(UserSelfService.class).
+ mustChangePassword(new
PasswordPatch.Builder().value("password123").build());
// 4. verify it worked
Triple<Map<String, Set<String>>, List<String>, UserTO> self =