From 7d5f85b4799d56800ccb603b49fdfb5d78d151d9 Mon Sep 17 00:00:00 2001 From: jgomer2001 Date: Fri, 14 Jun 2024 13:43:09 -0500 Subject: [PATCH] feat: apply password validation when defined and enabled #8146 Signed-off-by: jgomer2001 --- .../scim/model/conf/AppConfiguration.java | 10 +++++ .../scim/service/scim2/Scim2UserService.java | 39 +++++++++++++++++++ .../jans/scim/ws/rs/scim2/UserWebService.java | 21 ++++++++-- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/jans-scim/model/src/main/java/io/jans/scim/model/conf/AppConfiguration.java b/jans-scim/model/src/main/java/io/jans/scim/model/conf/AppConfiguration.java index 84fe9abafcc..3465112fa6a 100644 --- a/jans-scim/model/src/main/java/io/jans/scim/model/conf/AppConfiguration.java +++ b/jans-scim/model/src/main/java/io/jans/scim/model/conf/AppConfiguration.java @@ -51,6 +51,8 @@ public class AppConfiguration implements Configuration, Serializable { private Boolean disableJdkLogger = true; @DocProperty(description = "Boolean value specifying whether to enable local in-memory cache") private Boolean useLocalCache = false; + @DocProperty(description = "Boolean value specifying whether to bypass the validation defined upon the password attribute") + private boolean skipDefinedPasswordValidation; public String getBaseDN() { return baseDN; @@ -197,4 +199,12 @@ public void setUseLocalCache(Boolean useLocalCache) { this.useLocalCache = useLocalCache; } + public boolean isSkipDefinedPasswordValidation() { + return skipDefinedPasswordValidation; + } + + public void setSkipDefinedPasswordValidation(boolean skipDefinedPasswordValidation) { + this.skipDefinedPasswordValidation = skipDefinedPasswordValidation; + } + } diff --git a/jans-scim/server/src/main/java/io/jans/scim/service/scim2/Scim2UserService.java b/jans-scim/server/src/main/java/io/jans/scim/service/scim2/Scim2UserService.java index 4111a43ccc0..f2c71a07121 100644 --- a/jans-scim/server/src/main/java/io/jans/scim/service/scim2/Scim2UserService.java +++ b/jans-scim/server/src/main/java/io/jans/scim/service/scim2/Scim2UserService.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.regex.Pattern; import java.util.stream.Collectors; import jakarta.annotation.PostConstruct; @@ -30,7 +31,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.jans.scim.model.conf.AppConfiguration; +import io.jans.model.attribute.AttributeValidation; import io.jans.model.GluuStatus; +import io.jans.model.JansAttribute; import io.jans.orm.PersistenceEntryManager; import io.jans.orm.model.PagedResult; import io.jans.orm.model.SortOrder; @@ -619,6 +622,42 @@ public void removePPIDsBranch(String dn) { log.error(e.getMessage(), e); } } + + public boolean passwordValidationPassed(String password) { + + try { + Filter filter = Filter.createEqualityFilter("jansAttrName", "userPassword"); + List attrs = ldapEntryManager.findEntries("ou=attributes,o=jans", + JansAttribute.class, filter, new String[] { "jansValidation" }, 1); + + AttributeValidation av = attrs.get(0).getAttributeValidation(); + + if (av == null) return true; + + int len = Optional.ofNullable(av.getMinLength()).orElse(0); + if (len > 0 && password.length() < len) { + log.error("Password is required to have at least {} characters", len); + return false; + } + + len = Optional.ofNullable(av.getMaxLength()).orElse(0); + if (len > 0 && password.length() > len) { + log.error("Password is required to have at most {} characters", len); + return false; + } + + String regex = av.getRegexp(); + if (regex != null && !Pattern.matches(regex, password)) { + log.error("Provided password does not match the regular expression {}", regex); + return false; + } + + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return true; + + } @PostConstruct private void init() { diff --git a/jans-scim/server/src/main/java/io/jans/scim/ws/rs/scim2/UserWebService.java b/jans-scim/server/src/main/java/io/jans/scim/ws/rs/scim2/UserWebService.java index 50e8e4ec20f..383a8067873 100644 --- a/jans-scim/server/src/main/java/io/jans/scim/ws/rs/scim2/UserWebService.java +++ b/jans-scim/server/src/main/java/io/jans/scim/ws/rs/scim2/UserWebService.java @@ -96,6 +96,21 @@ private void checkUidExistence(String uid, String id) throws DuplicateEntryExcep } } + + private void executeUserValidation(UserResource user, boolean laxRequiredness) + throws SCIMException { + + executeValidation(user, laxRequiredness); + //See #8146 + if (!appConfiguration.isSkipDefinedPasswordValidation()) { + String pwd = user.getPassword(); + + if (pwd != null && !scim2UserService.passwordValidationPassed(pwd)) + throw new SCIMException("Supplied password not conformant to validation " + + "rules defined upon password attribute"); + } + + } private Response doSearch(String filter, Integer startIndex, Integer count, String sortBy, String sortOrder, String attrsList, String excludedAttrsList, String method) { @@ -148,7 +163,7 @@ public Response createUser( try { log.debug("Executing web service method. createUser"); - executeValidation(user); + executeUserValidation(user, false); if (StringUtils.isEmpty(user.getUserName())) throw new SCIMException("Empty username not allowed"); checkUidExistence(user.getUserName()); @@ -244,7 +259,7 @@ public Response updateUser( httpHeaders, uriInfo, HttpMethod.PUT, userResourceType); if (response != null) return response; - executeValidation(user, true); + executeUserValidation(user, true); if (StringUtils.isNotEmpty(user.getUserName())) { checkUidExistence(user.getUserName(), id); } @@ -389,7 +404,7 @@ public Response patchUser( //Throws exception if final representation does not pass overall validation log.debug("patchUser. Revising final resource representation still passes validations"); - executeValidation(user); + executeUserValidation(user, false); ScimResourceUtil.adjustPrimarySubAttributes(user); //Update timestamp