diff --git a/admin-tools-core/src/main/java/de/chandre/admintool/core/component/AdminComponent.java b/admin-tools-core/src/main/java/de/chandre/admintool/core/component/AdminComponent.java index 7045e1f..bc5c224 100644 --- a/admin-tools-core/src/main/java/de/chandre/admintool/core/component/AdminComponent.java +++ b/admin-tools-core/src/main/java/de/chandre/admintool/core/component/AdminComponent.java @@ -1,142 +1,146 @@ -package de.chandre.admintool.core.component; - -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * A admin component
- * should have at least a minimum of one menu entry - * @author Andre - * - */ -public interface AdminComponent extends Comparable -{ - /** - * @return the displayName - */ - String getDisplayName(); - - /** - * @param displayName the displayName to set - */ - void setDisplayName(String displayName); - - /** - * @return the mainMenu - */ - MenuEntry getMainMenu(); - - /** - * @param mainMenu the mainMenu to set - */ - void setMainMenu(MenuEntry mainMenu); - - /** - * @return the notificationTemplates - */ - List getNotificationTemplates(); - - /** - * path to notification template shown in the top right menu
- * should start with a "li" tag. eg:
- * - * <li class="dropdown messages-menu"> - * - * @see https://almsaeedstudio.com/themes/AdminLTE/documentation/index.html#component-main-header - * - * @param notificationTemplates the notificationTemplates to set - */ - void setNotificationTemplates(List notificationTemplates); - - /** - * path to notification template shown in the top right menu
- * should start with a "li" tag. eg:
- * - * <li class="dropdown messages-menu"> - * - * @see https://almsaeedstudio.com/themes/AdminLTE/documentation/index.html#component-main-header - * - * @param notificationTemplate the template path to notification template - */ - void addNotificationTemplate(String notificationTemplate); - - /** - * @return the additionalCSS - */ - Map getAdditionalCSS(); - - /** - * map with paths to CSS.
- * - * @param additionalCSS the additionalCSS to set - * @see #addAdditionalCSS(String, boolean) - */ - void setAdditionalCSS(Map additionalCSS); - - /** - * path to CSS.
- * Example:
- * - * addAdditionalCSS("/static/myComponent/css/myStyles.css", true);
- * addAdditionalCSS("http://example.com/styles.css", false); - *
- * @param additionalCSS the additionalCSS to set - */ - void addAdditionalCSS(String additionalCSS, boolean relative); - - /** - * @return the additionalJS - */ - Map getAdditionalJS(); - - /** - * map with path to additional JavaScript files.
- * @param additionalJS the additionalJS to set - * @see #addAdditionalJS(String, boolean) - */ - void setAdditionalJS(Map additionalJS); - - /** - * path to additional JavaScript files.
- * Example:
- * - * addAdditionalJS("/static/myComponent/js/myScripts.js", true);
- * addAdditionalJS("http://example.com/script.js", false); - *
- * @param additionalJS the additionalJS to set - */ - void addAdditionalJS(String additionalJS, boolean relative); - - /** - * should return a list of roles which should be able to access this component - * @return - * @since 1.0.1 - */ - Set getSecurityRoles(); - - /** - * security role like "ROLE_USER" or for anonymous "ROLE_ANONYMOUS" validated with spring security
- * requires: admin-tool-core-security
- * setting these roles will no effect on access, only for displaying or hiding menu entries
- * the access restrictions must be done by your own with the security configuration - * - * @param securityRole - * @since 1.0.1 - */ - void addSecurityRole(String securityRole); - - /** - * position of component in menu. Initially set to {@link Integer#MAX_VALUE} - * @return - * @since 1.0.1 - */ - Integer getPosition(); - - /** - * position of component in menu. Initially set to {@link Integer#MAX_VALUE} - * @param position - * @since 1.0.1 - */ - void setPosition(Integer position); -} +package de.chandre.admintool.core.component; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A admin component
+ * should have at least a minimum of one menu entry + * @author Andre + * + */ +public interface AdminComponent extends Comparable +{ + /** + * @return the displayName + */ + String getDisplayName(); + + /** + * @param displayName the displayName to set + */ + void setDisplayName(String displayName); + + /** + * @return the mainMenu + */ + MenuEntry getMainMenu(); + + /** + * @param mainMenu the mainMenu to set + */ + void setMainMenu(MenuEntry mainMenu); + + /** + * @return the notificationTemplates + */ + List getNotificationTemplates(); + + /** + * path to notification template shown in the top right menu
+ * should start with a "li" tag. eg:
+ * + * <li class="dropdown messages-menu"> + * + * @see https://almsaeedstudio.com/themes/AdminLTE/documentation/index.html#component-main-header + * + * @param notificationTemplates the notificationTemplates to set + */ + void setNotificationTemplates(List notificationTemplates); + + /** + * path to notification template shown in the top right menu
+ * should start with a "li" tag. eg:
+ * + * <li class="dropdown messages-menu"> + * + * @see https://almsaeedstudio.com/themes/AdminLTE/documentation/index.html#component-main-header + * + * @param notificationTemplate the template path to notification template + */ + void addNotificationTemplate(String notificationTemplate); + + /** + * @return the additionalCSS + */ + Map getAdditionalCSS(); + + /** + * map with paths to CSS.
+ * + * @param additionalCSS the additionalCSS to set + * @see #addAdditionalCSS(String, boolean) + */ + void setAdditionalCSS(Map additionalCSS); + + /** + * path to CSS.
+ * Example:
+ * + * addAdditionalCSS("/static/myComponent/css/myStyles.css", true);
+ * addAdditionalCSS("http://example.com/styles.css", false); + *
+ * @param additionalCSS the additionalCSS to set + */ + void addAdditionalCSS(String additionalCSS, boolean relative); + + /** + * @return the additionalJS + */ + Map getAdditionalJS(); + + /** + * map with path to additional JavaScript files.
+ * @param additionalJS the additionalJS to set + * @see #addAdditionalJS(String, boolean) + */ + void setAdditionalJS(Map additionalJS); + + /** + * path to additional JavaScript files.
+ * Example:
+ * + * addAdditionalJS("/static/myComponent/js/myScripts.js", true);
+ * addAdditionalJS("http://example.com/script.js", false); + *
+ * @param additionalJS the additionalJS to set + */ + void addAdditionalJS(String additionalJS, boolean relative); + + /** + * should return a list of roles which should be able to access this component + * @return + * @since 1.0.1 + * @deprecated unnecessary because url resolving in template will work, will be removed with version 1.2.0 + */ + @Deprecated + Set getSecurityRoles(); + + /** + * security role like "ROLE_USER" or for anonymous "ROLE_ANONYMOUS" validated with spring security
+ * requires: admin-tool-core-security
+ * setting these roles will no effect on access, only for displaying or hiding menu entries
+ * the access restrictions must be done by your own with the security configuration + * + * @param securityRole + * @since 1.0.1 + * @deprecated unnecessary because url resolving in template will work, will be removed with version 1.2.0 + */ + @Deprecated + void addSecurityRole(String securityRole); + + /** + * position of component in menu. Initially set to {@link Integer#MAX_VALUE} + * @return + * @since 1.0.1 + */ + Integer getPosition(); + + /** + * position of component in menu. Initially set to {@link Integer#MAX_VALUE} + * @param position + * @since 1.0.1 + */ + void setPosition(Integer position); +} diff --git a/admin-tools-core/src/main/java/de/chandre/admintool/core/component/AdminComponentImpl.java b/admin-tools-core/src/main/java/de/chandre/admintool/core/component/AdminComponentImpl.java index ab2ca9d..87edaca 100644 --- a/admin-tools-core/src/main/java/de/chandre/admintool/core/component/AdminComponentImpl.java +++ b/admin-tools-core/src/main/java/de/chandre/admintool/core/component/AdminComponentImpl.java @@ -25,6 +25,7 @@ public class AdminComponentImpl implements AdminComponent private Map additionalCSS = new LinkedHashMap<>(1); private Map additionalJS = new LinkedHashMap<>(1); + @Deprecated private Set securityRoles = new HashSet<>(); private Integer position; @@ -169,8 +170,10 @@ public void addAdditionalJS(String additionalJS, boolean relative) { * @since 1.0.1 * @return the securityRoles * @see #addSecurityRole(String) + * @deprecated unnecessary because url resolving in template will work, will be removed with version 1.2.0 */ @Override + @Deprecated public Set getSecurityRoles() { return securityRoles; } @@ -180,12 +183,15 @@ public Set getSecurityRoles() { * @since 1.0.1 * @param securityRoles the securityRoles to set * @see #addSecurityRole(String) + * @deprecated unnecessary because url resolving in template will work, will be removed with version 1.2.0 */ + @Deprecated public void setSecurityRoles(Set securityRoles) { this.securityRoles = securityRoles; } @Override + @Deprecated public void addSecurityRole(String securityRole) { this.securityRoles.add(securityRole); } @@ -380,8 +386,10 @@ public AdminComponentBuilder additionalJS(String additionalJS, boolean relative) /** * @see AdminComponent#addSecurityRole(String) * @param securityRole + * @deprecated unnecessary because url resolving in template will work, will be removed with version 1.2.0 * @return */ + @Deprecated public AdminComponentBuilder securityRole(String securityRole) { component.addSecurityRole(securityRole); return this; @@ -389,8 +397,10 @@ public AdminComponentBuilder securityRole(String securityRole) { /** * @see AdminComponent#getSecurityRoles().addAll(set) * @param securityRoles + * @deprecated unnecessary because url resolving in template will work, will be removed with version 1.2.0 * @return */ + @Deprecated public AdminComponentBuilder securityRoles(Set securityRoles) { component.getSecurityRoles().addAll(securityRoles); return this; diff --git a/admin-tools-core/src/main/java/de/chandre/admintool/core/component/MenuEntry.java b/admin-tools-core/src/main/java/de/chandre/admintool/core/component/MenuEntry.java index dc5f8f4..42742ea 100644 --- a/admin-tools-core/src/main/java/de/chandre/admintool/core/component/MenuEntry.java +++ b/admin-tools-core/src/main/java/de/chandre/admintool/core/component/MenuEntry.java @@ -44,6 +44,7 @@ public class MenuEntry implements Serializable private boolean useJSHierarchy = false; private Map additionalJS = new LinkedHashMap<>(1); + @Deprecated private Set securityRoles = new HashSet<>(); private Map variables = new HashMap<>(); @@ -74,6 +75,7 @@ public MenuEntry(String name, String displayName, String target) { * @param target - the template path. see {@link #setTarget(String)} * @param securityRoles - Set of roles to check against the current user for displaying/hiding menu entries in the frontend */ + @Deprecated public MenuEntry(String name, String displayName, String target, Set securityRoles) { super(); this.name = name; @@ -410,7 +412,9 @@ public void setUseJSHierarchy(boolean useJSHierarchy) { * first menu entry or component with roles will return it. * @return a unmodifiable Set or empty Set * @since 1.0.1 + * @deprecated unnecessary because url resolving in template will work, will be removed with version 1.2.0 */ + @Deprecated public Set getAffectedSecurityRoles() { if (CollectionUtils.isEmpty(securityRoles)) { Stream parents = reverseFlattened(); @@ -435,7 +439,9 @@ public Set getAffectedSecurityRoles() { /** * @return the securityRoles * @since 1.0.1 + * @deprecated unnecessary because url resolving in template will work, will be removed with version 1.2.0 */ + @Deprecated public Set getSecurityRoles() { return securityRoles; } @@ -443,7 +449,9 @@ public Set getSecurityRoles() { /** * @param securityRoles the securityRoles to set * @since 1.0.1 + * @deprecated unnecessary because url resolving in template will work, will be removed with version 1.2.0 */ + @Deprecated public void setSecurityRoles(Set securityRoles) { this.securityRoles = securityRoles; } @@ -451,7 +459,9 @@ public void setSecurityRoles(Set securityRoles) { /** * @param securityRole the securityRoles to set * @since 1.0.1 + * @deprecated unnecessary because url resolving in template will work, will be removed with version 1.2.0 */ + @Deprecated public void addSecurityRole(String securityRole) { this.securityRoles.add(securityRole); } @@ -749,7 +759,9 @@ public MenuEntryBuilder useJSHierarchy(boolean useJSHierarchy) { * @see MenuEntry#setSecurityRoles(Set) * @param securityRoles * @return + * @deprecated unnecessary because url resolving in template will work, will be removed with version 1.2.0 */ + @Deprecated public MenuEntryBuilder securityRoles(Set securityRoles) { entry.setSecurityRoles(securityRoles); return this; @@ -758,7 +770,9 @@ public MenuEntryBuilder securityRoles(Set securityRoles) { * @see MenuEntry#addSecurityRole(String) * @param securityRole * @return + * @deprecated unnecessary because url resolving in template will work, will be removed with version 1.2.0 */ + @Deprecated public MenuEntryBuilder securityRole(String securityRole) { entry.addSecurityRole(securityRole); return this; diff --git a/admin-tools-core/src/main/resources/static/admintool/js/select2-util.js b/admin-tools-core/src/main/resources/static/admintool/js/select2-util.js index cd7c30e..d4e71c2 100644 --- a/admin-tools-core/src/main/resources/static/admintool/js/select2-util.js +++ b/admin-tools-core/src/main/resources/static/admintool/js/select2-util.js @@ -26,7 +26,7 @@ AdminTool.Select2Util = function(parent) { }; this.clearSelect = function(selectId) { -// self.setValue(selectId); + self.setValue(selectId); }; this.setValue = function(selectId, value = []) { @@ -83,4 +83,4 @@ AdminTool.Select2Util = function(parent) { }; this.construct(parent); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/admin-tools-core/src/main/resources/static/admintool/js/validation-util.js b/admin-tools-core/src/main/resources/static/admintool/js/validation-util.js index 38576cf..78ea2e1 100644 --- a/admin-tools-core/src/main/resources/static/admintool/js/validation-util.js +++ b/admin-tools-core/src/main/resources/static/admintool/js/validation-util.js @@ -11,6 +11,10 @@ AdminTool.ValidationUtil = function(parent) { return getByID(formId).validator(); }; + this.destroy = function(formId) { + getByID(formId).validator('destroy'); + }; + this.validate = function(formId) { getByID(formId).validator('validate'); return self.hasValidationErrors(formId); @@ -27,19 +31,19 @@ AdminTool.ValidationUtil = function(parent) { }; this.reloadValidator = function(formId) { - getByID(formId).validator('destroy'); + self.destroy(formId); // validator doesn't remove everything getByID(formId).find('.form-control-feedback').removeClass( 'glyphicon-remove'); self.create(formId); }; - this.showFieldErrorsOnATErrorList = function(data) { + this.showFieldErrorsOnATErrorList = function(data, formId) { var globalErrors = []; for (var i = 0; i < data.length; i++) { var error = data[i]; if (error.field && error.field.length > 0) { - self.parent.showCustomError(error.field, error.message, this.userDataFormId); + self.showCustomError(error.field, error.message, formId); } else { globalErrors.push('
  • ' + error.message + '
  • '); } @@ -51,4 +55,4 @@ AdminTool.ValidationUtil = function(parent) { }; this.construct(parent); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/admin-tools-security/admin-tools-security-commons/src/main/java/de/chandre/admintool/security/commons/auth/LoginAttemptServiceImpl.java b/admin-tools-security/admin-tools-security-commons/src/main/java/de/chandre/admintool/security/commons/auth/LoginAttemptServiceImpl.java index 0d0c57f..77c8126 100644 --- a/admin-tools-security/admin-tools-security-commons/src/main/java/de/chandre/admintool/security/commons/auth/LoginAttemptServiceImpl.java +++ b/admin-tools-security/admin-tools-security-commons/src/main/java/de/chandre/admintool/security/commons/auth/LoginAttemptServiceImpl.java @@ -25,14 +25,33 @@ public class LoginAttemptServiceImpl implements LoginAttemptService { private final Map attemptsCache = new ConcurrentHashMap<>(); + /** + * initializes the LoginAttemptService with maxAttempts=5, useUserName=true and useRemoteAddress=false + * + * @see #LoginAttemptServiceImpl(int, boolean, boolean) + */ public LoginAttemptServiceImpl() { this(5); } + /** + * initializes the LoginAttemptService with given maxAttempt, useUserName=true and useRemoteAddress=false + * + * @param maxAttempts number of maximum attempts a user can try logging in with wrong password until service marks user as blocked + * + * @see #LoginAttemptServiceImpl(int, boolean, boolean) + */ public LoginAttemptServiceImpl(int maxAttempts) { this(maxAttempts, true, false); } + /** + * initializes the LoginAttemptService with given parameters + * + * @param maxAttempts number of maximum attempts a user can try logging in with wrong password until service marks user as blocked + * @param useUserName used in login failure/success listeners to add user/invalidate username + * @param useRemoteAddress used in login failure/success listeners to add user/invalidate remote address (may not work/be used when behind a (reverse)proxy) + */ public LoginAttemptServiceImpl(int maxAttempts, boolean useUserName, boolean useRemoteAddress) { super(); MAX_ATTEMPT = maxAttempts; diff --git a/admin-tools-security/admin-tools-security-commons/src/main/resources/templates/admintool/layout/anonymousLayout.html b/admin-tools-security/admin-tools-security-commons/src/main/resources/templates/admintool/layout/anonymousLayout.html new file mode 100644 index 0000000..2b5644d --- /dev/null +++ b/admin-tools-security/admin-tools-security-commons/src/main/resources/templates/admintool/layout/anonymousLayout.html @@ -0,0 +1,119 @@ + + + + + + + + + + + +
    +
    + +
    + + +
    + + + + + +
    + + + + +
    + +
    + + +
    + + + + + + + + +
    + +
    + + + + + +
    +
    + + + + + + + \ No newline at end of file diff --git a/admin-tools-security/admin-tools-security-commons/src/main/resources/templates/admintool/login.html b/admin-tools-security/admin-tools-security-commons/src/main/resources/templates/admintool/login.html index 3c4d899..ee3e9ed 100644 --- a/admin-tools-security/admin-tools-security-commons/src/main/resources/templates/admintool/login.html +++ b/admin-tools-security/admin-tools-security-commons/src/main/resources/templates/admintool/login.html @@ -1,122 +1,46 @@ - - - - - - - - - - - -
    -
    - -
    - - -
    - - - - - -
    - - - - -
    - - - -
    - - - - - - -
    - -
    - - - - - -
    -
    - - - - - - + + + +
    + + + +
    + + + \ No newline at end of file diff --git a/admin-tools-security/admin-tools-security-dbuser/README.md b/admin-tools-security/admin-tools-security-dbuser/README.md new file mode 100644 index 0000000..febbdd2 --- /dev/null +++ b/admin-tools-security/admin-tools-security-dbuser/README.md @@ -0,0 +1,206 @@ +# The Core-Security integration Plugin +> Integrates Spring Security and overrides the menu to use admintool.*.securityRoles to check if menu entries could be shown + +## Features +* `simple user view`: a view where you can manage user states, passwords and roles and add new users(since 1.1.5) + +![Preview image](doc/screen_userview_org.png?raw=true "AdminTool User-View UI") + +## Introduced with +* admin-tools-core:1.1.7 + +## Requirements, Dependencies +* spring-framework (core, security, spring-data, spring-mvc) +* JPA 2.1 + + + +## Usage +Until version 1.1.7 the following dependencies must be used. +```xml + + de.chandre.admin-tools + admin-tools-core + 1.1.7 + + + de.chandre.admin-tools.security + admin-tools-security-dbuser + 1.1.7 + +``` + +## Integration +An example configuration can be done analog to file-server demo project. + +At first there should be some special beans provided to spring context + +### Persistence-Beans +To enable the auditing (creations and last changes) you must provide an AuditProvider. +```java +@Configuration +@EnableTransactionManagement +@EnableJpaAuditing +@EnableJpaRepositories +public class PersistenceBeans { + + @Bean + public PlatformTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactory) { + JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(entityManagerFactory.getObject()); + return transactionManager; + } + + @Bean("systemUser") + public ATUser systemUser() { + //creating an system user to catch all job executions for auditing + return AdminToolSecDBBeans.createSystemUser(); + } + + @Bean + public AuditorAware auditorProvider(@Qualifier("systemUser") ATUser systemUser) { + //returning the pre-configured AuditProvider + return AdminToolSecDBBeans.auditorProvider(systemUser, AdminToolSecDBBeans.createDummyUser("UNKNOWN")); + } +} +``` + + +### Security-Beans +```java +@Configuration +public class SecurityBeans { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + /* + * LoginAttemptService is optional in all services + * can also be initialized with maxAttempts: new LoginAttemptServiceImpl(5) + */ + @Bean + public LoginAttemptService loginAttemptService() { + return new LoginAttemptServiceImpl(); + } + + @Bean + public AdminToolSecDBAuthenticationFailureListener adminToolAuthenticationFailureListener(LoginAttemptService loginAttemptService) { + return new AdminToolSecDBAuthenticationFailureListener(loginAttemptService); + } + + @Bean("adminToolAuthenticationSuccessHandler") + public AdminToolSecDBAuthenticationSuccessHandler adminToolAuthenticationSuccessHandler(LoginAttemptService loginAttemptService) { + return new AdminToolSecDBAuthenticationSuccessHandler(loginAttemptService); + } + +} +``` + +### Security-Config +This is just an example configuration, role names depending on your own! + +```java +@EnableWebSecurity +public class SecurityConfig { + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth, AdminToolSecDBUserDetailsService userDetailsService, + PasswordEncoder pwEncoder, DataSource dataSource) throws Exception { + + auth.userDetailsService(userDetailsService).passwordEncoder(pwEncoder); + } + + @Configuration + public static class AdminToolSecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private AdminToolSecDBAuthenticationSuccessHandler adminToolAuthenticationSuccessHandler; + + public AdminToolSecurityConfig() { + //optional: disable defaults -> must be set manually in configure() method + super(true); + } + + @Override + public void configure(WebSecurity web) throws Exception { + System.out.println("configure WebSecurity"); + web + .ignoring() + .antMatchers("/static/**", "/webjars/**"); + + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + System.out.println("configure HttpSecurity"); + http + .headers().frameOptions().sameOrigin().and() + + .authorizeRequests() + //Profile settings + .antMatchers(AdminTool.ROOTCONTEXT + "/accessmanagement/resetpassword/**").permitAll() + .antMatchers(AdminTool.ROOTCONTEXT + "/accessmanagement/user/profile").fullyAuthenticated() + .antMatchers(AdminTool.ROOTCONTEXT + "/accessmanagement/user/profile/**").fullyAuthenticated() + //Accessmanagement + .antMatchers(AdminTool.ROOTCONTEXT + "/accessmanagement/client").hasAnyRole("CLIENT") + .antMatchers(AdminTool.ROOTCONTEXT + "/accessmanagement/client/**").hasAnyRole("CLIENT") + .antMatchers(AdminTool.ROOTCONTEXT + "/accessmanagement/role").hasAnyRole("ROLES") + .antMatchers(AdminTool.ROOTCONTEXT + "/accessmanagement/role/**").hasAnyRole("ROLES") + .antMatchers(AdminTool.ROOTCONTEXT + "/accessmanagement/user").hasAnyRole("USERS") + .antMatchers(AdminTool.ROOTCONTEXT + "/accessmanagement/user/**").hasAnyRole("USERS") + .antMatchers(AdminTool.ROOTCONTEXT + "/accessmanagement/usergroup").hasAnyRole("GROUPS") + .antMatchers(AdminTool.ROOTCONTEXT + "/accessmanagement/usergroup/**").hasAnyRole("GROUPS") + .antMatchers(AdminTool.ROOTCONTEXT + "/accessmanagement").hasAnyRole("ACCMGMT") + .antMatchers(AdminTool.ROOTCONTEXT + "/accessmanagement/**").hasAnyRole("ACCMGMT") + + .antMatchers(AdminTool.ROOTCONTEXT + "/dbbrowser").hasAnyRole("DBBROWSER") + .antMatchers(AdminTool.ROOTCONTEXT + "/dbbrowser/**").hasAnyRole("DBBROWSER") + .antMatchers(AdminTool.ROOTCONTEXT + "/filebrowser").hasAnyRole("FILEBROWSER") + .antMatchers(AdminTool.ROOTCONTEXT + "/filebrowser/**").hasAnyRole("FILEBROWSER") + .antMatchers(AdminTool.ROOTCONTEXT + "/fileviewer").hasAnyRole("FILEVIEWER") + .antMatchers(AdminTool.ROOTCONTEXT + "/fileviewer/**").hasAnyRole("FILEVIEWER") + .antMatchers(AdminTool.ROOTCONTEXT + "/jmx").hasAnyRole("JMX") + .antMatchers(AdminTool.ROOTCONTEXT + "/jmx/**").hasAnyRole("JMX") + .antMatchers(AdminTool.ROOTCONTEXT + "/log4j2").hasAnyRole("LOG4J") + .antMatchers(AdminTool.ROOTCONTEXT + "/log4j2/**").hasAnyRole("LOG4J") + .antMatchers("/monitoring").hasAnyRole("MELODY") + .antMatchers(AdminTool.ROOTCONTEXT + "/javamelody").hasAnyRole("MELODY") + .antMatchers(AdminTool.ROOTCONTEXT + "/javamelody/**").hasAnyRole("MELODY") + .antMatchers(AdminTool.ROOTCONTEXT + "/properties").hasAnyRole("PROPS") + .antMatchers(AdminTool.ROOTCONTEXT + "/properties/**").hasAnyRole("PROPS") + .antMatchers(AdminTool.ROOTCONTEXT + "/quartz").hasAnyRole("QUARTZ") + .antMatchers(AdminTool.ROOTCONTEXT + "/quartz/**").hasAnyRole("QUARTZ") + .antMatchers(AdminTool.ROOTCONTEXT, AdminTool.ROOTCONTEXT + "/**").hasAnyRole("GENERIC") + .anyRequest().anonymous() + .and() + .formLogin() + .loginPage(AdminTool.ROOTCONTEXT + "/login") + //adding the custom success handler + .successHandler(adminToolAuthenticationSuccessHandler) + .permitAll() + .and() + .logout() + .logoutUrl(AdminTool.ROOTCONTEXT + "/logout") + .logoutSuccessUrl(AdminTool.ROOTCONTEXT) + .invalidateHttpSession(true) + .and() + .exceptionHandling() + .accessDeniedPage(AdminTool.ROOTCONTEXT + "/content/genericError") + //configuring defaults + .and() + .csrf().and() + .addFilter(new WebAsyncManagerIntegrationFilter()) + .sessionManagement().and() + .securityContext().and() + .requestCache().and() + .anonymous().and() + .servletApi().and() + .apply(new DefaultLoginPageConfigurer()) + ; + } + + } +} +``` \ No newline at end of file diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBLoader.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBLoader.java index daee503..a59aac4 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBLoader.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBLoader.java @@ -62,7 +62,6 @@ public void configureAdminTool() AdminComponent component = new AdminComponentImpl.AdminComponentBuilder() .displayName("User-Management") .position(config.getPosition()) - .securityRoles(allRoles) .build(); if (StringUtils.isEmpty(config.getValidatorCdnPath())) { @@ -84,14 +83,17 @@ public void configureAdminTool() component.addAdditionalJS("/static/admintool/js/validation-util.js", true); component.addAdditionalJS("/static/admintool/js/select2-util.js", true); + component.addAdditionalJS("/static/admintool/security/js/password.min.js", true); + component.addAdditionalCSS("/static/admintool/security/css/password.min.css", true); + component.addAdditionalCSS("/static/admintool/security/css/accessmanagement.css", true); + component.addAdditionalJS("/static/admintool/security/js/passwordgen.js", true); component.addAdditionalJS("/static/admintool/security/js/accessmanagement.js", true); MenuEntry mainMenu = MenuEntry.builder() .displayName("Access Management") .name("accessmanagement") .target("security/content/notExisting") - .securityRoles(allRoles) .resouceMessageKeySuffix(Constants.MSG_KEY_PREFIX + "accessManagement.displayname") .build(); component.setMainMenu(mainMenu); @@ -100,7 +102,6 @@ public void configureAdminTool() mainMenu.addSubmenuEntry(MenuEntry.builder().name("accessmanagement/users").displayName("Users") .resouceMessageKeySuffix(Constants.MSG_KEY_PREFIX + "accessManagement.users.displayname") .target("security/content/users") - .securityRoles(config.getSecurityRolesUsers()) .additionalJS("/static/admintool/security/js/users.js", true) .build()); @@ -108,7 +109,6 @@ public void configureAdminTool() mainMenu.addSubmenuEntry(MenuEntry.builder().name("accessmanagement/userGroups").displayName("UserGroups") .resouceMessageKeySuffix(Constants.MSG_KEY_PREFIX + "accessManagement.usergroups.displayname") .target("security/content/usergroups") - .securityRoles(config.getSecurityRolesUsers()) .additionalJS("/static/admintool/security/js/accessrelation.js", true) .additionalJS("/static/admintool/security/js/usergroups.js", true) .build()); @@ -117,7 +117,6 @@ public void configureAdminTool() mainMenu.addSubmenuEntry(MenuEntry.builder().name("accessmanagement/roles").displayName("Roles") .resouceMessageKeySuffix(Constants.MSG_KEY_PREFIX + "accessManagement.roles.displayname") .target("security/content/roles") - .securityRoles(config.getSecurityRolesUsers()) .additionalJS("/static/admintool/security/js/accessrelation.js", true) .build()); @@ -126,11 +125,24 @@ public void configureAdminTool() mainMenu.addSubmenuEntry(MenuEntry.builder().name("accessmanagement/clients").displayName("Clients") .resouceMessageKeySuffix(Constants.MSG_KEY_PREFIX + "accessManagement.clients.displayname") .target("security/content/clients") - .securityRoles(config.getSecurityRolesUsers()) .additionalJS("/static/admintool/security/js/accessrelation.js", true) .build()); } + //hidden pseudo target ... resolved via user controller -> addCommonContextVars(..) + mainMenu.addSubmenuEntry(MenuEntry.builder().name("accessmanagement/user/profile").displayName("Profile") + .hide(true) + .target("security/content/profile") + .additionalJS("/static/admintool/security/js/profile.js", true) + .build()); + + //hidden pseudo target ... resolved via user controller -> addCommonContextVars(..) + mainMenu.addSubmenuEntry(MenuEntry.builder().name("accessmanagement/reset").displayName("Reset Password") + .hide(true) + .target("security/content/resetPassword") + .additionalJS("/static/admintool/security/js/resetPassword.js", true) + .build()); + //finally adding the component adminTool.addComponent(component); } diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBProperties.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBProperties.java index 8f4a1fe..8ce8952 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBProperties.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBProperties.java @@ -1,5 +1,6 @@ package de.chandre.admintool.security.dbuser; +import java.time.Period; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -165,6 +166,10 @@ public static class Users { private Validations email = new Validations(); private Validations phone = new Validations(); + private boolean directPasswordChangeAllowed = true; + private boolean directPasswordChangeInProfileAllowed = true; + + private String passwordHashPeriod = "P7D"; public List getAvailableLocales() { return availableLocales; @@ -208,6 +213,26 @@ public Validations getPhone() { public void setPhone(Validations phone) { this.phone = phone; } + public boolean isDirectPasswordChangeAllowed() { + return directPasswordChangeAllowed; + } + public void setDirectPasswordChangeAllowed(boolean directPasswordChangeAllowed) { + this.directPasswordChangeAllowed = directPasswordChangeAllowed; + } + public boolean isDirectPasswordChangeInProfileAllowed() { + return directPasswordChangeInProfileAllowed; + } + public void setDirectPasswordChangeInProfileAllowed(boolean directPasswordChangeInProfileAllowed) { + this.directPasswordChangeInProfileAllowed = directPasswordChangeInProfileAllowed; + } + public void setPasswordHashPeriod(String passwordHashPeriod) { + this.passwordHashPeriod = passwordHashPeriod; + } + public Period getPasswordHashPeriod() { + return Period.parse(passwordHashPeriod); + } + + } public static class UserGroups { diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBTemplateUtils.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBTemplateUtils.java index c2d8093..4900fce 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBTemplateUtils.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBTemplateUtils.java @@ -11,4 +11,10 @@ public interface AdminToolSecDBTemplateUtils { String getDefaultTimeZone(); + boolean isDirectPasswordChangeAllowed(); + + boolean isDirectPasswordChangeInProfileAllowed(); + + boolean isCommunicatorImplemented(); + } diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBTemplateUtilsImpl.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBTemplateUtilsImpl.java index b1c25a6..c4c9c78 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBTemplateUtilsImpl.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/AdminToolSecDBTemplateUtilsImpl.java @@ -9,6 +9,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import de.chandre.admintool.security.dbuser.service.comm.AdminToolSecDBCommunicator; + /** * * @author André @@ -21,6 +23,9 @@ public class AdminToolSecDBTemplateUtilsImpl implements AdminToolSecDBTemplateUt @Autowired private AdminToolSecDBProperties properties; + @Autowired(required=false) + private AdminToolSecDBCommunicator communicator; + @Override public String[] getAvailableTimeZones() { return TimeZone.getAvailableIDs(); @@ -33,7 +38,22 @@ public String getDefaultTimeZone() { @Override public Set getAvailableLocales() { - return properties.getUsers().getAvailableLocales().stream().map(LocaleUtils::toLocale).collect(Collectors.toSet()); } + + @Override + public boolean isDirectPasswordChangeAllowed() { + return properties.getUsers().isDirectPasswordChangeAllowed(); + } + + @Override + public boolean isDirectPasswordChangeInProfileAllowed() { + return properties.getUsers().isDirectPasswordChangeInProfileAllowed(); + } + + @Override + public boolean isCommunicatorImplemented() { + return communicator != null; + } + } diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/Constants.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/Constants.java index bcdccd0..0cfa7fa 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/Constants.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/Constants.java @@ -7,5 +7,29 @@ */ public interface Constants { - String MSG_KEY_PREFIX = "security.db."; + String MSG_KEY_PREFIX = "security.db."; + + String CONTENT_TPL_PATH = "/security/content/"; + + /** + * Constants for initiator of communication + * + * @author André + * @since 1.1.7 + * + */ + public enum CommunicationProcess { + /** + * user has been created (by process or user) + */ + CREATE_USER, + /** + * reset password request was created by an admin user + */ + RESET_PASSWORD_REQUEST_ADMIN, + /** + * reset password request was created by user + */ + RESET_PASSWORD_REQUEST_SELF; + }; } diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/auth/PasswordTO.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/auth/PasswordTO.java new file mode 100644 index 0000000..1d4213e --- /dev/null +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/auth/PasswordTO.java @@ -0,0 +1,36 @@ +package de.chandre.admintool.security.dbuser.auth; + +import java.io.Serializable; + +/** + * + * @author André + * @since 1.1.7 + * + */ +public class PasswordTO implements Serializable { + private static final long serialVersionUID = 7282206856603860917L; + + private String currentPassword; + private String newPassword; + private String passwordConfirm; + + public String getCurrentPassword() { + return currentPassword; + } + public void setCurrentPassword(String currentPassword) { + this.currentPassword = currentPassword; + } + public String getNewPassword() { + return newPassword; + } + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } + public String getPasswordConfirm() { + return passwordConfirm; + } + public void setPasswordConfirm(String passwordConfirm) { + this.passwordConfirm = passwordConfirm; + } +} diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/ATSecDBAbctractController.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/ATSecDBAbctractController.java new file mode 100644 index 0000000..aa6c7b6 --- /dev/null +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/ATSecDBAbctractController.java @@ -0,0 +1,57 @@ +package de.chandre.admintool.security.dbuser.contoller; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; + +import de.chandre.admintool.core.controller.AbstractAdminController; +import de.chandre.admintool.core.ui.ATError; +import de.chandre.admintool.security.dbuser.service.validation.ATSecDBValidator; + +/** + * + * @author André + * @since 1.1.7 + * + */ +public class ATSecDBAbctractController extends AbstractAdminController { + + @Autowired(required=false) + private MessageSource messageSource; + + /** + * logging the error and creates error output + * + * @param e + * @param logger + * @param validator + * @param key + * @param suffix + * @param defaultMessagePrefix + * @return + */ + protected Set handleException(E e, Log logger, V validator, + String key, String suffix, String defaultMessagePrefix) { + logger.error(e.getMessage(), e); + Set errors = new HashSet<>(1); + errors.add(new ATError(key, + validator.getMessageWithSuffix(suffix, null, defaultMessagePrefix + ": "+ e.getMessage()))); + return errors; + } + + protected Set handleException(E e, Log logger, + String key, String messageKey, String defaultMessagePrefix) { + logger.error(e.getMessage(), e); + Set errors = new HashSet<>(1); + if (null != messageSource) { + errors.add(new ATError(key, + messageSource.getMessage(key, null, defaultMessagePrefix + ": "+ e.getMessage(), LocaleContextHolder.getLocale()))); + } + + return errors; + } +} diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBClientController.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBClientController.java index 8a98b26..cccae52 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBClientController.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBClientController.java @@ -14,12 +14,12 @@ import org.springframework.web.bind.annotation.ResponseBody; import de.chandre.admintool.core.AdminTool; -import de.chandre.admintool.core.controller.AbstractAdminController; import de.chandre.admintool.core.ui.ATError; import de.chandre.admintool.core.ui.select2.Select2GroupedTO; import de.chandre.admintool.security.dbuser.auth.AccessRelationTO; import de.chandre.admintool.security.dbuser.domain.ATClient; import de.chandre.admintool.security.dbuser.service.AdminToolSecDBClientService; +import de.chandre.admintool.security.dbuser.service.validation.AdminToolSecDBClientValidator; /** * Client controller @@ -29,7 +29,7 @@ */ @Controller @RequestMapping(AdminTool.ROOTCONTEXT + "/accessmanagement/client") -public class AdminToolSecDBClientController extends AbstractAdminController { +public class AdminToolSecDBClientController extends ATSecDBAbctractController { private static final Log LOGGER = LogFactory.getLog(AdminToolSecDBClientController.class); @@ -39,6 +39,9 @@ public class AdminToolSecDBClientController extends AbstractAdminController { @Autowired private AdminToolSecDBTransformUtil transformUtil; + @Autowired + private AdminToolSecDBClientValidator validator; + @RequestMapping(path="/get", method=RequestMethod.GET) @ResponseBody public Select2GroupedTO getUserGroups() { @@ -62,13 +65,21 @@ public String changeState(@PathVariable("name") String name) { @RequestMapping(path="/update", method=RequestMethod.POST) @ResponseBody public Set update(@RequestBody AccessRelationTO accessRelationTO) { - return clientService.updateClient(accessRelationTO); + try { + return clientService.updateClient(accessRelationTO); + } catch (Exception e) { + return handleException(e, LOGGER, validator, "error.update.client", "update.client.error", "Could not update Client"); + } } @RequestMapping(path="/add", method=RequestMethod.POST) @ResponseBody public Set add(@RequestBody AccessRelationTO accessRelationTO) { - return clientService.addClient(accessRelationTO); + try { + return clientService.addClient(accessRelationTO); + } catch (Exception e) { + return handleException(e, LOGGER, validator, "error.add.client", "add.client.error", "Could not add Client"); + } } @RequestMapping(path="/remove/name/{name}", method=RequestMethod.POST) diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBProfileController.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBProfileController.java new file mode 100644 index 0000000..97bac84 --- /dev/null +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBProfileController.java @@ -0,0 +1,129 @@ +package de.chandre.admintool.security.dbuser.contoller; + +import java.io.IOException; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +import de.chandre.admintool.core.AdminTool; +import de.chandre.admintool.core.ui.ATError; +import de.chandre.admintool.security.commons.LoginController; +import de.chandre.admintool.security.commons.TemplateUserService; +import de.chandre.admintool.security.commons.auth.UserTO; +import de.chandre.admintool.security.dbuser.AdminToolSecDBProperties; +import de.chandre.admintool.security.dbuser.AdminToolSecDBTemplateUtils; +import de.chandre.admintool.security.dbuser.Constants; +import de.chandre.admintool.security.dbuser.Constants.CommunicationProcess; +import de.chandre.admintool.security.dbuser.auth.PasswordTO; +import de.chandre.admintool.security.dbuser.domain.ATUser; +import de.chandre.admintool.security.dbuser.service.AdminToolSecDBUserDetailsService; +import de.chandre.admintool.security.dbuser.service.validation.AdminToolSecDBUserValidator; + +/** + * + * @author André + * @since 1.1.7 + * + */ +@Controller +@RequestMapping(AdminTool.ROOTCONTEXT + "/accessmanagement/user/profile") +public class AdminToolSecDBProfileController extends ATSecDBAbctractController { + + private final Log LOGGER = LogFactory.getLog(AdminToolSecDBProfileController.class); + + @Autowired + private AdminToolSecDBProperties properties; + + @Autowired + private TemplateUserService templateUserService; + + @Autowired + private AdminToolSecDBUserDetailsService userService; + + @Autowired + private AdminToolSecDBUserValidator userValidator; + + @Autowired + private AdminToolSecDBTemplateUtils templateUtils; + + @RequestMapping(value= {"", "/"}) + public String profile(ModelMap model, HttpServletRequest request, HttpServletResponse response) throws IOException { + if (templateUserService.isAnonymous()) { + response.sendRedirect(super.getRootContext(request) + LoginController.LOGIN_PATH); + } + addCommonContextVars(model, request); + ATUser currentUser = templateUserService.getUserPrincipal(ATUser.class); + model.put("currentUser", userService.getUserForId(currentUser.getId())); + return AdminTool.ROOTCONTEXT_NAME + Constants.CONTENT_TPL_PATH + "profile"; + } + + @RequestMapping(value="/update", method=RequestMethod.POST) + @ResponseBody + public Set profileUpdate(@RequestBody UserTO userTO, HttpServletRequest request, HttpServletResponse response) throws IOException { + LOGGER.debug("receiving /update request"); + if (templateUserService.isAnonymous()) { + return null; + } + + try { + userTO.setUsername(templateUserService.getUserName()); + Set errors = userService.updateProfile(userTO); + return errors; + } catch (Exception e) { + return handleException(e, LOGGER, "error.update.profile", + Constants.MSG_KEY_PREFIX + "profile.update.profile.error", "Could not update profile"); + } + } + + @RequestMapping(value="/password/update", method=RequestMethod.POST) + @ResponseBody + public Set updatePassword(@RequestBody PasswordTO userTO, HttpServletRequest request, HttpServletResponse response) throws IOException { + LOGGER.debug("receiving /password/update request"); + if (!properties.getUsers().isDirectPasswordChangeInProfileAllowed() || templateUserService.isAnonymous()) { + return null; + } + + try { + String userName = templateUserService.getUserName(); + Set errors = userValidator.validatePasswordChange(userName, userTO.getCurrentPassword(), + userTO.getNewPassword(), userTO.getPasswordConfirm()); + + if(CollectionUtils.isEmpty(errors)) { + userService.updatePassword(userName, userTO.getNewPassword()); + } + return errors; + } catch (Exception e) { + return handleException(e, LOGGER, "error.update.password", + Constants.MSG_KEY_PREFIX + "profile.update.password.error", "Could not update password"); + } + } + + @RequestMapping(value="/password/reset", method=RequestMethod.POST) + @ResponseBody + public String resetPassword( HttpServletRequest request, HttpServletResponse response) throws IOException { + LOGGER.debug("receiving /password/reset request"); + if (!templateUtils.isCommunicatorImplemented() || templateUserService.isAnonymous()) { + return Boolean.FALSE.toString(); + } + try { + userService.createResetPassword(templateUserService.getUserName(), CommunicationProcess.RESET_PASSWORD_REQUEST_SELF); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + return Boolean.FALSE.toString(); + } + return Boolean.TRUE.toString(); + } + +} diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBPublicController.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBPublicController.java new file mode 100644 index 0000000..0ac7c11 --- /dev/null +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBPublicController.java @@ -0,0 +1,132 @@ +package de.chandre.admintool.security.dbuser.contoller; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.Period; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import de.chandre.admintool.core.AdminTool; +import de.chandre.admintool.core.controller.AbstractAdminController; +import de.chandre.admintool.core.ui.ATError; +import de.chandre.admintool.security.commons.LoginController; +import de.chandre.admintool.security.dbuser.AdminToolSecDBProperties; +import de.chandre.admintool.security.dbuser.Constants; +import de.chandre.admintool.security.dbuser.Constants.CommunicationProcess; +import de.chandre.admintool.security.dbuser.domain.ATUser; +import de.chandre.admintool.security.dbuser.service.AdminToolSecDBUserDetailsService; +import de.chandre.admintool.security.dbuser.service.validation.AdminToolSecDBUserValidator; + +@Controller +@RequestMapping(AdminTool.ROOTCONTEXT + "/accessmanagement/reset") +public class AdminToolSecDBPublicController extends AbstractAdminController { + + private static final Log LOGGER = LogFactory.getLog(AdminToolSecDBUserController.class); + + @Autowired + private AdminToolSecDBProperties properties; + + @Autowired + private AdminToolSecDBUserDetailsService userService; + + @Autowired + private AdminToolSecDBUserValidator userValidator; + + @RequestMapping(value="/password", method=RequestMethod.GET) + public String createResetPasswordPage(ModelMap model, HttpServletRequest request, HttpServletResponse response) throws IOException { + addCommonContextVars(model, request); + model.put("init", true); + model.put("success", false); + return AdminTool.ROOTCONTEXT + Constants.CONTENT_TPL_PATH + "resetPasswordRequest"; + } + + @RequestMapping(value="/password/request", method=RequestMethod.POST) + public String createResetPassword(@RequestParam("username") String username, + ModelMap model, HttpServletRequest request, HttpServletResponse response) throws IOException { + LOGGER.debug("receiving resetpassword/create request for user: " + username); + addCommonContextVars(model, request); + model.put("init", false); + try { + userService.createResetPassword(username, CommunicationProcess.RESET_PASSWORD_REQUEST_SELF); + model.put("success", true); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + model.put("success", false); + } + return AdminTool.ROOTCONTEXT + Constants.CONTENT_TPL_PATH + "resetPasswordRequest"; + } + + @RequestMapping(value="/password/{hash}", method=RequestMethod.GET) + public String resetPasswordCheck(@PathVariable("hash") String passwordLinkHash, ModelMap model, HttpServletRequest request, HttpServletResponse response) throws IOException { + LOGGER.debug("receiving /password/{hash} request for hash: " + passwordLinkHash); + ATUser user = userService.getUserForPasswordHash(passwordLinkHash); + if (null == user) { + LOGGER.warn(String.format("reset password for hash %s requested, but no user found", passwordLinkHash)); + response.sendRedirect(super.getRootContext(request) + LoginController.LOGIN_PATH); + } + + Period hashPeriod = properties.getUsers().getPasswordHashPeriod(); + LocalDateTime maxDate = user.getPasswordLinkCreated().plus(hashPeriod); + if (LocalDateTime.now().isAfter(maxDate)) { + LOGGER.warn(String.format("reset password for hash %s and user %s is expired", passwordLinkHash, user.getUsername())); + response.sendRedirect(super.getRootContext(request) + LoginController.LOGIN_PATH + "?error=hashToOld"); + } + + addCommonContextVars(model, request); + model.put("passwordLinkHash", passwordLinkHash); + model.put("success", false); + model.put("init", true); + return AdminTool.ROOTCONTEXT + Constants.CONTENT_TPL_PATH + "resetPassword"; + } + + @RequestMapping(value="/password/update", method=RequestMethod.POST) + public String resetPassword( + @RequestParam("passwordLinkHash")String passwordLinkHash, + @RequestParam("username") String username, + @RequestParam("password") String password, + @RequestParam("passwordConfirm")String passwordConfirm, + ModelMap model, HttpServletRequest request, HttpServletResponse response) throws IOException { + LOGGER.debug("receiving /password request for user " + username +" hash: " + passwordLinkHash); + ATUser userByHash = userService.getUserForPasswordHash(passwordLinkHash); + ATUser userByName = userService.getUser(username); + + if (null == userByHash || null == userByName || !userByHash.getUsername().equals(userByName.getUsername())) { + LOGGER.warn(String.format("reset password for hash %s and user %s is not equals given user name %s, ", + passwordLinkHash, userByHash.getUsername(), userByName.getUsername())); + response.sendRedirect(super.getRootContext(request) + LoginController.LOGIN_PATH+ "?error=systemAdmin"); + } + + addCommonContextVars(model, request); + model.put("passwordLinkHash", passwordLinkHash); + model.put("username", username); + model.put("init", false); + try { + Set errors = userValidator.validatePasswordReset(username, password, passwordConfirm); + + if (CollectionUtils.isEmpty(errors)) { + userService.resetPassword(username, password); + model.put("success", true); + } else { + model.put("success", false); + model.put("errors", errors); + } + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + model.put("success", false); + } + return AdminTool.ROOTCONTEXT + Constants.CONTENT_TPL_PATH + "resetPassword"; + } +} diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBRoleController.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBRoleController.java index 622e939..531be62 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBRoleController.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBRoleController.java @@ -14,12 +14,12 @@ import org.springframework.web.bind.annotation.ResponseBody; import de.chandre.admintool.core.AdminTool; -import de.chandre.admintool.core.controller.AbstractAdminController; import de.chandre.admintool.core.ui.ATError; import de.chandre.admintool.core.ui.select2.Select2GroupedTO; import de.chandre.admintool.security.dbuser.auth.AccessRelationTO; import de.chandre.admintool.security.dbuser.domain.ATRole; import de.chandre.admintool.security.dbuser.service.AdminToolSecDBRoleService; +import de.chandre.admintool.security.dbuser.service.validation.AdminToolSecDBRoleValidator; /** * Role controller @@ -29,15 +29,18 @@ */ @Controller @RequestMapping(AdminTool.ROOTCONTEXT + "/accessmanagement/role") -public class AdminToolSecDBRoleController extends AbstractAdminController { +public class AdminToolSecDBRoleController extends ATSecDBAbctractController { - private static final Log LOGGER = LogFactory.getLog(AdminToolSecDBRoleController.class); + private final Log LOGGER = LogFactory.getLog(AdminToolSecDBRoleController.class); @Autowired private AdminToolSecDBRoleService roleService; @Autowired private AdminToolSecDBTransformUtil transformUtil; + + @Autowired + private AdminToolSecDBRoleValidator validator; @RequestMapping(path="/get", method=RequestMethod.GET) @ResponseBody @@ -62,7 +65,12 @@ public String changeState(@PathVariable("name") String name) { @RequestMapping(path="/update", method=RequestMethod.POST) @ResponseBody public Set update(@RequestBody AccessRelationTO accessRelationTO) { - return roleService.updateRole(accessRelationTO); + try { + return roleService.updateRole(accessRelationTO); + } catch (Exception e) { + return handleException(e, LOGGER, validator, "error.update.role", "update.role.error", "Could not update Role"); + } + } } diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBUserController.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBUserController.java index 9731e50..e5769d7 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBUserController.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBUserController.java @@ -1,17 +1,12 @@ package de.chandre.admintool.security.dbuser.contoller; -import java.io.IOException; import java.util.Arrays; import java.util.Set; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; -import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -19,16 +14,16 @@ import org.springframework.web.bind.annotation.ResponseBody; import de.chandre.admintool.core.AdminTool; -import de.chandre.admintool.core.controller.AbstractAdminController; import de.chandre.admintool.core.ui.ATError; import de.chandre.admintool.core.utils.ReflectUtils; -import de.chandre.admintool.security.commons.LoginController; -import de.chandre.admintool.security.commons.TemplateUserService; import de.chandre.admintool.security.commons.auth.UserStateType; import de.chandre.admintool.security.commons.auth.UserTO; +import de.chandre.admintool.security.dbuser.Constants.CommunicationProcess; import de.chandre.admintool.security.dbuser.auth.ExtUserTO; import de.chandre.admintool.security.dbuser.domain.ATUser; import de.chandre.admintool.security.dbuser.service.AdminToolSecDBUserDetailsService; +import de.chandre.admintool.security.dbuser.service.comm.SendException; +import de.chandre.admintool.security.dbuser.service.validation.AdminToolSecDBUserValidator; /** * User controller @@ -38,26 +33,15 @@ */ @Controller @RequestMapping(AdminTool.ROOTCONTEXT + "/accessmanagement/user") -public class AdminToolSecDBUserController extends AbstractAdminController { +public class AdminToolSecDBUserController extends ATSecDBAbctractController { - private static final Log LOGGER = LogFactory.getLog(AdminToolSecDBUserController.class); + private final Log LOGGER = LogFactory.getLog(AdminToolSecDBUserController.class); @Autowired private AdminToolSecDBUserDetailsService userService; - + @Autowired - private TemplateUserService templateUserService; - - @RequestMapping(value="/profile") - public String profile(ModelMap model, HttpServletRequest request, HttpServletResponse response) throws IOException { - if (templateUserService.isAnonymous()) { - response.sendRedirect(super.getRootContext(request) + LoginController.LOGIN_PATH); - } - addCommonContextVars(model, request); - ATUser currentUser = templateUserService.getUserPrincipal(ATUser.class); - model.put("currentUser", userService.getUserForId(currentUser.getId())); - return AdminTool.ROOTCONTEXT_NAME + "/security/content/profile"; - } + private AdminToolSecDBUserValidator userValidator; @RequestMapping(value="/get/{userId}", method=RequestMethod.GET) @ResponseBody @@ -83,13 +67,21 @@ public String changeUserState(@RequestBody UserTO userTo, @PathVariable("type") @ResponseBody public Set addUser(@RequestBody UserTO userTo) { - return userService.createUser(userTo); + try { + return userService.createUser(userTo); + } catch (SendException e) { + return handleException(e, LOGGER, userValidator, "error.create.user", "create.user.error", "Could not create user"); + } } @RequestMapping(value="/update", method=RequestMethod.POST) @ResponseBody public Set updateUser(@RequestBody UserTO userTo) { - return userService.updateUser(userTo); + try { + return userService.updateUser(userTo); + } catch (Exception e) { + return handleException(e, LOGGER, userValidator, "error.update.user", "update.user.error", "Could not update user"); + } } @RequestMapping(value="/remove", method=RequestMethod.POST) @@ -104,6 +96,18 @@ public String removeUser(@RequestBody UserTO userTo) { return Boolean.TRUE.toString(); } + @RequestMapping(value="/resetPassword", method=RequestMethod.POST) + @ResponseBody + public String resetPassword(@RequestBody UserTO userTo) { + try { + userService.createResetPassword(userTo.getUsername(), CommunicationProcess.RESET_PASSWORD_REQUEST_ADMIN); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + return Boolean.FALSE.toString(); + } + return Boolean.TRUE.toString(); + } + protected String setUserState(UserTO userTo, String type) { return setUserState(userTo, UserStateType.fromType(type)); } diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBUserGroupController.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBUserGroupController.java index 8c7cfdb..feddcc6 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBUserGroupController.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/contoller/AdminToolSecDBUserGroupController.java @@ -14,12 +14,12 @@ import org.springframework.web.bind.annotation.ResponseBody; import de.chandre.admintool.core.AdminTool; -import de.chandre.admintool.core.controller.AbstractAdminController; import de.chandre.admintool.core.ui.ATError; import de.chandre.admintool.core.ui.select2.Select2GroupedTO; import de.chandre.admintool.security.dbuser.auth.AccessRelationTO; import de.chandre.admintool.security.dbuser.domain.ATUserGroup; import de.chandre.admintool.security.dbuser.service.AdminToolSecDBUserGroupService; +import de.chandre.admintool.security.dbuser.service.validation.AdminToolSecDBUserGroupValidator; /** * UserGroup Controller @@ -29,9 +29,9 @@ */ @Controller @RequestMapping(AdminTool.ROOTCONTEXT + "/accessmanagement/usergroup") -public class AdminToolSecDBUserGroupController extends AbstractAdminController { +public class AdminToolSecDBUserGroupController extends ATSecDBAbctractController { - private static final Log LOGGER = LogFactory.getLog(AdminToolSecDBUserGroupController.class); + private final Log LOGGER = LogFactory.getLog(AdminToolSecDBUserGroupController.class); @Autowired private AdminToolSecDBUserGroupService userGroupService; @@ -39,6 +39,9 @@ public class AdminToolSecDBUserGroupController extends AbstractAdminController { @Autowired private AdminToolSecDBTransformUtil transformUtil; + @Autowired + private AdminToolSecDBUserGroupValidator validator; + @RequestMapping(path="/get", method=RequestMethod.GET) @ResponseBody public Select2GroupedTO getUserGroups() { @@ -62,13 +65,21 @@ public String changeState(@PathVariable("name") String name) { @RequestMapping(path="/update", method=RequestMethod.POST) @ResponseBody public Set update(@RequestBody AccessRelationTO accessRelationTO) { - return userGroupService.updateUserGroup(accessRelationTO); + try { + return userGroupService.updateUserGroup(accessRelationTO); + } catch (Exception e) { + return handleException(e, LOGGER, validator, "error.update.group", "update.group.error", "Could not update UserGroup"); + } } @RequestMapping(path="/add", method=RequestMethod.POST) @ResponseBody public Set add(@RequestBody AccessRelationTO accessRelationTO) { - return userGroupService.addUserGroup(accessRelationTO); + try { + return userGroupService.addUserGroup(accessRelationTO); + } catch (Exception e) { + return handleException(e, LOGGER, validator, "error.add.group", "add.group.error", "Could not add UserGroup"); + } } @RequestMapping(path="/remove/name/{name}", method=RequestMethod.POST) diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/domain/ATUser.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/domain/ATUser.java index 9a7cd55..31ed2b5 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/domain/ATUser.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/domain/ATUser.java @@ -42,8 +42,6 @@ public class ATUser extends AbstractEntity implements User, Constants { @Column(name="USERNAME", nullable=false, unique=true) private String username; - @NotNull(message = MSG_KEY_PREFIX + "user.password.NotNull") - @Size(min = 1, message = MSG_KEY_PREFIX + "user.password.Size") @Column(name="PASSWORD", nullable=false) private String password; @@ -375,7 +373,13 @@ public String getPasswordLinkHash() { } public void setPasswordLinkHash(String passwordLinkHash) { - this.passwordLinkHash = passwordLinkHash; + if (null != passwordLinkHash) { + this.passwordLinkHash = passwordLinkHash; + this.passwordLinkCreated = LocalDateTime.now(); + } else { + this.passwordLinkHash = null; + this.passwordLinkCreated = null; + } } @Override diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/repo/UserRepository.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/repo/UserRepository.java index 39416d1..157abd4 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/repo/UserRepository.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/repo/UserRepository.java @@ -15,5 +15,7 @@ public interface UserRepository extends JpaRepository { ATUser findByUsername(String username); + ATUser findByPasswordLinkHash(String passwordLinkHash); + void deleteByUsername(String username); } diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/AdminToolSecDBUserDetailsService.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/AdminToolSecDBUserDetailsService.java index b44706b..83212ad 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/AdminToolSecDBUserDetailsService.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/AdminToolSecDBUserDetailsService.java @@ -5,7 +5,9 @@ import de.chandre.admintool.core.ui.ATError; import de.chandre.admintool.security.commons.auth.AdminToolUserDetailsService; import de.chandre.admintool.security.commons.auth.UserTO; +import de.chandre.admintool.security.dbuser.Constants.CommunicationProcess; import de.chandre.admintool.security.dbuser.domain.ATUser; +import de.chandre.admintool.security.dbuser.service.comm.SendException; /** * extend interface for DB UserDetails service @@ -18,6 +20,8 @@ public interface AdminToolSecDBUserDetailsService extends AdminToolUserDetailsSe ATUser getUser(String username); ATUser getUserForId(String userid); + + ATUser getUserForPasswordHash(String passwordHash); ATUser saveUser(ATUser user); @@ -43,10 +47,40 @@ public interface AdminToolSecDBUserDetailsService extends AdminToolUserDetailsSe ATUser addCients(ATUser user, Set clientNames); - Set createUser(UserTO userTO); + Set createUser(UserTO userTO) throws SendException; Set updateUser(UserTO userTO); void removeByName(String username); + /** + * updates the user profile with given information
    + * password, usergroups/authorities and clients will be ignored! + * @param userTO + * @return + */ + Set updateProfile(UserTO userTO); + + /** + * sets the new password but not changes user states + * + * @param username the username + * @param password the new password + */ + void updatePassword(String username, String password); + + /** + * creates an new UUID for passwordLinkHash and saves user + * @param username + * @param process/user who initializes the request + */ + void createResetPassword(String username, CommunicationProcess process) throws SendException; + + /** + * sets the new password and enables the user + * @param username the username + * @param password the new password + */ + void resetPassword(String username, String password); + } diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/AdminToolSecDBUserDetailsServiceImpl.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/AdminToolSecDBUserDetailsServiceImpl.java index fce0a26..cfb2c73 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/AdminToolSecDBUserDetailsServiceImpl.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/AdminToolSecDBUserDetailsServiceImpl.java @@ -9,6 +9,8 @@ import java.util.Set; import java.util.TimeZone; +import javax.annotation.PostConstruct; + import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -24,7 +26,9 @@ import de.chandre.admintool.core.ui.ATError; import de.chandre.admintool.security.commons.auth.LoginAttemptService; import de.chandre.admintool.security.commons.auth.UserTO; +import de.chandre.admintool.security.dbuser.AdminToolSecDBProperties; import de.chandre.admintool.security.dbuser.Constants; +import de.chandre.admintool.security.dbuser.Constants.CommunicationProcess; import de.chandre.admintool.security.dbuser.domain.ATClient; import de.chandre.admintool.security.dbuser.domain.ATUser; import de.chandre.admintool.security.dbuser.domain.ATUserGroup; @@ -32,6 +36,8 @@ import de.chandre.admintool.security.dbuser.repo.ClientRepository; import de.chandre.admintool.security.dbuser.repo.UserGroupRepository; import de.chandre.admintool.security.dbuser.repo.UserRepository; +import de.chandre.admintool.security.dbuser.service.comm.AdminToolSecDBCommunicator; +import de.chandre.admintool.security.dbuser.service.comm.SendException; import de.chandre.admintool.security.dbuser.service.validation.AdminToolSecDBUserValidator; /** @@ -45,6 +51,9 @@ public class AdminToolSecDBUserDetailsServiceImpl implements AdminToolSecDBUserD private static final Log LOGGER = LogFactory.getLog(AdminToolSecDBUserDetailsServiceImpl.class); + @Autowired + private AdminToolSecDBProperties properties; + @Autowired private UserRepository userRepository; @@ -63,9 +72,23 @@ public class AdminToolSecDBUserDetailsServiceImpl implements AdminToolSecDBUserD @Autowired(required=false) private PasswordEncoder passwordEncoder; + @Autowired(required=false) + private AdminToolSecDBCommunicator communicator; + + @Autowired(required=false) + private PasswordLinkHashGenerator passwordLinkHashGenerator; + private String infoMessage; private int maxLoginAttempts = 5; + + @PostConstruct + private void init() { + if (null == passwordLinkHashGenerator) { + LOGGER.debug("no passwordLinkHashGenerator found, using default (UUID)"); + this.passwordLinkHashGenerator = new DefaultPasswordLinkHashGenerator(); + } + } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { @@ -94,6 +117,11 @@ public ATUser getUser(String username) { public ATUser getUserForId(String userid) { return userRepository.getOne(userid); } + + @Override + public ATUser getUserForPasswordHash(String passwordHash) { + return userRepository.findByPasswordLinkHash(passwordHash); + } @Override public String getInfoMessage() { @@ -111,11 +139,13 @@ public Collection getUsers() { } @Override + @Transactional public ATUser saveUser(ATUser user) { return this.userRepository.saveAndFlush(user); } @Override + @Transactional public ATUser saveUser(ATUser user, boolean encodePassword) { if (!encodePassword) { return saveUser(user); @@ -128,11 +158,13 @@ public ATUser saveUser(ATUser user, boolean encodePassword) { } @Override + @Transactional public void setUserName(String currentUsername, String newUserName) { setUserName(getUser(currentUsername), newUserName); } @Override + @Transactional public ATUser setUserName(ATUser currentUser, String newUserName) { if (!currentUser.getUsername().equals(newUserName)) { currentUser.setUsername(newUserName); @@ -142,11 +174,13 @@ public ATUser setUserName(ATUser currentUser, String newUserName) { } @Override + @Transactional public void setUserLocked(String username, boolean locked) { setUserLocked(getUser(username), locked); } @Override + @Transactional public ATUser setUserLocked(ATUser currentUser, boolean locked) { if (locked != currentUser.isAccountLocked()) { if (locked) { @@ -161,11 +195,13 @@ public ATUser setUserLocked(ATUser currentUser, boolean locked) { } @Override + @Transactional public void setUserExpired(String username, boolean expired) { setUserExpired(getUser(username), expired); } @Override + @Transactional public ATUser setUserExpired(ATUser currentUser, boolean expired) { if (expired != currentUser.isAccountExpired()) { if (expired) { @@ -180,11 +216,13 @@ public ATUser setUserExpired(ATUser currentUser, boolean expired) { } @Override + @Transactional public void setUserEnabled(String username, boolean enabled) { setUserEnabled(getUser(username), enabled); } @Override + @Transactional public ATUser setUserEnabled(ATUser currentUser, boolean enabled) { currentUser.setEnabled(enabled); currentUser = this.userRepository.saveAndFlush(currentUser); @@ -192,11 +230,13 @@ public ATUser setUserEnabled(ATUser currentUser, boolean enabled) { } @Override + @Transactional public void setUserCredentialsExpired(String username, boolean credentialsExpired) { setUserCredentialsExpired(getUser(username), credentialsExpired); } @Override + @Transactional public ATUser setUserCredentialsExpired(ATUser currentUser, boolean credentialsExpired) { if (credentialsExpired != currentUser.isCredentialsExpired()) { currentUser.setCredentialsNonExpired(!credentialsExpired); @@ -211,33 +251,44 @@ public ATUser setUserCredentialsExpired(ATUser currentUser, boolean credentialsE return currentUser; } - private Set setAndValidateAndSave(UserTO userTO, ATUser user, boolean validatePassword) { + private Set setAndValidateAndSave(UserTO userTO, ATUser user, boolean validatePassword, boolean updateUserGroups, boolean updateClients) { user.setEmail(StringUtils.trimToNull(userTO.getEmail())); user.setPhone(StringUtils.trimToNull(userTO.getPhone())); user.setFirstName(StringUtils.trimToNull(userTO.getFirstName())); user.setLastName(StringUtils.trimToNull(userTO.getLastName())); - if(!CollectionUtils.isEmpty(userTO.getAuthorities())) { - user.setUserGroups(this.userGroupRepository.findByNameIn(userTO.getAuthorities())); - } - if(!CollectionUtils.isEmpty(userTO.getClients())) { - user.setClients(this.clientRepository.findByNameIn(userTO.getClients())); - } - if (user.getTimeZone() == null) { - if (StringUtils.isBlank(userTO.getTimeZone())) { - user.setTimeZone(TimeZone.getDefault()); + //setting user groups + if (updateUserGroups) { + if(!CollectionUtils.isEmpty(userTO.getAuthorities())) { + user.setUserGroups(this.userGroupRepository.findByNameIn(userTO.getAuthorities())); } else { - user.setTimeZone(userTO.getTimeZone()); + user.getUserGroups().clear(); } } - if (user.getLocale() == null) { - if (StringUtils.isBlank(userTO.getLocale())) { - user.setLocale(LocaleContextHolder.getLocale()); + + //setting clients + if (updateClients) { + if(!CollectionUtils.isEmpty(userTO.getClients())) { + user.setClients(this.clientRepository.findByNameIn(userTO.getClients())); } else { - user.setLocale(userTO.getLocale()); + user.getClients().clear(); } } + //setting time zone + if (StringUtils.isBlank(userTO.getTimeZone()) && user.getTimeZone() == null) { + user.setTimeZone(TimeZone.getDefault()); + } else if (StringUtils.isNotBlank(userTO.getTimeZone())){ + user.setTimeZone(userTO.getTimeZone()); + } + + // setting locale + if (StringUtils.isBlank(userTO.getLocale()) && user.getLocale() == null) { + user.setLocale(LocaleContextHolder.getLocale()); + } else if(StringUtils.isNotBlank(userTO.getLocale())) { + user.setLocale(userTO.getLocale()); + } + Set errors = Collections.emptySet(); if (validator != null) { errors = validator.validate(user, validatePassword); @@ -256,7 +307,8 @@ private Set setAndValidateAndSave(UserTO userTO, ATUser user, boolean v } @Override - public Set createUser(UserTO userTO) { + @Transactional + public Set createUser(UserTO userTO) throws SendException { Set errors = null; if (null != getUser(StringUtils.trimToNull(userTO.getUsername()))) { errors = new HashSet<>(); @@ -268,12 +320,25 @@ public Set createUser(UserTO userTO) { if (LOGGER.isDebugEnabled()) { LOGGER.debug(String.format("creating user: %s", user.getUsername())); } + boolean validatePassword = true; + //if set password is not allowed .. set hash-code and send email afterwards + if (!properties.getUsers().isDirectPasswordChangeAllowed()) { + user.setPasswordLinkHash(this.passwordLinkHashGenerator.generatePasswordLinkHash()); + user.expireCredentials(); + communicator.sendResetedPasswordNotice(CommunicationProcess.CREATE_USER, user.getUsername(), user.getEmail(), user.getPhone(), user.getPasswordLinkHash()); + validatePassword = false; + } - return setAndValidateAndSave(userTO, user, true); + return setAndValidateAndSave(userTO, user, validatePassword, true, true); } @Override + @Transactional public Set updateUser(UserTO userTO) { + return updateUser(userTO, true, true); + } + + public Set updateUser(UserTO userTO, boolean updateUserGroups, boolean updateClients) { Set errors = null; ATUser user = getUser(userTO.getUsername()); if (null == user) { @@ -287,20 +352,62 @@ public Set updateUser(UserTO userTO) { } boolean passwordChanged= false; - if (StringUtils.isNotEmpty(userTO.getPassword())) { + if (StringUtils.isNotBlank(userTO.getPassword())) { user.setPassword(userTO.getPassword()); passwordChanged = true; } - return setAndValidateAndSave(userTO, user, passwordChanged); + return setAndValidateAndSave(userTO, user, passwordChanged, updateUserGroups, updateClients); + } + + @Override + @Transactional + public Set updateProfile(UserTO userTO) { + //securing unwanted profile changes + userTO.setPassword(null); + userTO.setAuthorities(null); + userTO.setClients(null); + return updateUser(userTO, false, false); + } + + @Override + @Transactional + public void updatePassword(String username, String password) { + ATUser user = getUser(username); + user.setPassword(password); + saveUser(user, true); + } + + @Override + @Transactional + public void resetPassword(String username, String password) { + ATUser user = getUser(username); + user.setPassword(password); + user.setEnabled(true); + saveUser(user, true); + } + + @Override + @Transactional + public void createResetPassword(String username, CommunicationProcess process) throws SendException { + ATUser user = getUser(username); + if (null == user) { + throw new UsernameNotFoundException("User with name " + username + " not found"); + } + user.setPasswordLinkHash(this.passwordLinkHashGenerator.generatePasswordLinkHash()); + user.expireCredentials(); + saveUser(user); + communicator.sendResetedPasswordNotice(process, user.getUsername(), user.getEmail(), user.getPhone(), user.getPasswordLinkHash()); } @Override + @Transactional public void removeByName(String username) { this.userRepository.deleteByUsername(username); } @Override + @Transactional public ATUser addUserGroups(ATUser user, Set groupNames) { List groups = this.userGroupRepository.findByNameIn(groupNames); user.getUserGroups().addAll(groups); @@ -308,6 +415,7 @@ public ATUser addUserGroups(ATUser user, Set groupNames) { } @Override + @Transactional public ATUser addCients(ATUser user, Set clientNames) { List groups = this.clientRepository.findByNameIn(clientNames); user.getClients().addAll(groups); @@ -315,6 +423,7 @@ public ATUser addCients(ATUser user, Set clientNames) { } @Override + @Transactional public ATUser removeUserGroups(ATUser user, Set groupNames) { Iterator userGroupsIter = user.getUserGroups().iterator(); while (userGroupsIter.hasNext()) { @@ -349,6 +458,7 @@ public int getMaxLoginAttempts() { } @Override + @Transactional public void loginFailed(String username) { LOGGER.info("registering login attempt"); ATUser user = getUser(username); @@ -362,6 +472,7 @@ public void loginFailed(String username) { } @Override + @Transactional public void loginSuccess(String username) { ATUser user = getUser(username); if (user.getLastLoginAttempt() != null diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/DefaultPasswordLinkHashGenerator.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/DefaultPasswordLinkHashGenerator.java new file mode 100644 index 0000000..f437a33 --- /dev/null +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/DefaultPasswordLinkHashGenerator.java @@ -0,0 +1,18 @@ +package de.chandre.admintool.security.dbuser.service; + +import java.util.UUID; + +/** + * uses the UUID to generate a unique non-reproducible string + * @author André + * @since 1.1.7 + * + */ +public class DefaultPasswordLinkHashGenerator implements PasswordLinkHashGenerator { + + @Override + public String generatePasswordLinkHash() { + return UUID.randomUUID().toString(); + } + +} diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/PasswordLinkHashGenerator.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/PasswordLinkHashGenerator.java new file mode 100644 index 0000000..470bd34 --- /dev/null +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/PasswordLinkHashGenerator.java @@ -0,0 +1,16 @@ +package de.chandre.admintool.security.dbuser.service; + +/** + * + * @author André + * @since 1.1.7 + * + */ +public interface PasswordLinkHashGenerator { + + /** + * should return a unique string (hash or UUID) to identify a user when password reset request has been executed + * @return a String + */ + String generatePasswordLinkHash(); +} diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/comm/AdminToolSecDBCommunicator.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/comm/AdminToolSecDBCommunicator.java new file mode 100644 index 0000000..c6e61d6 --- /dev/null +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/comm/AdminToolSecDBCommunicator.java @@ -0,0 +1,24 @@ +/** + * + */ +package de.chandre.admintool.security.dbuser.service.comm; + +import de.chandre.admintool.security.dbuser.Constants.CommunicationProcess; + +/** + * @author André + * @since 1.1.7 + * + */ +public interface AdminToolSecDBCommunicator { + + /** + * should send a notice to the user that password has been reseted and he/she must visit a special site to set a new one + * @param username + * @param email + * @param phone + * @param passwordLinkHash + * @throws SendException + */ + void sendResetedPasswordNotice(CommunicationProcess process, String username, String email, String phone, String passwordLinkHash) throws SendException; +} diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/comm/SendException.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/comm/SendException.java new file mode 100644 index 0000000..6a78ddb --- /dev/null +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/comm/SendException.java @@ -0,0 +1,28 @@ +package de.chandre.admintool.security.dbuser.service.comm; + +/** + * + * @author André + * @since 1.1.7 + * + */ +public class SendException extends Exception { + private static final long serialVersionUID = 6773428392816080970L; + + public SendException() { + super(); + } + + public SendException(String message) { + super(message); + } + + public SendException(Throwable cause) { + super(cause); + } + + public SendException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/validation/AbstractValidator.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/validation/AbstractValidator.java index ccc3e28..52c622e 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/validation/AbstractValidator.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/validation/AbstractValidator.java @@ -137,10 +137,20 @@ public void validate(String value, Validations validations, String fieldName, Se } if (value.length() < validations.getMinLength()) { - errors.add(new ATError(errorCode, getMessage(errorCode + ".minLength", null, fieldName + " is to short min-length is " + validations.getMinLength()), fieldName)); + errors.add(new ATError(errorCode, + getMessage( + errorCode + ".minLength", + new Object[] {validations.getMinLength()}, + fieldName + " is to short min-length is " + validations.getMinLength()), + fieldName)); } - if (value.length() > validations.getMaxLength()) { - errors.add(new ATError(errorCode, getMessage(errorCode + ".maxLength", null, fieldName + " is to long. max-length is " + validations.getMaxLength()), fieldName)); + if (validations.getMaxLength() > 0 && value.length() > validations.getMaxLength()) { + errors.add(new ATError(errorCode, + getMessage( + errorCode + ".maxLength", + new Object[] {validations.getMaxLength()}, + fieldName + " is to long. max-length is " + validations.getMaxLength()), + fieldName)); } if (null != validations.getPattern() && !validations.getPattern().matcher(value).matches()) { errors.add(new ATError(errorCode, getMessage(errorCode + ".patternMismatch", null, fieldName + " doesn't matches the required pattern"), fieldName)); diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/validation/AdminToolSecDBUserValidator.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/validation/AdminToolSecDBUserValidator.java index ea9aa86..76ae6e0 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/validation/AdminToolSecDBUserValidator.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/validation/AdminToolSecDBUserValidator.java @@ -12,5 +12,34 @@ */ public interface AdminToolSecDBUserValidator extends ATSecDBValidator { + /** + * user validation against configured rules + * + * @param user + * @param validatePassword + * @return + */ Set validate(User user, boolean validatePassword); + + /** + * validates the user and current password against AuthenticationManager and the new passwords against its validation rules + * + * @param userName + * @param currentPassword + * @param newPassword + * @param confirmPassword + * @return + */ + Set validatePasswordChange(String userName, String currentPassword, String newPassword, + String confirmPassword); + + /** + * just validates the passwords against its validation rules + * + * @param username + * @param newPassword + * @param confirmPassword + * @return + */ + Set validatePasswordReset(String username, String newPassword, String confirmPassword); } diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/validation/AdminToolSecDBUserValidatorImpl.java b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/validation/AdminToolSecDBUserValidatorImpl.java index d8a1ea9..1d8220d 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/validation/AdminToolSecDBUserValidatorImpl.java +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/java/de/chandre/admintool/security/dbuser/service/validation/AdminToolSecDBUserValidatorImpl.java @@ -9,6 +9,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -34,6 +37,9 @@ public class AdminToolSecDBUserValidatorImpl extends AbstractValidator imp @Autowired(required=false) private List> interceptors; + @Autowired + private AuthenticationManager authManager; + @PostConstruct private void init() { super.sortInterceptors(interceptors); @@ -69,4 +75,38 @@ public Set validate(User user, boolean validatePassword) { return errors; } + + @Override + public Set validatePasswordChange(String username, String currentPassword, String newPassword, String confirmPassword) { + + LOGGER.trace("start password validation for user: " + username != null ? username : "null-object"); + + Set errors = new HashSet<>(); + UsernamePasswordAuthenticationToken authReq = new UsernamePasswordAuthenticationToken(username, currentPassword); + Authentication auth = authManager.authenticate(authReq); + if (!auth.isAuthenticated()) { + errors.add(new ATError("currentPassword.wrong", getMessageWithSuffix("currentPassword.wrong", null, "Current password is wrong"), "currentPassword")); + return errors; + } + + if (null != newPassword && !newPassword.equals(confirmPassword)) { + errors.add(new ATError("confirmPassword.wrong", getMessageWithSuffix("confirmPassword.wrong", null, "Password confirmation is not equals new password."), "confirmPassword")); + } + + validate(newPassword, properties.getUsers().getPassword(), "newPassword", errors); + + return errors; + } + + @Override + public Set validatePasswordReset(String username, String newPassword, String confirmPassword) { + LOGGER.trace("start password validation for user: " + username != null ? username : "null-object"); + Set errors = new HashSet<>(); + if (null != newPassword && !newPassword.equals(confirmPassword)) { + errors.add(new ATError("confirmPassword.wrong", getMessageWithSuffix("confirmPassword.wrong", null, "Password confirmation is not equals new password."), "confirmPassword")); + } + + validate(newPassword, properties.getUsers().getPassword(), "newPassword", errors); + return errors; + } } diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/css/accessmanagement.css b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/css/accessmanagement.css index 323d9b0..d555770 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/css/accessmanagement.css +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/css/accessmanagement.css @@ -1,3 +1,11 @@ -input#userPassword { +input#userPassword, input#currentPassword, input#newPassword, input#password { font-family: monospace; +} + +.spacer5right { + margin-right: 5px; +} + +.spacer2left { + margin-left: 2px; } \ No newline at end of file diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/accessmanagement.js b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/accessmanagement.js index 3a8cf0e..2c76be2 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/accessmanagement.js +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/accessmanagement.js @@ -32,15 +32,37 @@ $.extend(AdminTool.AccessManagement.prototype, { this.switchFormElements(true, additionSelector, exceptionList); }, - switchFormElements: function(enable, additionSelector='', exceptionList=[]) { - $(additionSelector + ' .form-control').each(function() { + switchFormElements: function(disable, additionSelector='', exceptionList=[]) { + $(additionSelector).find("input, select, textarea").each(function() { var $thisElem = $(this); if (exceptionList.indexOf($thisElem.attr('id')) == -1) { - $thisElem.prop("disabled", enable); + $thisElem.prop("disabled", disable); } }); }, + generatePassword: function(fieldId) { + var pwdField = getByID(fieldId); + pwdField.val(this.passwordGen.generatePass()); + if(pwdField.attr('type') == 'password') { + this.switchPasswordVisibility(fieldId) + } + }, + + switchPasswordVisibility: function(fieldId, addFuction=null) { + var pwd = getByID(fieldId); + if (pwd.attr('type') == 'password') { + pwd.attr('type', 'text'); + } else { + pwd.attr('type', 'password'); + } + getByID(fieldId).parent().find('.eye-switch').switchClass('fa-eye', 'fa-eye-slash'); + + if (addFuction && typeof addFuction === 'function') { + addFuction(pwd.attr('type'), this); + } + }, + show: function(id) { var $toShow = getByID(id); if ($toShow && $toShow.length > 0) { diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/accessrelation.js b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/accessrelation.js index b120f8a..de4cfb8 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/accessrelation.js +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/accessrelation.js @@ -235,7 +235,7 @@ $.extend(AdminTool.AccessRelation.prototype, { } else { //show error modal (AdminTool.Core) query.ctx.showErrorModal('Error saving Relation', data); - query.ctx.validationUtil.showFieldErrorsOnATErrorList(data); + query.ctx.validationUtil.showFieldErrorsOnATErrorList(data, query.ctx.relationDataFormId); } }); diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/passwordgen.js b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/passwordgen.js new file mode 100644 index 0000000..f77eb43 --- /dev/null +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/passwordgen.js @@ -0,0 +1,52 @@ +AdminTool.PasswordGenerator = function(parent) { + var self = this; + + this.construct = function(parent) { + this.parent = parent; + this.options = {}; + $.extend(true, this.options, parent.options); + }; + + this.generatePass = function (plength) { + + if (!plength && self.options.hasOwnProperty('passwordLength')) { + plength = self.options.passwordLength; + } + if (!plength) { + plength = 12; + } + + var keylistalpha="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMOPQRSTUVWXYZ"; + var keylistint="1234567890"; + var keylistspec="!@#_$.-"; + var temp=''; + var len = plength/2; + var len = len - 1; + var lenspec = plength-len-len; + + for (i=0;i 0; i--) { + j = Math.floor(Math.random() * (i + 1)); + x = a[i]; + a[i] = a[j]; + a[j] = x; + } + return a; + }; + + this.construct(parent); +}; \ No newline at end of file diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/profile.js b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/profile.js new file mode 100644 index 0000000..279f488 --- /dev/null +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/profile.js @@ -0,0 +1,221 @@ + +AdminTool.Profile = function(el, options) { + if (el) { + this.init(el, options) + } +} +AdminTool.Profile.prototype = new AdminTool.AccessManagement(); + +$.extend(AdminTool.Profile.prototype, { + + name : 'profile', + + postInit: function() { + + this.options = $.extend( this.options, { + updateProfileURL : '/admintool/accessmanagement/user/profile/update', + changePasswordURL : '/admintool/accessmanagement/user/profile/password/update', + resetPasswordURL : '/admintool/accessmanagement/user/profile/password/reset' + }); + + this.validationUtil = new AdminTool.ValidationUtil(this); + this.profileDataFormId = '#profileDataForm'; + this.passwordDataFormId = '#passwordDataForm'; + + this.select2Util = new AdminTool.Select2Util(this); + + this.passwordGen = new AdminTool.PasswordGenerator(this); + + this.initEditUser(); + this.initResetPassword(); + this.initChangePassword(); + }, + + /* + * change user profile + */ + + initEditUser: function() { + getByID('editProfile').on('click', $.proxy(this.prepareEditProfile, this)); + }, + + prepareEditProfile: function() { + + this.validationUtil.reloadValidator(this.profileDataFormId); + + this.select2Util.initClassicSelect('#locale', '#profileDataModal'); + this.select2Util.initClassicSelect('#timeZone', '#profileDataModal'); + + getByID('saveProfile').off(); + getByID('saveProfile').on('click', $.proxy(this.saveProfile, this)); + getByID('profileDataModal').modal('show'); + }, + + saveProfile: function() { + var hasErrors = this.validationUtil.validate(this.profileDataFormId); + if (hasErrors) { + return; + } + + //build request and send + var userData = { + "firstName" : getByID('firstName').val(), + "lastName" : getByID('lastName').val(), + "email" : getByID('email').val(), + "phone" : getByID('phone').val(), + "timeZone" : getByID('timeZone').val(), + "locale" : getByID('locale').val(), + }; + + this.sendRequest({ + url: this.options.updateProfileURL, + requestType: "POST", + dataType: "json", + data: JSON.stringify(userData), + showModalOnError: true, + ctx: this + }, + function(data, query) { + if (data && Array.isArray(data) && data.length == 0) { + location.reload(); + } else { + //show error modal (AdminTool.Core) + query.ctx.showErrorModal('Error saving Profile', data); + query.ctx.validationUtil.showFieldErrorsOnATErrorList(data, query.ctx.profileDataFormId); + } + }); + }, + + /* + * Reset Password + */ + + initResetPassword: function() { + var respwbtn = getByID('resetPassword'); + if (respwbtn.length > 0) { + respwbtn.on('click', $.proxy(this.resetPasswordConfirm, this, respwbtn)); + } + }, + + resetPasswordConfirm: function(btn) { + this.showConfirmModal("Reset Password", "Do you really want to reset your password?", this.resetPassword, btn); + }, + + resetPassword: function(btn) { + this.sendRequest({ + url: this.options.resetPasswordURL, + requestType: "POST", + dataType: "text", + showModalOnError: true, + ctx: this + }, + function(data, query) { + if (!data || data == 'false') { + query.ctx.showErrorModal('Error', 'Error creating reset password Request'); + } + }); + }, + + /* + * Change password + */ + + initChangePassword: function() { + var chpwbtn = getByID('changePassword'); + if (chpwbtn.length > 0) { + chpwbtn.on('click', $.proxy(this.showChangePasswordModal, this)); + } + }, + + initPasswordButtons : function() { + var genPwdButton = getByID('generatePassword'); + genPwdButton.off(); + genPwdButton.on('click', $.proxy(this.generatePassword, this, 'newPassword')); + genPwdButton.parent().show(); + + var showPwdButton = getByID('showNewPassword'); + showPwdButton.off(); + showPwdButton.on('click', $.proxy(this.switchPasswordVisibility, this, 'newPassword', function(fieldtype, ctx) { + //if new password will be shown, we can hide confirm password + var cfmPwdField = getByID('passwordConfirm'); + if (fieldtype=='text') { + getByClazz('passwordConfirmRow').hide(); + cfmPwdField.prop('required', false); + } else { + getByClazz('passwordConfirmRow').show(); + cfmPwdField.attr('required', 'required'); + } + ctx.validationUtil.reloadValidator(ctx.passwordDataFormId); + })); + showPwdButton.parent().show(); + + var showPwdButton = getByID('showCurrentPassword'); + showPwdButton.off(); + showPwdButton.on('click', $.proxy(this.switchPasswordVisibility, this, 'currentPassword')); + showPwdButton.parent().show(); + }, + + showChangePasswordModal: function() { + getByID('currentPassword').val(""); + getByID('newPassword').val(""); + getByID('passwordConfirm').val(""); + + this.initPasswordButtons(); + + this.validationUtil.reloadValidator(this.passwordDataFormId); + + getByID('savePassword').off(); + getByID('savePassword').on('click', $.proxy(this.savePassword, this)); + getByID('passwordDataModal').modal('show'); + getByID('currentPassword').focus(); + }, + + savePassword: function() { + var hasErrors = this.validationUtil.validate(this.passwordDataFormId); + if (hasErrors) { + return; + } + var newPwdField = getByID('newPassword'); + var newPassword = newPwdField.val(); + var passwordConfirm = getByID('passwordConfirm').val(); + if (newPwdField.attr('type') == 'password' && newPassword != passwordConfirm) { + this.validationUtil.showCustomError('#passwordConfirm', 'Confirmation password is not equals new password', this.passwordDataFormId); + return; + } else { + passwordConfirm = newPassword; + } + + var data = { + 'currentPassword' : getByID('currentPassword').val(), + 'newPassword' : newPassword, + 'passwordConfirm' : passwordConfirm, + }; + + this.sendRequest({ + url: this.options.changePasswordURL, + requestType: "POST", + dataType: "json", + data: JSON.stringify(data), + showModalOnError: true, + ctx: this + }, + function(data, query) { + if (data && Array.isArray(data) && data.length == 0) { + location.reload(); + } else { + //show error modal (AdminTool.Core) + query.ctx.showErrorModal('Error saving User', data); + query.ctx.validationUtil.showFieldErrorsOnATErrorList(data, query.ctx.passwordDataFormId); + } + }); + } +}); + +$.pluginMaker(AdminTool.Profile); + + +$( document ).ready(function() { + if ($("#currentUserProfile").length > 0) { + $("#currentUserProfile").profile(); + } +}); diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/resetPassword.js b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/resetPassword.js new file mode 100644 index 0000000..a551a15 --- /dev/null +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/resetPassword.js @@ -0,0 +1,49 @@ + +AdminTool.ResetPassword = function(el, options) { + if (el) { + this.init(el, options) + } +} +AdminTool.ResetPassword.prototype = new AdminTool.AccessManagement(); + +$.extend(AdminTool.ResetPassword.prototype, { + + name : 'resetPassword', + + postInit: function() { + + this.options = $.extend( this.options, { + passwordLength : 14 + }); + + this.validationUtil = new AdminTool.ValidationUtil(this); + this.formId = '#profileDataForm'; + this.validationUtil.create(this.formId); + + this.passwordGen = new AdminTool.PasswordGenerator(this); + + this.initButtons(); + }, + + initButtons : function() { + var genPwdButton = getByID('generatePassword'); + genPwdButton.off(); + genPwdButton.on('click', $.proxy(this.generatePassword, this, 'password')); + genPwdButton.parent().show(); + + var showPwdButton = getByID('showPassword'); + showPwdButton.off(); + showPwdButton.on('click', $.proxy(this.switchPasswordVisibility, this, 'password')); + showPwdButton.parent().show(); + } + +}); + +$.pluginMaker(AdminTool.ResetPassword); + + +$( document ).ready(function() { + if ($("#resetPassword").length > 0) { + $("#resetPassword").resetPassword(); + } +}); diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/users.js b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/users.js index a23b828..52ba300 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/users.js +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/static/admintool/security/js/users.js @@ -20,11 +20,13 @@ $.extend(AdminTool.Users.prototype, { updateUserURL : '/admintool/accessmanagement/user/update', removeUserURL : '/admintool/accessmanagement/user/remove', getUserInfoURL : '/admintool/accessmanagement/user/get/', + resetUserPasswordURL: '/admintool/accessmanagement/user/resetPassword', passwordLength : 14 }); this.validationUtil = new AdminTool.ValidationUtil(this); this.select2Util = new AdminTool.Select2Util(this); + this.passwordGen = new AdminTool.PasswordGenerator(this); $("#users_table").DataTable(); //prepare @@ -50,6 +52,7 @@ $.extend(AdminTool.Users.prototype, { this.initAddUser(); this.initShowUserInfo(); this.initRemoveUser(); + this.initResetPassword(); }, initUserGroups: function() { @@ -123,6 +126,51 @@ $.extend(AdminTool.Users.prototype, { }); }, + /* ++++++++++++++++++++ + * Reset password + * ++++++++++++++++++++++ */ + + initResetPassword: function() { + var $resetables = $('.resetPassword'); + if ($resetables.length != 0) { + var pluginId = this.elementId; + $resetables.each(function() { + var $el = $(this); + $el.click(function() { + getByID(pluginId).users("initResetPasswordConfirm", this); + }); + }); + } + }, + + initResetPasswordConfirm: function(btn) { + this.showConfirmModal("Reset Password", "Do you really want to reset the users password: " + this.getUserIdentifier(btn, 'resetPassword')+"?", this.resetPassword, btn); + }, + + resetPassword: function(btn) { + var button = $(btn); + var userData = { + "username" : this.getUserIdentifier(button, 'resetPassword') + }; + + this.sendRequest({ + url: this.options.resetUserPasswordURL, + requestType: "POST", + dataType: "text", + data: JSON.stringify(userData), + showModalOnError: true, + ctx: this + }, + function(data, query) { + if (data && data == 'true') { + location.reload(); + } else { + //show error modal (AdminTool.Core) + query.ctx.showErrorModal('Error', 'Error reseting password'); + } + }); + }, + /* ++++++++++++++++++++ * Add/Edit User ++++++++++++++++++++++ */ @@ -209,6 +257,7 @@ $.extend(AdminTool.Users.prototype, { saveUserButton.off(); saveUserButton.on('click', $.proxy(this.saveUser, this, existingUser)); saveUserButton.show(); + getByID('editUser').hide(); //from parent accessManagement.js this.enableFormElements(this.userDataFormId); @@ -217,40 +266,28 @@ $.extend(AdminTool.Users.prototype, { var genPwdButton = getByID('generatePassword'); genPwdButton.off(); - genPwdButton.on('click', $.proxy(this.generatePassword, this)); + genPwdButton.on('click', $.proxy(this.generatePassword, this, 'userPassword')); genPwdButton.parent().show(); var showPwdButton = getByID('showPassword'); showPwdButton.off(); - showPwdButton.on('click', $.proxy(this.switchPasswordVisibility, this)); + showPwdButton.on('click', $.proxy(this.switchPasswordVisibility, this, 'userPassword')); showPwdButton.parent().show(); }, - generatePassword: function() { - getByID('userPassword').val(this.generatePass(this.options.passwordLength)); - }, - - switchPasswordVisibility: function() { - var pwd = getByID('userPassword'); - if (pwd.attr('type') == 'password') { - pwd.attr('type', 'text'); - } else { - pwd.attr('type', 'password'); - } - getByID('showPassword').find('.fa').switchClass('fa-eye', 'fa-eye-slash'); - }, - setRequiredFields: function(required) { var checkbox = getByID('#userPasswordOverride'); var pwd = getByID('#userPassword'); var username = getByID('#username') - checkbox.prop('checked', required); - checkbox.prop('disabled', required); - if (required) { - pwd.attr('required', 'required'); - } else { - pwd.prop('required', required); + if (checkbox.length > 0) { + checkbox.prop('checked', required); + checkbox.prop('disabled', required); + if (required) { + pwd.attr('required', 'required'); + } else { + pwd.prop('required', required); + } } username.prop('disabled', !required); @@ -306,7 +343,7 @@ $.extend(AdminTool.Users.prototype, { "clients" : this.getFormSelectChoice('clients', false) }; var checkbox = getByID('#userPasswordOverride'); - if (checkbox.is(':checked')) { + if (checkbox.length > 0 && checkbox.is(':checked')) { userData["password"] = getByID('#userPassword').val(); } @@ -328,7 +365,7 @@ $.extend(AdminTool.Users.prototype, { } else { //show error modal (AdminTool.Core) query.ctx.showErrorModal('Error saving User', data); - query.ctx.validationUtil.showFieldErrorsOnATErrorList(data); + query.ctx.validationUtil.showFieldErrorsOnATErrorList(data, query.ctx.userDataFormId); } }); }, diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/login.html b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/login.html new file mode 100644 index 0000000..50149f8 --- /dev/null +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/login.html @@ -0,0 +1,53 @@ + + + +
    + + + +
    + + + + \ No newline at end of file diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/security/content/profile.html b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/security/content/profile.html index 52da671..e818820 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/security/content/profile.html +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/security/content/profile.html @@ -17,7 +17,7 @@

    Info

    -
    +
    @@ -40,63 +40,63 @@

    - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + @@ -106,13 +106,227 @@

    + + + +
    diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/security/content/resetPassword.html b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/security/content/resetPassword.html new file mode 100644 index 0000000..ec37b3c --- /dev/null +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/security/content/resetPassword.html @@ -0,0 +1,80 @@ + + + +
    + + + +
    + + + + \ No newline at end of file diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/security/content/resetPasswordRequest.html b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/security/content/resetPasswordRequest.html new file mode 100644 index 0000000..5a386d5 --- /dev/null +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/security/content/resetPasswordRequest.html @@ -0,0 +1,58 @@ + + + +
    + + + +
    + + + + \ No newline at end of file diff --git a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/security/content/users.html b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/security/content/users.html index a5bbb45..45cdc1f 100644 --- a/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/security/content/users.html +++ b/admin-tools-security/admin-tools-security-dbuser/src/main/resources/templates/admintool/security/content/users.html @@ -98,12 +98,18 @@

    th:attr="data-clients=${#strings.listJoin(user.getClientNames(),',')}"> - - + + + + @@ -143,7 +149,7 @@