From fe510e8e1aa9de1f8efbee80980a2e39a947643e Mon Sep 17 00:00:00 2001 From: CieyouRaoul Date: Mon, 10 Feb 2014 04:04:16 +0530 Subject: [PATCH] MIFOSX-751 password reset functionality added by Musoni --- api-docs/apiLive.htm | 32 ++++-- ...orce_password_reset_in_c_configuration.sql | 17 +++ .../service/CommandWrapperBuilder.java | 7 +- ...CommandSourceWritePlatformServiceImpl.java | 5 +- .../SynchronousCommandProcessingService.java | 2 +- .../api/GlobalConfigurationApiConstant.java | 22 ++++ .../api/GlobalConfigurationApiResource.java | 23 ++-- .../GlobalConfigurationDataValidator.java | 67 +++++++++++ .../data/GlobalConfigurationPropertyData.java | 10 ++ .../domain/ConfigurationDomainService.java | 4 + .../domain/ConfigurationDomainServiceJpa.java | 20 ++++ .../domain/GlobalConfigurationProperty.java | 37 +++++- ...onfigurationPropertyNotFoundException.java | 4 + ...dateGlobalConfigurationCommandHandler.java | 2 +- .../ConfigurationReadPlatformServiceImpl.java | 7 +- ...obalConfigurationWritePlatformService.java | 4 +- ...WritePlatformServiceJpaRepositoryImpl.java | 95 ++++++++++++---- .../api/AuthenticationApiResource.java | 25 ++++- .../security/data/AuthenticatedUserData.java | 20 ++++ .../exception/ResetPasswordException.java | 44 ++++++++ .../service/PlatformSecurityContext.java | 6 + ...SpringSecurityPlatformSecurityContext.java | 105 ++++++++++++++++++ .../api/AppUserApiConstant.java | 14 +++ .../useradministration/domain/AppUser.java | 49 ++++++-- .../domain/AppUserPreviewPassword.java | 53 +++++++++ .../AppUserPreviewPasswordRepository.java | 24 ++++ .../PasswordPreviouslyUsedException.java | 16 +++ ...WritePlatformServiceJpaRepositoryImpl.java | 71 ++++++++++-- 28 files changed, 708 insertions(+), 77 deletions(-) create mode 100644 mifosng-db/migrations/core_db/V145__add_force_password_reset_in_c_configuration.sql create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/api/GlobalConfigurationApiConstant.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/data/GlobalConfigurationDataValidator.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/exception/ResetPasswordException.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/useradministration/api/AppUserApiConstant.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUserPreviewPassword.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUserPreviewPasswordRepository.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/useradministration/exception/PasswordPreviouslyUsedException.java diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 646aca77b..b6131856e 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -10225,9 +10225,17 @@ GET https://DomainName/api/v1/configurations { "globalConfiguration": [ { - "name": "maker-checker", - "enabled": false - } + "name": "maker-checker", + "enabled": true, + "value": 0, + "id": 1 + }, + { + "name": "amazon-S3", + "enabled": false, + "value": 0, + "id": 2 + }, ] } @@ -10242,22 +10250,24 @@ GET https://DomainName/api/v1/configurations
-PUT https://DomainName/api/v1/configurations?maker-checker=false +PUT https://DomainName/api/v1/configurations/9 PUT configurations Content-Type: application/json -Request Body: { } +Request Body: { + "enabled":"true", + "value":2 + + } { - "changes": { - "globalConfiguration": { - "maker-checker": false + "resourceId": 9, + "changes": { + "enabled": true } - } -} - +}
diff --git a/mifosng-db/migrations/core_db/V145__add_force_password_reset_in_c_configuration.sql b/mifosng-db/migrations/core_db/V145__add_force_password_reset_in_c_configuration.sql new file mode 100644 index 000000000..0b38dd918 --- /dev/null +++ b/mifosng-db/migrations/core_db/V145__add_force_password_reset_in_c_configuration.sql @@ -0,0 +1,17 @@ +INSERT INTO `c_configuration` (`id`, `name`, `value`, `enabled`) VALUES (NULL, 'force-password-reset-days', '0', '0'); + +ALTER TABLE `m_appuser` ADD `last_time_password_updated` DATE NOT NULL , +ADD INDEX ( `last_time_password_updated` ) ; + +UPDATE `m_appuser` SET `last_time_password_updated` = NOW() WHERE `m_appuser`.`last_time_password_updated` ='0000-00-00'; + +CREATE TABLE IF NOT EXISTS `m_appuser_previews_passwords` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `user_id` bigint(20) NOT NULL, + `password` varchar(255) NOT NULL, + `removal_date` date NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; + +ALTER TABLE m_appuser_previews_passwords +ADD FOREIGN KEY (user_id) REFERENCES m_appuser(id); \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java index 535ad77c8..1e1aa0127 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java @@ -54,11 +54,12 @@ public class CommandWrapperBuilder { return this; } - public CommandWrapperBuilder updateGlobalConfiguration() { + public CommandWrapperBuilder updateGlobalConfiguration(final Long configId) { this.actionName = "UPDATE"; this.entityName = "CONFIGURATION"; - this.entityId = null; - this.href = "/configurations"; + this.entityId = configId; + + this.href = "/configurations/"+configId; return this; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java index e506b75f4..ccb64d6d6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java @@ -47,7 +47,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio boolean isApprovedByChecker = false; // check if is update of own account details - if (wrapper.isUpdateOfOwnUserDetails(this.context.authenticatedUser().getId())) { + if (wrapper.isUpdateOfOwnUserDetails(this.context.authenticatedUser(wrapper).getId())) { // then allow this operation to proceed. // maker checker doesnt mean anything here. isApprovedByChecker = true; // set to true in case permissions have @@ -56,7 +56,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio } else { // if not user changing their own details - check user has // permission to perform specific task. - this.context.authenticatedUser().validateHasPermissionTo(wrapper.getTaskPermissionName()); + this.context.authenticatedUser(wrapper).validateHasPermissionTo(wrapper.getTaskPermissionName()); } validateIsUpdateAllowed(); @@ -72,6 +72,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio result = this.processAndLogCommandService.processAndLogCommand(wrapper, command, isApprovedByChecker); } catch (final RollbackTransactionAsCommandIsNotApprovedByCheckerException e) { + result = this.processAndLogCommandService.logCommand(e.getCommandSourceResult()); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java index bbfbd0f8c..4dc7901bf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java @@ -59,7 +59,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer final NewCommandSourceHandler handler = findCommandHandler(wrapper); final CommandProcessingResult result = handler.processCommand(command); - final AppUser maker = this.context.authenticatedUser(); + final AppUser maker = this.context.authenticatedUser(wrapper); CommandSource commandSourceResult = null; if (command.commandId() != null) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/api/GlobalConfigurationApiConstant.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/api/GlobalConfigurationApiConstant.java new file mode 100644 index 000000000..e24f32293 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/api/GlobalConfigurationApiConstant.java @@ -0,0 +1,22 @@ +package org.mifosplatform.infrastructure.configuration.api; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Created by Cieyou on 1/16/14. + */ +public class GlobalConfigurationApiConstant { + + + public static final String enabled = "enabled"; + public static final String value = "value"; + public static final String id ="id"; + public static final String CONFIGURATION_RESOURCE_NAME = "globalConfiguration"; + + + public static final Set UPDATE_CONFIGURATION_DATA_PARAMETERS = new HashSet(Arrays.asList(enabled, + value)); + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/api/GlobalConfigurationApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/api/GlobalConfigurationApiResource.java index a98929123..8a644935d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/api/GlobalConfigurationApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/api/GlobalConfigurationApiResource.java @@ -11,12 +11,9 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; +import javax.ws.rs.*; import javax.ws.rs.core.Context; +import javax.ws.rs.PathParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriInfo; @@ -76,25 +73,21 @@ public class GlobalConfigurationApiResource { return this.toApiJsonSerializer.serialize(settings, configurationData, this.RESPONSE_DATA_PARAMETERS); } + @PUT + @Path("{configId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public String updateConfiguration(@Context final UriInfo uriInfo) { + public String updateConfiguration(@PathParam("configId") final Long configId,final String apiRequestBodyAsJson) { - final Map configurationParameters = ApiParameterHelper.asMap(uriInfo.getQueryParameters()); - - final Map globalConfiguration = new HashMap(); - globalConfiguration.put("globalConfiguration", configurationParameters); - - final String apiRequestJson = this.toApiJsonSerializer.serialize(globalConfiguration); final CommandWrapper commandRequest = new CommandWrapperBuilder() // - .updateGlobalConfiguration() // - .withJson(apiRequestJson) // + .updateGlobalConfiguration(configId) // + .withJson(apiRequestBodyAsJson) // .build(); final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); return this.toApiJsonSerializer.serialize(result); - } + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/data/GlobalConfigurationDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/data/GlobalConfigurationDataValidator.java new file mode 100644 index 000000000..b02cd0878 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/data/GlobalConfigurationDataValidator.java @@ -0,0 +1,67 @@ +package org.mifosplatform.infrastructure.configuration.data; + + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import org.apache.commons.lang.StringUtils; +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.ApiParameterError; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; +import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.springframework.beans.factory.annotation.Autowired; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.mifosplatform.infrastructure.configuration.api.GlobalConfigurationApiConstant.*; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; +import org.springframework.stereotype.Component; + +/** + * Created by Cieyou on 1/16/14. + */ +@Component +public class GlobalConfigurationDataValidator { + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public GlobalConfigurationDataValidator(final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public void validateForUpdate(final JsonCommand command) { + final String json = command.json(); + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,UPDATE_CONFIGURATION_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(CONFIGURATION_RESOURCE_NAME); + final JsonElement element = this.fromApiJsonHelper.parse(json); + final JsonObject jsonElement = element.getAsJsonObject(); + + if(this.fromApiJsonHelper.parameterExists(enabled, element)) + { + final boolean enabledBool = this.fromApiJsonHelper.extractBooleanNamed(enabled,element); + baseDataValidator.reset().parameter(enabled).value(enabledBool).validateForBooleanValue(); + } + + if(this.fromApiJsonHelper.parameterExists(value, element)) + { + final Long valueStr = this.fromApiJsonHelper.extractLongNamed(value,element); + baseDataValidator.reset().parameter(enabled).value(valueStr).zeroOrPositiveAmount(); + } + + + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/data/GlobalConfigurationPropertyData.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/data/GlobalConfigurationPropertyData.java index a21bbc1f0..6d36cd9c7 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/data/GlobalConfigurationPropertyData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/data/GlobalConfigurationPropertyData.java @@ -16,10 +16,20 @@ public class GlobalConfigurationPropertyData { private final boolean enabled; @SuppressWarnings("unused") private final Long value; + @SuppressWarnings("unused") + private final Long id; public GlobalConfigurationPropertyData(final String name, final boolean enabled, final Long value) { this.name = name; this.enabled = enabled; this.value = value; + this.id = null; + } + + public GlobalConfigurationPropertyData(final String name, final boolean enabled, final Long value, final Long id) { + this.name = name; + this.enabled = enabled; + this.value = value; + this.id = id; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java index bc5ef6ec5..464be38d3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java @@ -28,4 +28,8 @@ public interface ConfigurationDomainService { void updateCache(CacheType cacheType); Long retrievePenaltyWaitPeriod(); + + boolean isPasswordForcedResetEnable(); + + Long retrievePasswordLiveTime(); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java index ad2b55ef7..5145d7277 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java @@ -120,4 +120,24 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService if (property == null) { throw new GlobalConfigurationPropertyNotFoundException(propertyName); } return property.getValue(); } + + @Override + public boolean isPasswordForcedResetEnable(){ + + final String propertyName ="force-password-reset-days"; + final GlobalConfigurationProperty property= this.globalConfigurationRepository.findOneByName(propertyName); + if (property == null) { throw new GlobalConfigurationPropertyNotFoundException(propertyName); } + + return property.isEnabled(); + } + + @Override + public Long retrievePasswordLiveTime(){ + + final String propertyName ="force-password-reset-days"; + final GlobalConfigurationProperty property= this.globalConfigurationRepository.findOneByName(propertyName); + if (property == null) { throw new GlobalConfigurationPropertyNotFoundException(propertyName); } + + return property.getValue(); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/GlobalConfigurationProperty.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/GlobalConfigurationProperty.java index 22302929c..f4af012f5 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/GlobalConfigurationProperty.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/GlobalConfigurationProperty.java @@ -5,11 +5,18 @@ */ package org.mifosplatform.infrastructure.configuration.domain; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.ApiParameterError; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.springframework.data.jpa.domain.AbstractPersistable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; @Entity @Table(name = "c_configuration") @@ -22,7 +29,7 @@ public class GlobalConfigurationProperty extends AbstractPersistable { private boolean enabled; @Column(name= "value", nullable= true) - private final Long value; + private Long value; protected GlobalConfigurationProperty() { this.name = null; @@ -50,4 +57,32 @@ public class GlobalConfigurationProperty extends AbstractPersistable { return updated; } + public Map update(final JsonCommand command) + { + + final Map actualChanges = new LinkedHashMap(7); + + // final String localeAsInput = command.locale(); + + // final List dataValidationErrors = new ArrayList(); + // final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("c"); + + final String enabledParamName = "enabled"; + if (command.isChangeInBooleanParameterNamed(enabledParamName, this.enabled)) { + final Boolean newValue = command.booleanPrimitiveValueOfParameterNamed (enabledParamName); + actualChanges.put(enabledParamName, newValue); + this.enabled = newValue; + } + + final String valueParamName = "value"; + if (command.isChangeInLongParameterNamed(valueParamName, this.value)) { + final Long newValue = command.longValueOfParameterNamed(valueParamName); + actualChanges.put(valueParamName, newValue); + this.value = newValue; + } + + return actualChanges; + + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/exception/GlobalConfigurationPropertyNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/exception/GlobalConfigurationPropertyNotFoundException.java index 8616db365..968b9b126 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/exception/GlobalConfigurationPropertyNotFoundException.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/exception/GlobalConfigurationPropertyNotFoundException.java @@ -16,4 +16,8 @@ public class GlobalConfigurationPropertyNotFoundException extends AbstractPlatfo public GlobalConfigurationPropertyNotFoundException(final String propertyName) { super("error.msg.configuration.property.invalid", "Configuration property `" + propertyName + "` does not exist", propertyName); } + + public GlobalConfigurationPropertyNotFoundException(final Long configId) { + super("error.msg.configuration.id.invalid", "Configuration identifier `" + configId + "` does not exist", configId); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/handler/UpdateGlobalConfigurationCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/handler/UpdateGlobalConfigurationCommandHandler.java index a7c788dbf..4c37bd1d7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/handler/UpdateGlobalConfigurationCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/handler/UpdateGlobalConfigurationCommandHandler.java @@ -27,6 +27,6 @@ public class UpdateGlobalConfigurationCommandHandler implements NewCommandSource @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.update(command); + return this.writePlatformService.update(command.entityId(),command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/service/ConfigurationReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/service/ConfigurationReadPlatformServiceImpl.java index 26f4444d4..421d9637b 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/service/ConfigurationReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/service/ConfigurationReadPlatformServiceImpl.java @@ -38,7 +38,7 @@ public class ConfigurationReadPlatformServiceImpl implements ConfigurationReadPl this.context.authenticatedUser(); - final String sql = "SELECT c.name, c.enabled, c.value FROM c_configuration c order by c.id"; + final String sql = "SELECT c.id, c.name, c.enabled, c.value FROM c_configuration c order by c.id"; final List globalConfiguration = this.jdbcTemplate.query(sql, this.rm, new Object[] {}); return new GlobalConfigurationData(globalConfiguration); @@ -52,8 +52,11 @@ public class ConfigurationReadPlatformServiceImpl implements ConfigurationReadPl final String name = rs.getString("name"); final boolean enabled = rs.getBoolean("enabled"); final Long value = rs.getLong("value"); + final Long id = rs.getLong("id"); - return new GlobalConfigurationPropertyData(name, enabled,value); + return new GlobalConfigurationPropertyData(name, enabled,value,id); } } + + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/service/GlobalConfigurationWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/service/GlobalConfigurationWritePlatformService.java index 47c2de8fb..4665f7693 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/service/GlobalConfigurationWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/service/GlobalConfigurationWritePlatformService.java @@ -10,5 +10,7 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; public interface GlobalConfigurationWritePlatformService { - CommandProcessingResult update(JsonCommand command); + //CommandProcessingResult update(JsonCommand command); + + CommandProcessingResult update(Long configId, JsonCommand command); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/service/GlobalConfigurationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/service/GlobalConfigurationWritePlatformServiceJpaRepositoryImpl.java index 8797373af..3ef99f81a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/service/GlobalConfigurationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/service/GlobalConfigurationWritePlatformServiceJpaRepositoryImpl.java @@ -16,61 +16,114 @@ import org.mifosplatform.infrastructure.configuration.serialization.GlobalConfig import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.mifosplatform.infrastructure.configuration.data.GlobalConfigurationDataValidator; +import org.springframework.dao.DataIntegrityViolationException; @Service public class GlobalConfigurationWritePlatformServiceJpaRepositoryImpl implements GlobalConfigurationWritePlatformService { + private final static Logger logger = LoggerFactory.getLogger(GlobalConfigurationWritePlatformServiceJpaRepositoryImpl.class); + private final PlatformSecurityContext context; private final GlobalConfigurationRepository repository; private final GlobalConfigurationCommandFromApiJsonDeserializer fromApiJsonDeserializer; + private final GlobalConfigurationDataValidator globalConfigurationDataValidator ; @Autowired public GlobalConfigurationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final GlobalConfigurationRepository codeRepository, - final GlobalConfigurationCommandFromApiJsonDeserializer fromApiJsonDeserializer) { + final GlobalConfigurationCommandFromApiJsonDeserializer fromApiJsonDeserializer, + final GlobalConfigurationDataValidator dataValidator) { this.context = context; this.repository = codeRepository; this.fromApiJsonDeserializer = fromApiJsonDeserializer; + this.globalConfigurationDataValidator = dataValidator; + } +// @Transactional +// @Override +// public CommandProcessingResult update(final JsonCommand command) { +// +// this.context.authenticatedUser(); +// +// final UpdateGlobalConfigurationCommand configurationCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json()); +// +// final Map params = configurationCommand.getGlobalConfiguration(); +// +// final Map changes = new LinkedHashMap(params.size()); +// final Map propertiesMap = new LinkedHashMap(1); +// for (final String propertyName : params.keySet()) { +// final GlobalConfigurationProperty property = retrieveBy(propertyName); +// +// final Boolean value = params.get(propertyName); +// final boolean updated = property.updateTo(value); +// +// if (updated) { +// propertiesMap.put(propertyName, value); +// this.repository.save(property); +// } +// } +// +// if (!propertiesMap.isEmpty()) { +// changes.put("globalConfiguration", propertiesMap); +// } +// +// return new CommandProcessingResultBuilder().withCommandId(command.commandId()).with(changes).build(); +// } + @Transactional @Override - public CommandProcessingResult update(final JsonCommand command) { + public CommandProcessingResult update(final Long configId , final JsonCommand command) { this.context.authenticatedUser(); - final UpdateGlobalConfigurationCommand configurationCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json()); + try { + this.globalConfigurationDataValidator.validateForUpdate(command); - final Map params = configurationCommand.getGlobalConfiguration(); + final GlobalConfigurationProperty configItemForUpdate = this.repository.findOne(configId); - final Map changes = new LinkedHashMap(params.size()); - final Map propertiesMap = new LinkedHashMap(1); - for (final String propertyName : params.keySet()) { - final GlobalConfigurationProperty property = retrieveBy(propertyName); - - final Boolean value = params.get(propertyName); - final boolean updated = property.updateTo(value); - - if (updated) { - propertiesMap.put(propertyName, value); - this.repository.save(property); + if (configItemForUpdate == null) { + throw new GlobalConfigurationPropertyNotFoundException(configId); } + + final Map changes = configItemForUpdate.update(command); + + if (!changes.isEmpty()) { + this.repository.save(configItemForUpdate); + } + + return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(configId).with(changes).build(); + + } catch (final DataIntegrityViolationException dve) { + handleDataIntegrityIssues(command, dve); + return CommandProcessingResult.empty(); } - if (!propertiesMap.isEmpty()) { - changes.put("globalConfiguration", propertiesMap); - } - - return new CommandProcessingResultBuilder().withCommandId(command.commandId()).with(changes).build(); - } + } private GlobalConfigurationProperty retrieveBy(final String propertyName) { final GlobalConfigurationProperty property = this.repository.findOneByName(propertyName); if (property == null) { throw new GlobalConfigurationPropertyNotFoundException(propertyName); } return property; } + + /* + * Guaranteed to throw an exception no matter what the data integrity issue + * is. + */ + private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) { + + final Throwable realCause = dve.getMostSpecificCause(); + logger.error(dve.getMessage(), dve); + throw new PlatformDataIntegrityException("error.msg.globalConfiguration.unknown.data.integrity.issue", + "Unknown data integrity issue with resource: " + realCause.getMessage()); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/api/AuthenticationApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/api/AuthenticationApiResource.java index 16ab3d59c..89c2f9f6d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/api/AuthenticationApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/api/AuthenticationApiResource.java @@ -18,9 +18,13 @@ import javax.ws.rs.core.MediaType; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.serialization.ToApiJsonSerializer; import org.mifosplatform.infrastructure.security.data.AuthenticatedUserData; +import org.mifosplatform.infrastructure.security.exception.ResetPasswordException; +import org.mifosplatform.infrastructure.security.service.SpringSecurityPlatformSecurityContext; import org.mifosplatform.useradministration.data.RoleData; import org.mifosplatform.useradministration.domain.AppUser; import org.mifosplatform.useradministration.domain.Role; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Scope; @@ -39,13 +43,17 @@ public class AuthenticationApiResource { private final DaoAuthenticationProvider customAuthenticationProvider; private final ToApiJsonSerializer apiJsonSerializerService; + private final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext; + + private final static Logger logger = LoggerFactory.getLogger(AuthenticationApiResource.class); @Autowired public AuthenticationApiResource( @Qualifier("customAuthenticationProvider") final DaoAuthenticationProvider customAuthenticationProvider, - final ToApiJsonSerializer apiJsonSerializerService) { + final ToApiJsonSerializer apiJsonSerializerService,final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext) { this.customAuthenticationProvider = customAuthenticationProvider; this.apiJsonSerializerService = apiJsonSerializerService; + this.springSecurityPlatformSecurityContext = springSecurityPlatformSecurityContext; } @POST @@ -72,6 +80,8 @@ public class AuthenticationApiResource { roles.add(role.toData()); } + + final Long officeId = principal.getOffice().getId(); final String officeName = principal.getOffice().getName(); @@ -80,8 +90,17 @@ public class AuthenticationApiResource { final EnumOptionData organisationalRole = principal.organisationalRoleData(); - authenticatedUserData = new AuthenticatedUserData(username, officeId, officeName, staffId, staffDisplayName, - organisationalRole, roles, permissions, principal.getId(), new String(base64EncodedAuthenticationKey)); + if(this.springSecurityPlatformSecurityContext.doesPasswordHasToBeRenewed(principal)){ + //throw new ResetPasswordException(principal.getId()); + authenticatedUserData = new AuthenticatedUserData(username,principal.getId(), new String(base64EncodedAuthenticationKey)); + } + else{ + + authenticatedUserData = new AuthenticatedUserData(username, officeId, officeName, staffId, staffDisplayName, + organisationalRole, roles, permissions, principal.getId(), new String(base64EncodedAuthenticationKey)); + } + + } return this.apiJsonSerializerService.serialize(authenticatedUserData); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/data/AuthenticatedUserData.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/data/AuthenticatedUserData.java index b787217c6..93720d737 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/data/AuthenticatedUserData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/data/AuthenticatedUserData.java @@ -38,6 +38,9 @@ public class AuthenticatedUserData { @SuppressWarnings("unused") private final Collection permissions; + @SuppressWarnings("unused") + private final boolean shouldRenewPassword; + public AuthenticatedUserData(final String username, final Collection permissions) { this.username = username; this.userId = null; @@ -50,6 +53,7 @@ public class AuthenticatedUserData { this.organisationalRole = null; this.roles = null; this.permissions = permissions; + this.shouldRenewPassword = false; } public AuthenticatedUserData(final String username, final Long officeId, final String officeName, final Long staffId, @@ -66,5 +70,21 @@ public class AuthenticatedUserData { this.authenticated = true; this.roles = roles; this.permissions = permissions; + this.shouldRenewPassword = false; + } + + public AuthenticatedUserData(final String username, final Long userId, final String base64EncodedAuthenticationKey) { + this.username = username; + this.officeId = null; + this.officeName = null; + this.staffId = null; + this.staffDisplayName = null; + this.organisationalRole = null; + this.userId = userId; + this.base64EncodedAuthenticationKey = base64EncodedAuthenticationKey; + this.authenticated = true; + this.roles = null; + this.permissions = null; + this.shouldRenewPassword = true; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/exception/ResetPasswordException.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/exception/ResetPasswordException.java new file mode 100644 index 000000000..824d84ba3 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/exception/ResetPasswordException.java @@ -0,0 +1,44 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.infrastructure.security.exception; + +import org.mifosplatform.infrastructure.core.data.ApiParameterError; +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformServiceUnavailableException; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; + +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link RuntimeException} that is thrown in the case where a user does not + * have sufficient authorization to execute operation on platform. + */ +public class ResetPasswordException extends PlatformApiDataValidationException { + + + public ResetPasswordException(final Long userId) { + + super( + "error.msg.password.outdated", + "The password of the user with id "+userId+" has expired, please reset it it", + new ArrayList(){ + { + add (ApiParameterError.parameterError( + "error.msg.password.outdated", + "The password of the user with id "+userId+" has expired, please reset it it", + "userId",userId) + ); + + } + } + + ); + + + } + + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/service/PlatformSecurityContext.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/service/PlatformSecurityContext.java index f38f6dfd0..a1e2df9c8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/service/PlatformSecurityContext.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/service/PlatformSecurityContext.java @@ -5,6 +5,8 @@ */ package org.mifosplatform.infrastructure.security.service; +import org.mifosplatform.commands.domain.CommandWrapper; +import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.useradministration.domain.AppUser; public interface PlatformSecurityContext { @@ -14,4 +16,8 @@ public interface PlatformSecurityContext { void validateAccessRights(String resourceOfficeHierarchy); String officeHierarchy(); + + boolean doesPasswordHasToBeRenewed(AppUser currentUser); + + AppUser authenticatedUser(CommandWrapper commandWrapper); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/service/SpringSecurityPlatformSecurityContext.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/service/SpringSecurityPlatformSecurityContext.java index 95f07a06e..dd6faa509 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/service/SpringSecurityPlatformSecurityContext.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/security/service/SpringSecurityPlatformSecurityContext.java @@ -5,23 +5,56 @@ */ package org.mifosplatform.infrastructure.security.service; +import com.sun.jersey.api.spring.Autowire; +import org.mifosplatform.commands.domain.CommandWrapper; +import org.mifosplatform.commands.service.CommandWrapperBuilder; +import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.infrastructure.security.exception.NoAuthorizationException; +import org.mifosplatform.infrastructure.security.exception.ResetPasswordException; import org.mifosplatform.useradministration.domain.AppUser; import org.mifosplatform.useradministration.exception.UnAuthenticatedUserException; +import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainServiceJpa; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import org.springframework.web.method.HandlerMethod; +import sun.print.resources.serviceui_zh_TW; + +import java.lang.reflect.Array; +import java.util.*; /** * Wrapper around spring security's {@link SecurityContext} for extracted the * current authenticated {@link AppUser}. */ + @Service public class SpringSecurityPlatformSecurityContext implements PlatformSecurityContext { + private final ConfigurationDomainService configurationDomainService ; + + private final static Logger logger = LoggerFactory.getLogger(SpringSecurityPlatformSecurityContext.class); + + public static final List EXEMPT_FROM_PASSWORD_RESET_CHECK = new ArrayList(){ + { + add(new CommandWrapperBuilder().updateUser(null).build()); + } + }; + + @Autowired + SpringSecurityPlatformSecurityContext(final ConfigurationDomainService configurationDomainService){ + this.configurationDomainService = configurationDomainService; + } + @Override public AppUser authenticatedUser() { + AppUser currentUser = null; final SecurityContext context = SecurityContextHolder.getContext(); if (context != null) { @@ -33,9 +66,34 @@ public class SpringSecurityPlatformSecurityContext implements PlatformSecurityCo if (currentUser == null) { throw new UnAuthenticatedUserException(); } + + if(this.doesPasswordHasToBeRenewed(currentUser)) { throw new ResetPasswordException(currentUser.getId());} + return currentUser; } + public AppUser authenticatedUser(CommandWrapper commandWrapper) + { + + AppUser currentUser = null; + final SecurityContext context = SecurityContextHolder.getContext(); + if (context != null) { + final Authentication auth = context.getAuthentication(); + if (auth != null) { + currentUser = (AppUser) auth.getPrincipal(); + } + } + + + if (currentUser == null) { throw new UnAuthenticatedUserException(); } + + if(this.shouldCheckForPasswordForceReset(commandWrapper) && this.doesPasswordHasToBeRenewed(currentUser)) { throw new ResetPasswordException(currentUser.getId());} + + return currentUser; + + } + + @Override public void validateAccessRights(final String resourceOfficeHierarchy) { @@ -51,4 +109,51 @@ public class SpringSecurityPlatformSecurityContext implements PlatformSecurityCo public String officeHierarchy() { return authenticatedUser().getOffice().getHierarchy(); } + + @Override + public boolean doesPasswordHasToBeRenewed(AppUser currentUser ) + { + + if( this.configurationDomainService.isPasswordForcedResetEnable()){ + + Long passwordDurationDays = this.configurationDomainService.retrievePasswordLiveTime(); + final Date passWordLastUpdateDate = currentUser .getLastTimePasswordUpdated(); + + Calendar c = Calendar.getInstance(); + c.setTime(passWordLastUpdateDate); + c.add(Calendar.DATE,passwordDurationDays.intValue()); + + final Date passwordExpirationDate = c.getTime(); + + if(DateUtils.getDateOfTenant().after(passwordExpirationDate)){ + + return true ; + } + else { return false; } + + } + else{ + + return false; + } + + } + + private boolean shouldCheckForPasswordForceReset(CommandWrapper commandWrapper) + { + for( CommandWrapper commandItem : this.EXEMPT_FROM_PASSWORD_RESET_CHECK ) + { + if(commandItem.actionName().equals(commandWrapper.actionName()) + && commandItem.getEntityName().equals(commandWrapper.getEntityName())) + { + + return false; + } + } + + return true; + } + + + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/api/AppUserApiConstant.java b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/api/AppUserApiConstant.java new file mode 100644 index 000000000..62d94d25e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/api/AppUserApiConstant.java @@ -0,0 +1,14 @@ +package org.mifosplatform.useradministration.api; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Created by Cieyou on 1/16/14. + */ +public class AppUserApiConstant { + + public static final int numberOfPreviousPasswords = 3; + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java index eb8deba31..bf05bf950 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java @@ -12,16 +12,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; -import javax.persistence.ManyToMany; -import javax.persistence.ManyToOne; -import javax.persistence.Table; -import javax.persistence.UniqueConstraint; +import javax.persistence.*; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.EnumOptionData; @@ -37,6 +30,8 @@ import org.springframework.data.jpa.domain.AbstractPersistable; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; +import org.mifosplatform.infrastructure.core.service.DateUtils; + @Entity @Table(name = "m_appuser", uniqueConstraints = @UniqueConstraint(columnNames = { "username" }, name = "username_org")) @@ -89,6 +84,10 @@ public class AppUser extends AbstractPersistable implements PlatformUser { @JoinTable(name = "m_appuser_role", joinColumns = @JoinColumn(name = "appuser_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) private Set roles; + @Column(name = "last_time_password_updated") + @Temporal(TemporalType.DATE) + private Date lastTimePasswordUpdated; + public static AppUser fromJson(final Office userOffice, final Set allRoles, final JsonCommand command) { final String username = command.stringValueOfParameterNamed("username"); @@ -137,6 +136,7 @@ public class AppUser extends AbstractPersistable implements PlatformUser { this.enabled = user.isEnabled(); this.roles = roles; this.firstTimeLoginRemaining = true; + this.lastTimePasswordUpdated = DateUtils.getDateOfTenant(); } public EnumOptionData organisationalRoleData() { @@ -150,6 +150,8 @@ public class AppUser extends AbstractPersistable implements PlatformUser { public void updatePassword(final String encodePassword) { this.password = encodePassword; this.firstTimeLoginRemaining = false; + this.lastTimePasswordUpdated = DateUtils.getDateOfTenant(); + } public void changeOffice(final Office differentOffice) { @@ -324,6 +326,8 @@ public class AppUser extends AbstractPersistable implements PlatformUser { return this.office; } + public Date getLastTimePasswordUpdated(){ return this.lastTimePasswordUpdated; } + public boolean canNotApproveLoanInPast() { return hasNotPermissionForAnyOf("ALL_FUNCTIONS", "APPROVEINPAST_LOAN"); } @@ -491,4 +495,31 @@ public class AppUser extends AbstractPersistable implements PlatformUser { } return staffDisplayName; } + + public String getEncodedPassword(final JsonCommand command,final PlatformPasswordEncoder platformPasswordEncoder) + { + final String passwordParamName = "password"; + final String passwordEncodedParamName = "passwordEncoded"; + String passwordEncodedValue = null; + + if (command.hasParameter(passwordParamName)) { + if (command.isChangeInPasswordParameterNamed(passwordParamName, this.password + , platformPasswordEncoder,getId())) { + + passwordEncodedValue = command.passwordValueOfParameterNamed(passwordParamName, platformPasswordEncoder, + getId()); + + } + } + else if (command.hasParameter(passwordEncodedParamName)) { + if (command.isChangeInStringParameterNamed(passwordEncodedParamName, this.password)) { + + passwordEncodedValue = command.stringValueOfParameterNamed(passwordEncodedParamName); + + + } + } + + return passwordEncodedValue; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUserPreviewPassword.java b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUserPreviewPassword.java new file mode 100644 index 000000000..8de454831 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUserPreviewPassword.java @@ -0,0 +1,53 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.useradministration.domain; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.jpa.domain.AbstractPersistable; + +import javax.persistence.*; +import java.util.*; + + +@Entity +@Table(name = "m_appuser_previews_passwords") +public class AppUserPreviewPassword extends AbstractPersistable { + + private final static Logger logger = LoggerFactory.getLogger(AppUserPreviewPassword.class); + + @Column(name = "user_id", nullable = false) + private Long userId; + + @Column(name = "removal_date") + @Temporal(TemporalType.DATE) + private Date removalDate; + + @Column(name = "password", nullable = false) + private String password; + + + protected AppUserPreviewPassword() { + + } + + public AppUserPreviewPassword(final AppUser user) { + + this.userId = user.getId(); + this.password = user.getPassword().trim(); + this.removalDate = DateUtils.getDateOfTenant(); + } + + public String getPassword() + { + return this.password; + } + + + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUserPreviewPasswordRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUserPreviewPasswordRepository.java new file mode 100644 index 000000000..21fcfe024 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUserPreviewPasswordRepository.java @@ -0,0 +1,24 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.useradministration.domain; + +import org.mifosplatform.infrastructure.security.domain.PlatformUserRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface AppUserPreviewPasswordRepository extends JpaRepository, JpaSpecificationExecutor { + + // no behaviour added + // @Query("SELECT upp FROM AppUserPreviewPassword upp WHERE upp.userId = :userId order by upp.removalDate ASC ") + public List findByUserId(Long userId,Pageable pageable); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/exception/PasswordPreviouslyUsedException.java b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/exception/PasswordPreviouslyUsedException.java new file mode 100644 index 000000000..d084405db --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/exception/PasswordPreviouslyUsedException.java @@ -0,0 +1,16 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.useradministration.exception; + +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; + +public class PasswordPreviouslyUsedException extends PlatformApiDataValidationException { + + public PasswordPreviouslyUsedException() { + super("error.msg.password.already.used", "The submitted password has already been used in the past",null); + } +} + diff --git a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java index 23ef92d42..f3a2c2106 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java @@ -11,6 +11,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.mifosplatform.commands.domain.CommandWrapper; +import org.mifosplatform.commands.service.CommandWrapperBuilder; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; @@ -23,11 +25,9 @@ import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext import org.mifosplatform.organisation.office.domain.Office; import org.mifosplatform.organisation.office.domain.OfficeRepository; import org.mifosplatform.organisation.office.exception.OfficeNotFoundException; -import org.mifosplatform.useradministration.domain.AppUser; -import org.mifosplatform.useradministration.domain.AppUserRepository; -import org.mifosplatform.useradministration.domain.Role; -import org.mifosplatform.useradministration.domain.RoleRepository; -import org.mifosplatform.useradministration.domain.UserDomainService; +import org.mifosplatform.useradministration.api.AppUserApiConstant; +import org.mifosplatform.useradministration.domain.*; +import org.mifosplatform.useradministration.exception.PasswordPreviouslyUsedException; import org.mifosplatform.useradministration.exception.RoleNotFoundException; import org.mifosplatform.useradministration.exception.UserNotFoundException; import org.slf4j.Logger; @@ -36,6 +36,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Caching; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; @@ -52,11 +54,12 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit private final OfficeRepository officeRepository; private final RoleRepository roleRepository; private final UserDataValidator fromApiJsonDeserializer; + private final AppUserPreviewPasswordRepository appUserPreviewPasswordRepository; @Autowired public AppUserWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final AppUserRepository appUserRepository, final UserDomainService userDomainService, final OfficeRepository officeRepository, final RoleRepository roleRepository, - final PlatformPasswordEncoder platformPasswordEncoder, final UserDataValidator fromApiJsonDeserializer) { + final PlatformPasswordEncoder platformPasswordEncoder, final UserDataValidator fromApiJsonDeserializer,final AppUserPreviewPasswordRepository appUserPreviewPasswordRepository) { this.context = context; this.appUserRepository = appUserRepository; this.userDomainService = userDomainService; @@ -64,6 +67,7 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit this.roleRepository = roleRepository; this.platformPasswordEncoder = platformPasswordEncoder; this.fromApiJsonDeserializer = fromApiJsonDeserializer; + this.appUserPreviewPasswordRepository = appUserPreviewPasswordRepository; } @Transactional @@ -120,13 +124,21 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit public CommandProcessingResult updateUser(final Long userId, final JsonCommand command) { try { - this.context.authenticatedUser(); + + + this.context.authenticatedUser(new CommandWrapperBuilder().updateUser(null).build()); this.fromApiJsonDeserializer.validateForUpdate(command.json()); final AppUser userToUpdate = this.appUserRepository.findOne(userId); + + if (userToUpdate == null) { throw new UserNotFoundException(userId); } + + final AppUserPreviewPassword currentPasswordToSaveAsPreview = getCurrentPasswordToSaveAsPreview(userToUpdate,command); + + final Map changes = userToUpdate.update(command, this.platformPasswordEncoder); if (changes.containsKey("officeId")) { @@ -146,6 +158,9 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit if (!changes.isEmpty()) { this.appUserRepository.saveAndFlush(userToUpdate); + + if(currentPasswordToSaveAsPreview!=null) {this.appUserPreviewPasswordRepository.save(currentPasswordToSaveAsPreview);} + } return new CommandProcessingResultBuilder() // @@ -155,10 +170,52 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit .build(); } catch (final DataIntegrityViolationException dve) { handleDataIntegrityIssues(command, dve); + return CommandProcessingResult.empty(); } } + /** + * encode the new submitted password + * retrieve the last n used password + * check if the current submitted password, match with one of them + * @param user + * @param command + * @return + */ + private AppUserPreviewPassword getCurrentPasswordToSaveAsPreview(final AppUser user,final JsonCommand command) + { + + final String passWordEncodedValue = user.getEncodedPassword(command,this.platformPasswordEncoder); + + AppUserPreviewPassword currentPasswordToSaveAsPreview = null; + + if(passWordEncodedValue!=null ) + { + + PageRequest pageRequest = new PageRequest(0, AppUserApiConstant.numberOfPreviousPasswords, Sort.Direction.DESC,"removalDate"); + + final List nLastUsedPasswords = this.appUserPreviewPasswordRepository.findByUserId(user.getId(),pageRequest); + + for(AppUserPreviewPassword aPreviewPassword : nLastUsedPasswords) + { + + if(aPreviewPassword.getPassword().equals(passWordEncodedValue)){ + + throw new PasswordPreviouslyUsedException(); + + } + } + + currentPasswordToSaveAsPreview = new AppUserPreviewPassword(user); + + } + + + return currentPasswordToSaveAsPreview ; + + } + private Set assembleSetOfRoles(final String[] rolesArray) { final Set allRoles = new HashSet();