Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,22 @@
private boolean usernameAllowed;

/**
* Substrings not permitted.
* Words not permitted.
*/
private final List<String> wordsNotPermitted = new ArrayList<>();

/**
* User attribute values not permitted.
*/
@Schema(anyTypeKind = AnyTypeKind.USER,
type = { SchemaType.PLAIN, SchemaType.DERIVED })
@Schema(anyTypeKind = AnyTypeKind.USER, type = { SchemaType.PLAIN, SchemaType.DERIVED })
private final List<String> schemasNotPermitted = new ArrayList<>();

private boolean notPermittedCaseSensitive;

private boolean notPermittedAsSubstrings;

private boolean notPermittedBackwards;

public int getMaxLength() {
return maxLength;
}
Expand Down Expand Up @@ -149,7 +154,31 @@
return wordsNotPermitted;
}

public List<String> getSchemasNotPermitted() {

Check notice

Code scanning / CodeQL

Exposing internal representation Note

getSchemasNotPermitted exposes the internal representation stored in field schemasNotPermitted. The value may be modified
after this call to getSchemasNotPermitted
.
getSchemasNotPermitted exposes the internal representation stored in field schemasNotPermitted. The value may be modified
after this call to getSchemasNotPermitted
.
getSchemasNotPermitted exposes the internal representation stored in field schemasNotPermitted. The value may be modified
after this call to getSchemasNotPermitted
.
return schemasNotPermitted;
}

public boolean isNotPermittedCaseSensitive() {
return notPermittedCaseSensitive;
}

public void setNotPermittedCaseSensitive(final boolean notPermittedCaseSensitive) {
this.notPermittedCaseSensitive = notPermittedCaseSensitive;
}

public boolean isNotPermittedAsSubstrings() {
return notPermittedAsSubstrings;
}

public void setNotPermittedAsSubstrings(final boolean notPermittedAsSubstrings) {
this.notPermittedAsSubstrings = notPermittedAsSubstrings;
}

public boolean isNotPermittedBackwards() {
return notPermittedBackwards;
}

public void setNotPermittedBackwards(final boolean notPermittedBackwards) {
this.notPermittedBackwards = notPermittedBackwards;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -944,14 +944,16 @@ public Cache<EntityCacheKey, Neo4jImplementation> implementationCache(final Cach
@Bean
public ImplementationRepoExt implementationRepoExt(
final ExternalResourceDAO resourceDAO,
final PolicyDAO policyDAO,
final RealmDAO realmDAO,
final EntityCacheDAO entityCacheDAO,
final Neo4jTemplate neo4jTemplate,
final Neo4jClient neo4jClient,
final NodeValidator nodeValidator,
final Cache<EntityCacheKey, Neo4jImplementation> implementationCache) {

return new ImplementationRepoExtImpl(
resourceDAO, entityCacheDAO, neo4jTemplate, neo4jClient, nodeValidator, implementationCache);
return new ImplementationRepoExtImpl(resourceDAO, policyDAO, realmDAO, entityCacheDAO, neo4jTemplate,
neo4jClient, nodeValidator, implementationCache);
}

@ConditionalOnMissingBean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,18 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.core.persistence.api.dao.EntityCacheDAO;
import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.api.entity.Implementation;
import org.apache.syncope.core.persistence.neo4j.dao.AbstractDAO;
import org.apache.syncope.core.persistence.neo4j.entity.EntityCacheKey;
import org.apache.syncope.core.persistence.neo4j.entity.Neo4jExternalResource;
import org.apache.syncope.core.persistence.neo4j.entity.Neo4jImplementation;
import org.apache.syncope.core.persistence.neo4j.entity.Neo4jRealm;
import org.apache.syncope.core.persistence.neo4j.entity.policy.Neo4jAccountPolicy;
import org.apache.syncope.core.persistence.neo4j.entity.policy.Neo4jInboundPolicy;
import org.apache.syncope.core.persistence.neo4j.entity.policy.Neo4jPasswordPolicy;
import org.apache.syncope.core.persistence.neo4j.entity.policy.Neo4jPushPolicy;
import org.apache.syncope.core.persistence.neo4j.spring.NodeValidator;
import org.apache.syncope.core.spring.implementation.ImplementationManager;
import org.springframework.data.neo4j.core.Neo4jClient;
Expand All @@ -41,6 +48,10 @@ public class ImplementationRepoExtImpl extends AbstractDAO implements Implementa

protected final ExternalResourceDAO resourceDAO;

protected final PolicyDAO policyDAO;

protected final RealmDAO realmDAO;

protected final EntityCacheDAO entityCacheDAO;

protected final NodeValidator nodeValidator;
Expand All @@ -49,6 +60,8 @@ public class ImplementationRepoExtImpl extends AbstractDAO implements Implementa

public ImplementationRepoExtImpl(
final ExternalResourceDAO resourceDAO,
final PolicyDAO policyDAO,
final RealmDAO realmDAO,
final EntityCacheDAO entityCacheDAO,
final Neo4jTemplate neo4jTemplate,
final Neo4jClient neo4jClient,
Expand All @@ -57,6 +70,8 @@ public ImplementationRepoExtImpl(

super(neo4jTemplate, neo4jClient);
this.resourceDAO = resourceDAO;
this.policyDAO = policyDAO;
this.realmDAO = realmDAO;
this.entityCacheDAO = entityCacheDAO;
this.nodeValidator = nodeValidator;
this.cache = cache;
Expand Down Expand Up @@ -107,6 +122,25 @@ public Implementation save(final Implementation implementation) {
resourceDAO.findByProvisionSorter(saved).
forEach(resource -> entityCacheDAO.evict(Neo4jExternalResource.class, resource.getKey()));

policyDAO.findByAccountRule(saved).forEach(policy -> {
entityCacheDAO.evict(Neo4jAccountPolicy.class, policy.getKey());
realmDAO.findByPolicy(policy).forEach(realm -> entityCacheDAO.evict(Neo4jRealm.class, realm.getKey()));
});
policyDAO.findByInboundCorrelationRule(saved).forEach(policy -> {
entityCacheDAO.evict(Neo4jInboundPolicy.class, policy.getKey());
resourceDAO.findByPolicy(policy)
.forEach(resource -> entityCacheDAO.evict(Neo4jExternalResource.class, resource.getKey()));
});
policyDAO.findByPasswordRule(saved).forEach(policy -> {
entityCacheDAO.evict(Neo4jPasswordPolicy.class, policy.getKey());
realmDAO.findByPolicy(policy).forEach(realm -> entityCacheDAO.evict(Neo4jRealm.class, realm.getKey()));
});
policyDAO.findByPushCorrelationRule(saved).forEach(policy -> {
entityCacheDAO.evict(Neo4jPushPolicy.class, policy.getKey());
resourceDAO.findByPolicy(policy)
.forEach(resource -> entityCacheDAO.evict(Neo4jExternalResource.class, resource.getKey()));
});

return saved;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.passay.dictionary.WordListDictionary;
import org.passay.resolver.PropertiesMessageResolver;
import org.passay.rule.DictionaryRule;
import org.passay.rule.DictionarySubstringRule;
import org.passay.rule.Rule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -89,9 +90,15 @@ public void setConf(final PasswordRuleConf conf) {
protected void enforce(final String username, final String clearPassword, final Collection<String> notPermitted) {
List<Rule> rules = PasswordGenerator.conf2Rules(conf);
if (!notPermitted.isEmpty()) {
rules.add(new DictionaryRule(new WordListDictionary(new ArrayWordList(
notPermitted.stream().distinct().sorted(Comparator.naturalOrder()).toArray(String[]::new), true)),
true));
WordListDictionary wld = new WordListDictionary(new ArrayWordList(notPermitted.stream()
.distinct()
.sorted(conf.isNotPermittedCaseSensitive()
? Comparator.naturalOrder()
: String.CASE_INSENSITIVE_ORDER)
.toArray(String[]::new), conf.isNotPermittedCaseSensitive()));
rules.add(conf.isNotPermittedAsSubstrings()
? new DictionarySubstringRule(wld, conf.isNotPermittedBackwards())
: new DictionaryRule(wld, conf.isNotPermittedBackwards()));
}

PasswordValidator passwordValidator = new DefaultPasswordValidator(messageResolver, rules);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.passay.dictionary.WordListDictionary;
import org.passay.rule.CharacterRule;
import org.passay.rule.DictionaryRule;
import org.passay.rule.DictionarySubstringRule;
import org.passay.rule.IllegalCharacterRule;
import org.passay.rule.LengthRule;
import org.passay.rule.RepeatCharactersRule;
Expand Down Expand Up @@ -95,9 +96,15 @@ public String getCharacters() {
}

if (!conf.getWordsNotPermitted().isEmpty()) {
conf.getWordsNotPermitted().sort(Comparator.naturalOrder());
rules.add(new DictionaryRule(new WordListDictionary(
new ArrayWordList(conf.getWordsNotPermitted().toArray(String[]::new), true)), true));
WordListDictionary wld = new WordListDictionary(
Comment thread
andrea-patricelli marked this conversation as resolved.
new ArrayWordList(conf.getWordsNotPermitted().stream().
distinct().sorted(conf.isNotPermittedCaseSensitive()
? Comparator.naturalOrder()
: String.CASE_INSENSITIVE_ORDER).toArray(String[]::new),
conf.isNotPermittedCaseSensitive()));
rules.add(conf.isNotPermittedAsSubstrings()
? new DictionarySubstringRule(wld, conf.isNotPermittedBackwards())
: new DictionaryRule(wld, conf.isNotPermittedBackwards()));
}

return rules;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

Expand All @@ -47,6 +48,7 @@
import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
import org.apache.syncope.common.lib.policy.PushPolicyTO;
import org.apache.syncope.common.lib.policy.TicketExpirationPolicyTO;
import org.apache.syncope.common.lib.request.UserCR;
import org.apache.syncope.common.lib.to.ImplementationTO;
import org.apache.syncope.common.lib.to.ResourceTO;
import org.apache.syncope.common.lib.types.AnyTypeKind;
Expand Down Expand Up @@ -482,4 +484,38 @@ public void issueSYNCOPE682() {
RESOURCE_SERVICE.update(ldap);
}
}

@Test
public void issueSYNCOPE1979() {
// 1. Set a new password policy with not permitted schemas and not permitted words
ImplementationTO originalRule =
IMPLEMENTATION_SERVICE.read(IdRepoImplementationType.PASSWORD_RULE, "DefaultPasswordRuleConf2");
DefaultPasswordRuleConf defaultPasswordRuleConf =
POJOHelper.deserialize(originalRule.getBody(), DefaultPasswordRuleConf.class);
defaultPasswordRuleConf.getSchemasNotPermitted().add("firstname");
defaultPasswordRuleConf.getSchemasNotPermitted().add("surname");
defaultPasswordRuleConf.getSchemasNotPermitted().add("changePwdDate");
defaultPasswordRuleConf.setNotPermittedAsSubstrings(true);
originalRule.setBody(POJOHelper.serialize(defaultPasswordRuleConf));
IMPLEMENTATION_SERVICE.update(originalRule);
try {
UserCR userCR = UserITCase.getUniqueSample("syncope1979@syncope.apache.org");
// 1. set password with not permitted word inside
SyncopeClientException sce = assertThrows(SyncopeClientException.class, () -> {
userCR.setPassword("Notpermitted12345!");
createUser(userCR);
});
assertTrue(sce.getElements().iterator().next().startsWith("InvalidPassword"));

// 2. set password with not permitted schema inside
sce = assertThrows(SyncopeClientException.class, () -> {
userCR.setPassword(userCR.getPlainAttr("firstname").get().getValues().getFirst() + "12345!");
createUser(userCR);
});
assertTrue(sce.getElements().iterator().next().startsWith("InvalidPassword"));
} finally {
// restore old password policy
IMPLEMENTATION_SERVICE.update(originalRule);
}
}
}
Loading