diff --git a/privilege/src/main/java/li/strolch/privilege/base/PasswordStrengthException.java b/privilege/src/main/java/li/strolch/privilege/base/PasswordStrengthException.java new file mode 100644 index 000000000..3e51819bf --- /dev/null +++ b/privilege/src/main/java/li/strolch/privilege/base/PasswordStrengthException.java @@ -0,0 +1,7 @@ +package li.strolch.privilege.base; + +public class PasswordStrengthException extends PrivilegeException { + public PasswordStrengthException(String string) { + super(string); + } +} diff --git a/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java index ce054812b..bb0005790 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java @@ -1882,9 +1882,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } @Override - public void validatePassword(Locale locale, char[] password) throws PrivilegeException { + public void validatePassword(Locale locale, char[] password) throws PasswordStrengthException { if (!this.passwordStrengthHandler.validateStrength(password)) - throw new PrivilegeException(this.passwordStrengthHandler.getDescription(locale)); + throw new PasswordStrengthException(this.passwordStrengthHandler.getDescription(locale)); } @Override diff --git a/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java index c24950fcb..2662bb620 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java @@ -19,10 +19,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import li.strolch.privilege.base.AccessDeniedException; -import li.strolch.privilege.base.NotAuthenticatedException; -import li.strolch.privilege.base.PrivilegeConflictResolution; -import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.base.*; import li.strolch.privilege.model.*; import li.strolch.privilege.model.internal.Role; import li.strolch.privilege.model.internal.User; @@ -40,8 +37,8 @@ public interface PrivilegeHandler { /// /** - * Privilege "PrivilegeAction" which is used for privileges which are not further categorized e.g. s {@link - * #PRIVILEGE_ACTION_PERSIST} and {@link #PRIVILEGE_ACTION_GET_POLICIES} + * Privilege "PrivilegeAction" which is used for privileges which are not further categorized e.g. s + * {@link #PRIVILEGE_ACTION_PERSIST} and {@link #PRIVILEGE_ACTION_GET_POLICIES} */ String PRIVILEGE_ACTION = "PrivilegeAction"; @@ -360,7 +357,7 @@ public interface PrivilegeHandler { * *
* If the password given is null, then the user is created, but can not not login! Otherwise the password must meet - * the requirements of the implementation under {@link PrivilegeHandler#validatePassword(char[])} + * the requirements of the implementation under {@link PrivilegeHandler#validatePassword(Locale, char[])} *
* * @param certificate @@ -369,7 +366,7 @@ public interface PrivilegeHandler { * the {@link UserRep} containing the information to create the new {@link User} * @param password * the password of the new user. If the password is null, then this is accepted but the user can not login, - * otherwise the password must be validated against {@link PrivilegeHandler#validatePassword(char[])} + * otherwise the password must be validated against {@link PrivilegeHandler#validatePassword(Locale, char[])} * * @throws AccessDeniedException * if the user for this certificate may not perform the action @@ -386,8 +383,6 @@ public interface PrivilegeHandler { * the {@link Certificate} of the user which has the privilege to perform this action * @param userReps * the list of users to add or update - * - * @throws PrivilegeException */ void addOrUpdateUsers(Certificate certificate, List* The following fields are considered updateable: *
* If the password given is null, then the user is created, but can not not login! Otherwise the password must meet - * the requirements of the implementation under {@link PrivilegeHandler#validatePassword(char[])} + * the requirements of the implementation under {@link PrivilegeHandler#validatePassword(Locale, char[])} *
* * @param certificate @@ -438,7 +433,7 @@ public interface PrivilegeHandler { * the {@link UserRep} containing the information to replace the existing {@link User} * @param password * the password of the new user. If the password is null, then this is accepted but the user can not login, - * otherwise the password must be validated against {@link PrivilegeHandler#validatePassword(char[])} + * otherwise the password must be validated against {@link PrivilegeHandler#validatePassword(Locale, char[])} * * @throws AccessDeniedException * if the user for this certificate may not perform the action @@ -515,8 +510,8 @@ public interface PrivilegeHandler { /** ** Changes the password for the {@link User} with the given username. If the password is null, then the {@link User} - * can not login anymore. Otherwise the password must meet the requirements of the implementation under {@link - * PrivilegeHandler#validatePassword(char[])} + * can not login anymore. Otherwise the password must meet the requirements of the implementation under + * {@link PrivilegeHandler#validatePassword(Locale, char[])} *
* *@@ -529,7 +524,8 @@ public interface PrivilegeHandler { * the username of the {@link User} for which the password is to be changed * @param password * the new password for this user. If the password is null, then the {@link User} can not login anymore. Otherwise - * the password must meet the requirements of the implementation under {@link PrivilegeHandler#validatePassword(char[])} + * the password must meet the requirements of the implementation under + * {@link PrivilegeHandler#validatePassword(Locale, char[])} * * @throws AccessDeniedException * if the user for this certificate may not perform the action @@ -653,7 +649,7 @@ public interface PrivilegeHandler { * the username of the {@link User} which is registered in the {@link PersistenceHandler} * @param password * the password with which this user is to be authenticated. Null passwords are not accepted and they must meet - * the requirements of the {@link #validatePassword(char[])}-method + * the requirements of the {@link #validatePassword(Locale, char[])}-method * @param keepAlive * should this session be kept alive * @@ -672,7 +668,7 @@ public interface PrivilegeHandler { * the username of the {@link User} which is registered in the {@link PersistenceHandler} * @param password * the password with which this user is to be authenticated. Null passwords are not accepted and they must meet - * the requirements of the {@link #validatePassword(char[])}-method + * the requirements of the {@link #validatePassword(Locale, char[])}-method * @param source * the source of the authentication request, i.e. remote IP * @param usage @@ -763,7 +759,7 @@ public interface PrivilegeHandler { /** * Checks if the given {@link Certificate} is valid. This means that the certificate is for a valid session and that * the user exists for the certificate. This method checks if the {@link Certificate} has been tampered with - * + *
* Returns the {@link PrivilegeContext} for the given {@link Certificate}. The {@link PrivilegeContext} is an * encapsulated state of a user's privileges so that for the duration of a user's call, the user can perform their * actions and do not need to access the {@link PrivilegeHandler} anymore @@ -798,7 +794,7 @@ public interface PrivilegeHandler { /** * Checks if the given {@link Certificate} is valid. This means that the certificate is for a valid session and that * the user exists for the certificate. This method checks if the {@link Certificate} has been tampered with - * + *
* Returns the {@link PrivilegeContext} for the given {@link Certificate}. The {@link PrivilegeContext} is an * encapsulated state of a user's privileges so that for the duration of a user's call, the user can perform their * actions and do not need to access the {@link PrivilegeHandler} anymore @@ -820,7 +816,7 @@ public interface PrivilegeHandler { /** * @see li.strolch.privilege.handler.PasswordStrengthHandler#validateStrength(char[]) */ - void validatePassword(Locale locale, char[] password) throws PrivilegeException; + void validatePassword(Locale locale, char[] password) throws PasswordStrengthException; /** *
@@ -908,9 +904,9 @@ public interface PrivilegeHandler { /** * Special method to open a {@link PrivilegeContext} as a System user, meaning the given systemUsername corresponds - * to an account which has the state {@link UserState#SYSTEM}. This is used in cases where a system user's {@link - * PrivilegeContext} should be open for a longer period of time, or where opening many {@link PrivilegeContext} is - * resource intensive e.g. on low power devices. + * to an account which has the state {@link UserState#SYSTEM}. This is used in cases where a system user's + * {@link PrivilegeContext} should be open for a longer period of time, or where opening many + * {@link PrivilegeContext} is resource intensive e.g. on low power devices. * * @param systemUsername * the username of the system user to perform the action as diff --git a/web-rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java b/web-rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java index ad056b0f9..3823819c7 100644 --- a/web-rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java +++ b/web-rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java @@ -15,17 +15,13 @@ */ package li.strolch.rest.endpoint; +import static jakarta.ws.rs.core.Response.Status.NOT_ACCEPTABLE; import static java.util.Comparator.comparing; import static li.strolch.privilege.handler.PrivilegeHandler.PRIVILEGE_GET_USER; import static li.strolch.rest.helper.ResponseUtil.toResponse; import static li.strolch.rest.helper.RestfulHelper.toJson; import static li.strolch.search.SearchBuilder.buildSimpleValueSearch; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.ws.rs.*; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; import java.text.MessageFormat; import java.util.Arrays; import java.util.Base64; @@ -33,11 +29,17 @@ import java.util.List; import java.util.Locale; import com.google.gson.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import li.strolch.agent.api.ComponentContainer; import li.strolch.model.Tags; import li.strolch.model.json.PrivilegeElementFromJsonVisitor; import li.strolch.model.json.PrivilegeElementToJsonVisitor; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.base.PasswordStrengthException; import li.strolch.privilege.handler.PrivilegeHandler; import li.strolch.privilege.model.Certificate; import li.strolch.privilege.model.UserRep; @@ -312,8 +314,11 @@ public class PrivilegeUsersService { arg.password = passwordString.toCharArray(); ServiceResult svcResult = svcHandler.doService(cert, svc, arg); - if (svcResult.isNok()) + if (svcResult.isNok()) { + if (svcResult.getRootCause() instanceof PasswordStrengthException) + return toResponse(NOT_ACCEPTABLE, svcResult.getRootCause()); return toResponse(svcResult); + } // if user changes their own password, then invalidate the session if (cert.getUsername().equals(username)) { diff --git a/web-rest/src/main/java/li/strolch/rest/filters/AuthenticationResponseFilter.java b/web-rest/src/main/java/li/strolch/rest/filters/AuthenticationResponseFilter.java index e61a7fbae..266cd890b 100644 --- a/web-rest/src/main/java/li/strolch/rest/filters/AuthenticationResponseFilter.java +++ b/web-rest/src/main/java/li/strolch/rest/filters/AuthenticationResponseFilter.java @@ -20,6 +20,7 @@ import static li.strolch.rest.StrolchRestfulConstants.STROLCH_CERTIFICATE; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerResponseContext; import jakarta.ws.rs.container.ContainerResponseFilter; +import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; import li.strolch.privilege.model.Certificate; @@ -47,8 +48,11 @@ public class AuthenticationResponseFilter implements ContainerResponseFilter { logger.info("Invalidating single usage certificate for " + cert.getUsername()); RestfulStrolchComponent.getInstance().getSessionHandler().invalidate(cert); } else if (cert.getUsage().isSetPassword()) { - logger.info("Invalidating SET_PASSWORD usage certificate for " + cert.getUsername()); - RestfulStrolchComponent.getInstance().getSessionHandler().invalidate(cert); + // if not acceptable, then user can try again + if (responseContext.getStatusInfo().toEnum() != Response.Status.NOT_ACCEPTABLE) { + logger.info("Invalidating SET_PASSWORD usage certificate for " + cert.getUsername()); + RestfulStrolchComponent.getInstance().getSessionHandler().invalidate(cert); + } } } }