add support for soft delete of users and changing users passwords.
This commit is contained in:
@@ -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
|
@Autowired
|
||||||
private PlatformSecurityContext context;
|
private PlatformSecurityContext context;
|
||||||
|
|
||||||
|
private static final Set<String> typicalResponseParameters = new HashSet<String>(Arrays.asList("id", "officeId", "officeName", "username", "firstname", "lastname", "email", "selectedRoles"));
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Consumes({MediaType.APPLICATION_JSON})
|
@Consumes({MediaType.APPLICATION_JSON})
|
||||||
@Produces({MediaType.APPLICATION_JSON})
|
@Produces({MediaType.APPLICATION_JSON})
|
||||||
public String retrieveUsers(@Context final UriInfo uriInfo) {
|
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());
|
Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
|
||||||
if (responseParameters.isEmpty()) {
|
if (responseParameters.isEmpty()) {
|
||||||
responseParameters.addAll(typicalResponseParameters);
|
responseParameters.addAll(typicalResponseParameters);
|
||||||
@@ -83,8 +83,6 @@ public class UsersApiResource {
|
|||||||
@Produces({MediaType.APPLICATION_JSON})
|
@Produces({MediaType.APPLICATION_JSON})
|
||||||
public String retrieveUser(@PathParam("userId") final Long userId, @Context final UriInfo uriInfo) {
|
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());
|
Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
|
||||||
if (responseParameters.isEmpty()) {
|
if (responseParameters.isEmpty()) {
|
||||||
responseParameters.addAll(typicalResponseParameters);
|
responseParameters.addAll(typicalResponseParameters);
|
||||||
@@ -96,27 +94,29 @@ public class UsersApiResource {
|
|||||||
if (template) {
|
if (template) {
|
||||||
List<OfficeLookup> offices = new ArrayList<OfficeLookup>(this.officeReadPlatformService.retrieveAllOfficesForLookup());
|
List<OfficeLookup> offices = new ArrayList<OfficeLookup>(this.officeReadPlatformService.retrieveAllOfficesForLookup());
|
||||||
user = new AppUserData(user, offices);
|
user = new AppUserData(user, offices);
|
||||||
responseParameters.add("allowedOffices");
|
addTemplateRelatedParameters(responseParameters);
|
||||||
responseParameters.add("availableRoles");
|
|
||||||
responseParameters.add("selectedRoles");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.apiJsonSerializerService.serializeAppUserDataToJson(prettyPrint, responseParameters, user);
|
return this.apiJsonSerializerService.serializeAppUserDataToJson(prettyPrint, responseParameters, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addTemplateRelatedParameters(final Set<String> responseParameters) {
|
||||||
|
responseParameters.add("allowedOffices");
|
||||||
|
responseParameters.add("availableRoles");
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("template")
|
@Path("template")
|
||||||
@Consumes({MediaType.APPLICATION_JSON})
|
@Consumes({MediaType.APPLICATION_JSON})
|
||||||
@Produces({MediaType.APPLICATION_JSON})
|
@Produces({MediaType.APPLICATION_JSON})
|
||||||
public String newUserDetails(@Context final UriInfo uriInfo) {
|
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());
|
Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
|
||||||
if (responseParameters.isEmpty()) {
|
if (responseParameters.isEmpty()) {
|
||||||
responseParameters.addAll(typicalResponseParameters);
|
responseParameters.addAll(typicalResponseParameters);
|
||||||
}
|
}
|
||||||
|
addTemplateRelatedParameters(responseParameters);
|
||||||
|
|
||||||
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
|
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
|
||||||
|
|
||||||
AppUserData user = this.appUserReadPlatformService.retrieveNewUserDetails();
|
AppUserData user = this.appUserReadPlatformService.retrieveNewUserDetails();
|
||||||
|
|||||||
-15
@@ -55,19 +55,4 @@ public class BasicPasswordEncodablePlatformUser implements PlatformUser {
|
|||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return false;
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+1
-5
@@ -7,9 +7,5 @@ import org.springframework.security.core.userdetails.UserDetails;
|
|||||||
*/
|
*/
|
||||||
public interface PlatformUser extends UserDetails {
|
public interface PlatformUser extends UserDetails {
|
||||||
|
|
||||||
boolean isFirstTimeLoginRemaining();
|
// void updatePasswordOnFirstTimeLogin(String newPasswordEncoded);
|
||||||
|
|
||||||
void updateUsernamePasswordOnFirstTimeLogin(String newUsername, String newPasswordEncoded);
|
|
||||||
|
|
||||||
void updatePasswordOnFirstTimeLogin(String newPasswordEncoded);
|
|
||||||
}
|
}
|
||||||
@@ -63,9 +63,11 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
@Embedded
|
@Embedded
|
||||||
private final LoanProductRelatedDetail loanRepaymentScheduleDetail;
|
private final LoanProductRelatedDetail loanRepaymentScheduleDetail;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
@Column(name = "term_frequency", nullable = false)
|
@Column(name = "term_frequency", nullable = false)
|
||||||
private Integer termFrequency;
|
private Integer termFrequency;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
@Enumerated(EnumType.ORDINAL)
|
@Enumerated(EnumType.ORDINAL)
|
||||||
@Column(name = "term_period_frequency_enum", nullable = false)
|
@Column(name = "term_period_frequency_enum", nullable = false)
|
||||||
private PeriodFrequencyType termPeriodFrequencyType;
|
private PeriodFrequencyType termPeriodFrequencyType;
|
||||||
@@ -81,6 +83,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
@Column(name = "rejectedon_date")
|
@Column(name = "rejectedon_date")
|
||||||
private Date rejectedOnDate;
|
private Date rejectedOnDate;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
@Column(name = "withdrawnon_date")
|
@Column(name = "withdrawnon_date")
|
||||||
private Date withdrawnOnDate;
|
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));
|
LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_OVERPAYMENT, LoanStatus.fromInt(this.loanStatus));
|
||||||
this.loanStatus = statusEnum.getValue();
|
this.loanStatus = statusEnum.getValue();
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class AppUser extends AbstractAuditableCustom<AppUser, Long> implements P
|
|||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@Column(name = "nonexpired", nullable=false)
|
@Column(name = "nonexpired", nullable=false)
|
||||||
private final boolean accountNonExpired;
|
private boolean accountNonExpired;
|
||||||
|
|
||||||
@Column(name = "nonlocked", nullable=false)
|
@Column(name = "nonlocked", nullable=false)
|
||||||
private final boolean accountNonLocked;
|
private final boolean accountNonLocked;
|
||||||
@@ -53,10 +53,14 @@ public class AppUser extends AbstractAuditableCustom<AppUser, Long> implements P
|
|||||||
private final boolean credentialsNonExpired;
|
private final boolean credentialsNonExpired;
|
||||||
|
|
||||||
@Column(name = "enabled", nullable=false)
|
@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;
|
private boolean firstTimeLoginRemaining;
|
||||||
|
|
||||||
|
@Column(name = "is_deleted", nullable=false)
|
||||||
|
private boolean deleted;
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "office_id")
|
@JoinColumn(name = "office_id")
|
||||||
@@ -65,7 +69,6 @@ public class AppUser extends AbstractAuditableCustom<AppUser, Long> implements P
|
|||||||
@ManyToMany(fetch = FetchType.EAGER)
|
@ManyToMany(fetch = FetchType.EAGER)
|
||||||
@JoinTable(name = "m_appuser_role", joinColumns = @JoinColumn(name = "appuser_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
|
@JoinTable(name = "m_appuser_role", joinColumns = @JoinColumn(name = "appuser_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
|
||||||
private Set<Role> roles;
|
private Set<Role> roles;
|
||||||
|
|
||||||
|
|
||||||
public static AppUser createNew(final Office office,
|
public static AppUser createNew(final Office office,
|
||||||
final Set<Role> allRoles, final String username, final String email,
|
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;
|
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) {
|
public void updatePassword(final String encodePassword) {
|
||||||
this.password = encodePassword;
|
this.password = encodePassword;
|
||||||
|
this.firstTimeLoginRemaining = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(final Set<Role> allRoles, final Office office, final UserCommand userCommand) {
|
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();
|
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
|
@Override
|
||||||
public Collection<GrantedAuthority> getAuthorities() {
|
public Collection<GrantedAuthority> getAuthorities() {
|
||||||
|
|||||||
+2
-2
@@ -54,7 +54,7 @@ public class AppUserReadPlatformServiceImpl implements AppUserReadPlatformServic
|
|||||||
|
|
||||||
AppUserMapper mapper = new AppUserMapper(offices);
|
AppUserMapper mapper = new AppUserMapper(offices);
|
||||||
String sql = "select " + mapper.schema()
|
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[] {});
|
return this.jdbcTemplate.query(sql, mapper, new Object[] {});
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ public class AppUserReadPlatformServiceImpl implements AppUserReadPlatformServic
|
|||||||
context.authenticatedUser();
|
context.authenticatedUser();
|
||||||
|
|
||||||
AppUser user = this.appUserRepository.findOne(userId);
|
AppUser user = this.appUserRepository.findOne(userId);
|
||||||
if (user == null) {
|
if (user == null || user.isDeleted()) {
|
||||||
throw new UserNotFoundException(userId);
|
throw new UserNotFoundException(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-11
@@ -29,7 +29,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
@@ -88,7 +87,7 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
|
|||||||
|
|
||||||
return appUser.getId();
|
return appUser.getId();
|
||||||
} catch (DataIntegrityViolationException dve) {
|
} catch (DataIntegrityViolationException dve) {
|
||||||
handleAppUserDataIntegrityIssues(command, dve);
|
handleDataIntegrityIssues(command, dve);
|
||||||
return Long.valueOf(-1);
|
return Long.valueOf(-1);
|
||||||
} catch (PlatformEmailSendException e) {
|
} catch (PlatformEmailSendException e) {
|
||||||
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||||
@@ -134,13 +133,13 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
|
|||||||
|
|
||||||
String newPasswordEncoded = this.platformPasswordEncoder.encode(dummyPlatformUser);
|
String newPasswordEncoded = this.platformPasswordEncoder.encode(dummyPlatformUser);
|
||||||
|
|
||||||
userToUpdate.updatePasswordOnFirstTimeLogin(newPasswordEncoded);
|
userToUpdate.updatePassword(newPasswordEncoded);
|
||||||
this.appUserRepository.saveAndFlush(userToUpdate);
|
this.appUserRepository.saveAndFlush(userToUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
return userToUpdate.getId();
|
return userToUpdate.getId();
|
||||||
} catch (DataIntegrityViolationException dve) {
|
} catch (DataIntegrityViolationException dve) {
|
||||||
handleAppUserDataIntegrityIssues(command, dve);
|
handleDataIntegrityIssues(command, dve);
|
||||||
return Long.valueOf(-1);
|
return Long.valueOf(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,23 +169,23 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
|
|||||||
return allRoles;
|
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
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public void deleteUser(final Long userId) {
|
public void deleteUser(final Long userId) {
|
||||||
try {
|
|
||||||
this.appUserRepository.delete(userId);
|
AppUser user = this.appUserRepository.findOne(userId);
|
||||||
} catch (InvalidDataAccessApiUsageException exception) {
|
if (user == null || user.isDeleted()) {
|
||||||
// spring data jpa throws this when deleting user that doesnt exist
|
|
||||||
throw new UserNotFoundException(userId);
|
throw new UserNotFoundException(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user.delete();
|
||||||
|
this.appUserRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Guaranteed to throw an exception no matter what the data integrity issue is.
|
* 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();
|
Throwable realCause = dve.getMostSpecificCause();
|
||||||
if (realCause.getMessage().contains("username_org")) {
|
if (realCause.getMessage().contains("username_org")) {
|
||||||
|
|||||||
Reference in New Issue
Block a user