add support for soft delete of users and changing users passwords.

This commit is contained in:
Keith Woodlock
2012-08-24 12:32:18 +01:00
parent 5f60266921
commit 91890ff204
8 changed files with 54 additions and 73 deletions
@@ -0,0 +1,2 @@
ALTER TABLE `m_appuser`
ADD COLUMN `is_deleted` TINYINT(1) DEFAULT 0 NOT NULL AFTER `id`;
@@ -58,14 +58,14 @@ public class UsersApiResource {
@Autowired
private PlatformSecurityContext context;
private static final Set<String> typicalResponseParameters = new HashSet<String>(Arrays.asList("id", "officeId", "officeName", "username", "firstname", "lastname", "email", "selectedRoles"));
@GET
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public String retrieveUsers(@Context final UriInfo uriInfo) {
Set<String> typicalResponseParameters = new HashSet<String>(Arrays.asList("id", "officeId", "officeName", "username", "firstname", "lastname", "email"));
Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
if (responseParameters.isEmpty()) {
responseParameters.addAll(typicalResponseParameters);
@@ -83,8 +83,6 @@ public class UsersApiResource {
@Produces({MediaType.APPLICATION_JSON})
public String retrieveUser(@PathParam("userId") final Long userId, @Context final UriInfo uriInfo) {
Set<String> typicalResponseParameters = new HashSet<String>(Arrays.asList("id", "officeId", "officeName", "username", "firstname", "lastname", "email"));
Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
if (responseParameters.isEmpty()) {
responseParameters.addAll(typicalResponseParameters);
@@ -96,27 +94,29 @@ public class UsersApiResource {
if (template) {
List<OfficeLookup> offices = new ArrayList<OfficeLookup>(this.officeReadPlatformService.retrieveAllOfficesForLookup());
user = new AppUserData(user, offices);
responseParameters.add("allowedOffices");
responseParameters.add("availableRoles");
responseParameters.add("selectedRoles");
addTemplateRelatedParameters(responseParameters);
}
return this.apiJsonSerializerService.serializeAppUserDataToJson(prettyPrint, responseParameters, user);
}
private void addTemplateRelatedParameters(final Set<String> responseParameters) {
responseParameters.add("allowedOffices");
responseParameters.add("availableRoles");
}
@GET
@Path("template")
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public String newUserDetails(@Context final UriInfo uriInfo) {
Set<String> typicalResponseParameters = new HashSet<String>(Arrays.asList("id", "officeId", "officeName", "username", "firstname", "lastname", "email",
"allowedOffices", "availableRoles", "selectedRoles"));
Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
if (responseParameters.isEmpty()) {
responseParameters.addAll(typicalResponseParameters);
}
addTemplateRelatedParameters(responseParameters);
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
AppUserData user = this.appUserReadPlatformService.retrieveNewUserDetails();
@@ -55,19 +55,4 @@ public class BasicPasswordEncodablePlatformUser implements PlatformUser {
public boolean isEnabled() {
return false;
}
@Override
public boolean isFirstTimeLoginRemaining() {
return false;
}
@Override
public void updateUsernamePasswordOnFirstTimeLogin(final String newUsername, final String newPasswordEncoded) {
// dummy method
}
@Override
public void updatePasswordOnFirstTimeLogin(final String newPasswordEncoded) {
// dummy method
}
}
@@ -7,9 +7,5 @@ import org.springframework.security.core.userdetails.UserDetails;
*/
public interface PlatformUser extends UserDetails {
boolean isFirstTimeLoginRemaining();
void updateUsernamePasswordOnFirstTimeLogin(String newUsername, String newPasswordEncoded);
void updatePasswordOnFirstTimeLogin(String newPasswordEncoded);
// void updatePasswordOnFirstTimeLogin(String newPasswordEncoded);
}
@@ -63,9 +63,11 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
@Embedded
private final LoanProductRelatedDetail loanRepaymentScheduleDetail;
@SuppressWarnings("unused")
@Column(name = "term_frequency", nullable = false)
private Integer termFrequency;
@SuppressWarnings("unused")
@Enumerated(EnumType.ORDINAL)
@Column(name = "term_period_frequency_enum", nullable = false)
private PeriodFrequencyType termPeriodFrequencyType;
@@ -81,6 +83,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
@Column(name = "rejectedon_date")
private Date rejectedOnDate;
@SuppressWarnings("unused")
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "withdrawnon_date")
private Date withdrawnOnDate;
@@ -520,7 +523,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
}
}
private void handleLoanOverpayment(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
private void handleLoanOverpayment(@SuppressWarnings("unused") final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_OVERPAYMENT, LoanStatus.fromInt(this.loanStatus));
this.loanStatus = statusEnum.getValue();
@@ -44,7 +44,7 @@ public class AppUser extends AbstractAuditableCustom<AppUser, Long> implements P
private String password;
@Column(name = "nonexpired", nullable=false)
private final boolean accountNonExpired;
private boolean accountNonExpired;
@Column(name = "nonlocked", nullable=false)
private final boolean accountNonLocked;
@@ -53,10 +53,14 @@ public class AppUser extends AbstractAuditableCustom<AppUser, Long> implements P
private final boolean credentialsNonExpired;
@Column(name = "enabled", nullable=false)
private final boolean enabled;
private boolean enabled;
@Column(name = "firsttime_login_remaining", nullable=false)
@SuppressWarnings("unused")
@Column(name = "firsttime_login_remaining", nullable=false)
private boolean firstTimeLoginRemaining;
@Column(name = "is_deleted", nullable=false)
private boolean deleted;
@ManyToOne
@JoinColumn(name = "office_id")
@@ -65,7 +69,6 @@ public class AppUser extends AbstractAuditableCustom<AppUser, Long> implements P
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "m_appuser_role", joinColumns = @JoinColumn(name = "appuser_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles;
public static AppUser createNew(final Office office,
final Set<Role> allRoles, final String username, final String email,
@@ -111,33 +114,9 @@ public class AppUser extends AbstractAuditableCustom<AppUser, Long> implements P
this.firstTimeLoginRemaining = true;
}
@Override
public boolean isFirstTimeLoginRemaining() {
return this.firstTimeLoginRemaining;
}
@Override
public void updateUsernamePasswordOnFirstTimeLogin(final String newUsername,
final String newPasswordEncoded) {
if (this.username.equals(newUsername)) {
throw new UsernameMustBeDifferentException();
}
this.username = newUsername;
updatePasswordOnFirstTimeLogin(newPasswordEncoded);
this.firstTimeLoginRemaining = false;
}
@Override
public void updatePasswordOnFirstTimeLogin(final String newPasswordEncoded) {
if (this.password.equals(newPasswordEncoded)) {
throw new PasswordMustBeDifferentException();
}
this.password = newPasswordEncoded;
this.firstTimeLoginRemaining = false;
}
public void updatePassword(final String encodePassword) {
this.password = encodePassword;
this.firstTimeLoginRemaining = false;
}
public void update(final Set<Role> allRoles, final Office office, final UserCommand userCommand) {
@@ -162,6 +141,23 @@ public class AppUser extends AbstractAuditableCustom<AppUser, Long> implements P
this.email = userCommand.getEmail();
}
}
/**
* Delete is a <i>soft delete</i>. Updates flag so it wont appear in query/report results.
*
* Any fields with unique constraints and prepended with id of record.
*/
public void delete() {
this.deleted = true;
this.enabled = false;
this.accountNonExpired = false;
this.firstTimeLoginRemaining = true;
this.username = this.getId() + "_DELETED_" + this.username;
}
public boolean isDeleted() {
return deleted;
}
@Override
public Collection<GrantedAuthority> getAuthorities() {
@@ -54,7 +54,7 @@ public class AppUserReadPlatformServiceImpl implements AppUserReadPlatformServic
AppUserMapper mapper = new AppUserMapper(offices);
String sql = "select " + mapper.schema()
+ " where u.office_id in (" + officeIdsList + ") order by u.id";
+ " where u.office_id in (" + officeIdsList + ") and u.is_deleted = 0 order by u.id";
return this.jdbcTemplate.query(sql, mapper, new Object[] {});
}
@@ -90,7 +90,7 @@ public class AppUserReadPlatformServiceImpl implements AppUserReadPlatformServic
context.authenticatedUser();
AppUser user = this.appUserRepository.findOne(userId);
if (user == null) {
if (user == null || user.isDeleted()) {
throw new UserNotFoundException(userId);
}
@@ -29,7 +29,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
@@ -88,7 +87,7 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
return appUser.getId();
} catch (DataIntegrityViolationException dve) {
handleAppUserDataIntegrityIssues(command, dve);
handleDataIntegrityIssues(command, dve);
return Long.valueOf(-1);
} catch (PlatformEmailSendException e) {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
@@ -134,13 +133,13 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
String newPasswordEncoded = this.platformPasswordEncoder.encode(dummyPlatformUser);
userToUpdate.updatePasswordOnFirstTimeLogin(newPasswordEncoded);
userToUpdate.updatePassword(newPasswordEncoded);
this.appUserRepository.saveAndFlush(userToUpdate);
}
return userToUpdate.getId();
} catch (DataIntegrityViolationException dve) {
handleAppUserDataIntegrityIssues(command, dve);
handleDataIntegrityIssues(command, dve);
return Long.valueOf(-1);
}
}
@@ -170,23 +169,23 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
return allRoles;
}
// FIXME - KW - move this to a soft delete of user marking a is_deleted field as true so doesnt appear in results
// more than making a user disabled, locked?
@Transactional
@Override
public void deleteUser(final Long userId) {
try {
this.appUserRepository.delete(userId);
} catch (InvalidDataAccessApiUsageException exception) {
// spring data jpa throws this when deleting user that doesnt exist
AppUser user = this.appUserRepository.findOne(userId);
if (user == null || user.isDeleted()) {
throw new UserNotFoundException(userId);
}
user.delete();
this.appUserRepository.save(user);
}
/*
* Guaranteed to throw an exception no matter what the data integrity issue is.
*/
private void handleAppUserDataIntegrityIssues(final UserCommand command, final DataIntegrityViolationException dve) {
private void handleDataIntegrityIssues(final UserCommand command, final DataIntegrityViolationException dve) {
Throwable realCause = dve.getMostSpecificCause();
if (realCause.getMessage().contains("username_org")) {