Customer Self Service API Phase 1

This commit is contained in:
Adi Narayana Raju
2015-12-16 17:38:52 +05:30
parent 65b767c31d
commit 196d19d87a
38 changed files with 3833 additions and 38 deletions
+2034 -1
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -192,7 +192,7 @@ body {
.flybar-nav {
display: block;
float: left;
min-width: 90px;
min-width: 80px;
position: relative;
}
@@ -34,21 +34,23 @@ public final class SearchParameters {
private final Long provisioningEntryId ;
private final Long productId ;
private final Long categoryId ;
private final boolean isSelfUser;
public static SearchParameters from(final String sqlSearch, final Long officeId, final String externalId, final String name,
public static SearchParameters from(final String sqlSearch, final Long officeId, final String externalId, final String name,
final String hierarchy) {
final Long staffId = null;
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(sqlSearch, officeId, externalId, name, hierarchy, null, null, null, null, null, null, staffId,
accountNo, loanId, savingsId, orphansOnly);
accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forClients(final String sqlSearch, final Long officeId, final String externalId,
final String displayName, final String firstname, final String lastname, final String hierarchy, final Integer offset,
final Integer limit, final String orderBy, final String sortOrder, final Boolean orphansOnly) {
final Integer limit, final String orderBy, final String sortOrder, final Boolean orphansOnly, final boolean isSelfUser) {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
@@ -57,7 +59,7 @@ public final class SearchParameters {
final Long savingsId = null;
return new SearchParameters(sqlSearch, officeId, externalId, displayName, hierarchy, firstname, lastname, offset, maxLimitAllowed,
orderBy, sortOrder, staffId, accountNo, loanId, savingsId, orphansOnly);
orderBy, sortOrder, staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forGroups(final String sqlSearch, final Long officeId, final Long staffId, final String externalId,
@@ -68,15 +70,17 @@ public final class SearchParameters {
final String accountNo = null;
final Long loanId = null;
final Long savingsId = null;
final boolean isSelfUser = false;
return new SearchParameters(sqlSearch, officeId, externalId, name, hierarchy, null, null, offset, maxLimitAllowed, orderBy,
sortOrder, staffId, accountNo, loanId, savingsId, orphansOnly);
sortOrder, staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forOffices(final String orderBy, final String sortOrder) {
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(null, null, null, null, null, null, null, null, null, orderBy, sortOrder, null, null, null, null,
orphansOnly);
orphansOnly, isSelfUser);
}
public static SearchParameters forLoans(final String sqlSearch, final String externalId, final Integer offset, final Integer limit,
@@ -87,9 +91,10 @@ public final class SearchParameters {
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(sqlSearch, null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder,
staffId, accountNo, loanId, savingsId, orphansOnly);
staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forJournalEntries(final Long officeId, final Integer offset, final Integer limit, final String orderBy,
@@ -98,9 +103,10 @@ public final class SearchParameters {
final Integer maxLimitAllowed = getCheckedLimit(limit);
final Long staffId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(null, officeId, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId,
null, loanId, savingsId, orphansOnly);
null, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forJournalEntries(final Long officeId, final Integer offset, final Integer limit, final String orderBy,
@@ -120,9 +126,10 @@ public final class SearchParameters {
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(null, null, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId, null,
loanId, savingsId, orphansOnly);
loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forPagination(final Integer offset, final Integer limit) {
@@ -134,9 +141,10 @@ public final class SearchParameters {
final Boolean orphansOnly = false;
final String orderBy = null;
final String sortOrder = null;
final boolean isSelfUser = false;
return new SearchParameters(null, null, null, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder, staffId, null,
loanId, savingsId, orphansOnly);
loanId, savingsId, orphansOnly, isSelfUser);
}
public final static SearchParameters forProvisioningEntries(final Long provisioningEntryId, final Long officeId, final Long productId,
@@ -153,9 +161,10 @@ public final class SearchParameters {
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(sqlSearch, null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder,
staffId, accountNo, loanId, savingsId, orphansOnly);
staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
public static SearchParameters forAccountTransfer(final String sqlSearch, final String externalId, final Integer offset,
@@ -167,15 +176,16 @@ public final class SearchParameters {
final Long loanId = null;
final Long savingsId = null;
final Boolean orphansOnly = false;
final boolean isSelfUser = false;
return new SearchParameters(sqlSearch, null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder,
staffId, accountNo, loanId, savingsId, orphansOnly);
staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
}
private SearchParameters(final String sqlSearch, final Long officeId, final String externalId, final String name,
final String hierarchy, final String firstname, final String lastname, final Integer offset, final Integer limit,
final String orderBy, final String sortOrder, final Long staffId, final String accountNo, final Long loanId,
final Long savingsId, final Boolean orphansOnly) {
final Long savingsId, final Boolean orphansOnly, boolean isSelfUser) {
this.sqlSearch = sqlSearch;
this.officeId = officeId;
this.externalId = externalId;
@@ -196,6 +206,7 @@ public final class SearchParameters {
this.provisioningEntryId = null ;
this.productId = null ;
this.categoryId = null ;
this.isSelfUser = isSelfUser;
}
@@ -221,6 +232,7 @@ public final class SearchParameters {
this.provisioningEntryId = provisioningEntryId ;
this.productId = productId ;
this.categoryId = categoryId ;
this.isSelfUser = false;
}
@@ -248,6 +260,7 @@ public final class SearchParameters {
this.provisioningEntryId = null ;
this.productId = null ;
this.categoryId = null ;
this.isSelfUser = false;
}
public boolean isOrderByRequested() {
@@ -397,4 +410,9 @@ public final class SearchParameters {
public boolean isCategoryIdPassed() {
return this.categoryId != null && this.categoryId != 0 ;
}
public boolean isSelfUser() {
return this.isSelfUser;
}
}
@@ -24,11 +24,15 @@ import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.infrastructure.security.data.PlatformRequestLog;
import org.mifosplatform.infrastructure.security.exception.InvalidTenantIdentiferException;
import org.mifosplatform.infrastructure.security.service.BasicAuthTenantDetailsService;
import org.mifosplatform.useradministration.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@@ -143,4 +147,22 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil
logger.info(this.toApiJsonSerializer.serialize(log));
}
}
@Override
protected void onSuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, Authentication authResult)
throws IOException {
super.onSuccessfulAuthentication(request, response, authResult);
AppUser user = (AppUser) authResult.getPrincipal();
String pathURL = request.getRequestURI();
boolean isSelfServiceRequest = (pathURL != null && pathURL.contains("/self/"));
boolean notAllowed = ((isSelfServiceRequest && !user.isSelfServiceUser())
||(!isSelfServiceRequest && user.isSelfServiceUser()));
if(notAllowed){
throw new BadCredentialsException("User not authorised to use the requested resource.");
}
}
}
@@ -88,7 +88,7 @@ public class TenantAwareTenantIdentifierFilter extends GenericFilterBean {
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
final String reqHead = request.getHeader("Access-Control-Request-Headers");
if (null != reqHead && !reqHead.equals(null)) {
if (null != reqHead && !reqHead.isEmpty()) {
response.setHeader("Access-Control-Allow-Headers", reqHead);
}
@@ -0,0 +1,48 @@
/**
* 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.vote;
import java.util.Collection;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
public class SelfServiceUserAccessVote implements AccessDecisionVoter<FilterInvocation> {
@Override
public boolean supports(@SuppressWarnings("unused") ConfigAttribute attribute) {
// This implementation supports any attribute, because it does not rely on it.
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
@Override
public int vote(final Authentication authentication, final FilterInvocation fi,
@SuppressWarnings("unused") final Collection<ConfigAttribute> attributes) {
if(!"OPTIONS".equalsIgnoreCase(fi.getHttpRequest().getMethod())){
AppUser user = (AppUser) authentication.getPrincipal();
String pathURL = fi.getRequestUrl();
boolean isSelfServiceRequest = (pathURL != null && pathURL.contains("/self/"));
boolean notAllowed = ((isSelfServiceRequest && !user.isSelfServiceUser())
||(!isSelfServiceRequest && user.isSelfServiceUser()));
if(notAllowed){
return ACCESS_DENIED;
}
}
return ACCESS_GRANTED;
}
}
@@ -75,7 +75,7 @@ public class AccountTransfersReadPlatformServiceImpl implements AccountTransfers
final Integer mostRelevantFromAccountType = fromAccountType;
final Collection<EnumOptionData> fromAccountTypeOptions = Arrays.asList(savingsAccountType, loanAccountType);
final Collection<EnumOptionData> toAccountTypeOptions;
if (mostRelevantFromAccountType == 1) {
if (mostRelevantFromAccountType != null && mostRelevantFromAccountType == 1) {
// overpaid loan amt transfer to savings account
toAccountTypeOptions = Arrays.asList(savingsAccountType);
} else {
@@ -117,10 +117,22 @@ public class ClientsApiResource {
@QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder,
@QueryParam("orphansOnly") final Boolean orphansOnly) {
return this.retrieveAll(uriInfo, sqlSearch, officeId, externalId, displayName, firstname,
lastname, hierarchy, offset, limit, orderBy, sortOrder, orphansOnly, false);
}
public String retrieveAll(final UriInfo uriInfo, final String sqlSearch,
final Long officeId, final String externalId,
final String displayName, final String firstname,
final String lastname, final String hierarchy,
final Integer offset, final Integer limit,
final String orderBy, final String sortOrder,
final Boolean orphansOnly, final boolean isSelfUser) {
this.context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
final SearchParameters searchParameters = SearchParameters.forClients(sqlSearch, officeId, externalId, displayName, firstname,
lastname, hierarchy, offset, limit, orderBy, sortOrder, orphansOnly);
lastname, hierarchy, offset, limit, orderBy, sortOrder, orphansOnly, isSelfUser);
final Page<ClientData> clientData = this.clientReadPlatformService.retrieveAll(searchParameters);
@@ -113,6 +113,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
final String userOfficeHierarchy = this.context.officeHierarchy();
final String underHierarchySearchString = userOfficeHierarchy + "%";
final String appUserID = String.valueOf(context.authenticatedUser().getId());
// if (searchParameters.isScopedByOfficeHierarchy()) {
// this.context.validateAccessRights(searchParameters.getHierarchy());
@@ -123,6 +124,10 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
sqlBuilder.append(this.clientMapper.schema());
sqlBuilder.append(" where (o.hierarchy like ? or transferToOffice.hierarchy like ?) ");
if(searchParameters.isSelfUser()){
sqlBuilder.append(" and c.id in (select umap.client_id from m_selfservice_user_client_mapping as umap where umap.appuser_id = ? ) ");
}
final String extraCriteria = buildSqlStringFromClientCriteria(searchParameters);
@@ -146,8 +151,11 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
}
final String sqlCountRows = "SELECT FOUND_ROWS()";
return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), new Object[] {
underHierarchySearchString, underHierarchySearchString }, this.clientMapper);
Object[] params = new Object[] {underHierarchySearchString, underHierarchySearchString };
if(searchParameters.isSelfUser()){
params = new Object[] {underHierarchySearchString, underHierarchySearchString, appUserID };
}
return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), params, this.clientMapper);
}
private String buildSqlStringFromClientCriteria(final SearchParameters searchParameters) {
@@ -0,0 +1,85 @@
/**
* 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.portfolio.self.account.api;
import java.util.Collection;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.account.api.AccountTransfersApiResource;
import org.mifosplatform.portfolio.self.account.data.SelfAccountTemplateData;
import org.mifosplatform.portfolio.self.account.data.SelfAccountTransferData;
import org.mifosplatform.portfolio.self.account.data.SelfAccountTransferDataValidator;
import org.mifosplatform.portfolio.self.account.service.SelfAccountTransferReadService;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/self/accounttransfers")
@Component
@Scope("singleton")
public class SelfAccountTransferApiResource {
private final PlatformSecurityContext context;
private final DefaultToApiJsonSerializer<SelfAccountTransferData> toApiJsonSerializer;
private final AccountTransfersApiResource accountTransfersApiResource;
private final SelfAccountTransferReadService selfAccountTransferReadService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final SelfAccountTransferDataValidator dataValidator;
@Autowired
public SelfAccountTransferApiResource(
final PlatformSecurityContext context,
final DefaultToApiJsonSerializer<SelfAccountTransferData> toApiJsonSerializer,
final AccountTransfersApiResource accountTransfersApiResource,
final SelfAccountTransferReadService selfAccountTransferReadService,
final ApiRequestParameterHelper apiRequestParameterHelper,
final SelfAccountTransferDataValidator dataValidator) {
this.context = context;
this.toApiJsonSerializer = toApiJsonSerializer;
this.accountTransfersApiResource = accountTransfersApiResource;
this.selfAccountTransferReadService = selfAccountTransferReadService;
this.apiRequestParameterHelper = apiRequestParameterHelper;
this.dataValidator = dataValidator;
}
@GET
@Path("template")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String template(@Context final UriInfo uriInfo) {
AppUser user = this.context.authenticatedUser();
Collection<SelfAccountTemplateData> templateData = this.selfAccountTransferReadService
.retrieveSelfAccountTemplateData(user);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings,
new SelfAccountTransferData(templateData));
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String create(final String apiRequestBodyAsJson) {
this.dataValidator.validateCreate(apiRequestBodyAsJson);
return this.accountTransfersApiResource.create(apiRequestBodyAsJson);
}
}
@@ -0,0 +1,93 @@
/**
* 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.portfolio.self.account.data;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.account.service.AccountTransferEnumerations;
@SuppressWarnings("unused")
public class SelfAccountTemplateData implements
Comparable<SelfAccountTemplateData> {
private final Long accountId;
private final String accountNo;
private final EnumOptionData accountType;
private final Long clientId;
private final String clientName;
private final Long officeId;
private final String officeName;
public SelfAccountTemplateData(final Long accountId,
final String accountNo, final Integer accountType,
final Long clientId, final String clientName, final Long officeId,
final String officeName) {
this.accountId = accountId;
this.accountNo = accountNo;
this.accountType = AccountTransferEnumerations.accountType(accountType);
this.clientId = clientId;
this.clientName = clientName;
this.officeId = officeId;
this.officeName = officeName;
}
public SelfAccountTemplateData(final Long accountId,
final Integer accountType, final Long clientId, final Long officeId) {
this.accountId = accountId;
this.accountNo = null;
this.accountType = AccountTransferEnumerations.accountType(accountType);
this.clientId = clientId;
this.clientName = null;
this.officeId = officeId;
this.officeName = null;
}
@Override
public int compareTo(final SelfAccountTemplateData obj) {
if (obj == null) {
return -1;
}
return new CompareToBuilder() //
.append(this.accountId, obj.accountId) //
.append(this.accountType.getValue(), obj.accountType.getValue()) //
.append(this.clientId, obj.clientId) //
.append(this.officeId, obj.officeId) //
.toComparison();
}
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (obj.getClass() != getClass()) {
return false;
}
final SelfAccountTemplateData rhs = (SelfAccountTemplateData) obj;
return new EqualsBuilder() //
.append(this.accountId, rhs.accountId) //
.append(this.accountType.getValue(), rhs.accountType.getValue()) //
.append(this.clientId, rhs.clientId) //
.append(this.officeId, rhs.officeId) //
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37) //
.append(this.accountId) //
.append(this.accountType.getValue()) //
.append(this.clientId) //
.append(this.officeId) //
.toHashCode();
}
}
@@ -0,0 +1,20 @@
/**
* 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.portfolio.self.account.data;
import java.util.Collection;
@SuppressWarnings("unused")
public class SelfAccountTransferData {
private final Collection<SelfAccountTemplateData> accountOptions;
public SelfAccountTransferData(
final Collection<SelfAccountTemplateData> accountOptions) {
this.accountOptions = accountOptions;
}
}
@@ -0,0 +1,180 @@
/**
* 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.portfolio.self.account.data;
import static org.mifosplatform.portfolio.account.AccountDetailConstants.fromAccountIdParamName;
import static org.mifosplatform.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
import static org.mifosplatform.portfolio.account.AccountDetailConstants.fromClientIdParamName;
import static org.mifosplatform.portfolio.account.AccountDetailConstants.fromOfficeIdParamName;
import static org.mifosplatform.portfolio.account.AccountDetailConstants.toAccountIdParamName;
import static org.mifosplatform.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
import static org.mifosplatform.portfolio.account.AccountDetailConstants.toClientIdParamName;
import static org.mifosplatform.portfolio.account.AccountDetailConstants.toOfficeIdParamName;
import static org.mifosplatform.portfolio.account.api.AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.lang.StringUtils;
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.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.self.account.service.SelfAccountTransferReadService;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonElement;
@Component
public class SelfAccountTransferDataValidator {
private final PlatformSecurityContext context;
private final SelfAccountTransferReadService selfAccountTransferReadService;
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public SelfAccountTransferDataValidator(
final PlatformSecurityContext context,
final SelfAccountTransferReadService selfAccountTransferReadService,
final FromJsonHelper fromApiJsonHelper) {
this.context = context;
this.selfAccountTransferReadService = selfAccountTransferReadService;
this.fromApiJsonHelper = fromApiJsonHelper;
}
public void validateCreate(String apiRequestBodyAsJson) {
if (StringUtils.isBlank(apiRequestBodyAsJson)) {
throw new InvalidJsonException();
}
JsonElement element = this.fromApiJsonHelper
.parse(apiRequestBodyAsJson);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
dataValidationErrors).resource(ACCOUNT_TRANSFER_RESOURCE_NAME);
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(
fromOfficeIdParamName, element);
baseDataValidator.reset().parameter(fromOfficeIdParamName)
.value(fromOfficeId).notNull().integerGreaterThanZero();
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(
fromClientIdParamName, element);
baseDataValidator.reset().parameter(fromClientIdParamName)
.value(fromClientId).notNull().integerGreaterThanZero();
final Long fromAccountId = this.fromApiJsonHelper.extractLongNamed(
fromAccountIdParamName, element);
baseDataValidator.reset().parameter(fromAccountIdParamName)
.value(fromAccountId).notNull().integerGreaterThanZero();
final Integer fromAccountType = this.fromApiJsonHelper
.extractIntegerSansLocaleNamed(fromAccountTypeParamName,
element);
baseDataValidator.reset().parameter(fromAccountTypeParamName)
.value(fromAccountType).notNull()
.isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2));
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(
toOfficeIdParamName, element);
baseDataValidator.reset().parameter(toOfficeIdParamName)
.value(toOfficeId).notNull().integerGreaterThanZero();
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(
toClientIdParamName, element);
baseDataValidator.reset().parameter(toClientIdParamName)
.value(toClientId).notNull().integerGreaterThanZero();
final Long toAccountId = this.fromApiJsonHelper.extractLongNamed(
toAccountIdParamName, element);
baseDataValidator.reset().parameter(toAccountIdParamName)
.value(toAccountId).notNull().integerGreaterThanZero();
final Integer toAccountType = this.fromApiJsonHelper
.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
baseDataValidator.reset().parameter(toAccountTypeParamName)
.value(toAccountType).notNull()
.isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2));
if (fromAccountType != null && fromAccountType == 1
&& toAccountType != null && toAccountType == 1) {
baseDataValidator
.reset()
.failWithCode("loan.to.loan.transfer.not.allowed",
"Cannot transfer from Loan account to another Loan account.");
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
SelfAccountTemplateData fromAccount = new SelfAccountTemplateData(
fromAccountId, fromAccountType, fromClientId, fromOfficeId);
SelfAccountTemplateData toAccount = new SelfAccountTemplateData(
toAccountId, toAccountType, toClientId, toOfficeId);
validateSelfUserAccounts(fromAccount, toAccount, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void validateSelfUserAccounts(
final SelfAccountTemplateData fromAccount,
final SelfAccountTemplateData toAccount,
final DataValidatorBuilder baseDataValidator) {
AppUser user = this.context.authenticatedUser();
Collection<SelfAccountTemplateData> userValidAccounts = this.selfAccountTransferReadService
.retrieveSelfAccountTemplateData(user);
boolean validFromAccount = false;
for (SelfAccountTemplateData validAccount : userValidAccounts) {
if (validAccount.equals(fromAccount)) {
validFromAccount = true;
break;
}
}
boolean validToAccount = false;
for (SelfAccountTemplateData validAccount : userValidAccounts) {
if (validAccount.equals(toAccount)) {
validToAccount = true;
break;
}
}
if (!validFromAccount) {
baseDataValidator
.reset()
.failWithCode("invalid.from.account.details",
"Source account details doesn't match with valid user account details.");
}
if (!validToAccount) {
baseDataValidator
.reset()
.failWithCode("invalid.to.account.details",
"Destination account details doesn't match with valid user account details.");
}
if (fromAccount.equals(toAccount)) {
baseDataValidator.reset().failWithCode(
"same.from.to.account.details",
"Source and Destination account details are same.");
}
}
private void throwExceptionIfValidationWarningsExist(
final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
}
@@ -0,0 +1,18 @@
/**
* 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.portfolio.self.account.service;
import java.util.Collection;
import org.mifosplatform.portfolio.self.account.data.SelfAccountTemplateData;
import org.mifosplatform.useradministration.domain.AppUser;
public interface SelfAccountTransferReadService {
Collection<SelfAccountTemplateData> retrieveSelfAccountTemplateData(
AppUser user);
}
@@ -0,0 +1,89 @@
/**
* 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.portfolio.self.account.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.portfolio.self.account.data.SelfAccountTemplateData;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class SelfAccountTransferReadServiceImpl implements
SelfAccountTransferReadService {
private final JdbcTemplate jdbcTemplate;
@Autowired
public SelfAccountTransferReadServiceImpl(final RoutingDataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Collection<SelfAccountTemplateData> retrieveSelfAccountTemplateData(
AppUser user) {
SelfAccountTemplateMapper mapper = new SelfAccountTemplateMapper();
StringBuffer sql = new StringBuffer()
.append("select s.id as accountId, ")
.append("s.account_no as accountNo, ")
.append("2 as accountType, ")
.append("c.id as clientId, ")
.append("c.display_name as clientName, ")
.append("o.id as officeId, ")
.append("o.name as officeName ")
.append("from m_appuser as u ")
.append("inner join m_selfservice_user_client_mapping as map on u.id = map.appuser_id ")
.append("inner join m_client as c on map.client_id = c.id ")
.append("inner join m_office as o on c.office_id = o.id ")
.append("inner join m_savings_account as s on s.client_id = c.id ")
.append("where u.id = ? ")
.append("and s.status_enum = 300 ")
.append("union ")
.append("select l.id as accountId, ")
.append("l.account_no as accountNo, ")
.append("1 as accountType, ")
.append("c.id as clientId, ")
.append("c.display_name as clientName, ")
.append("o.id as officeId, ")
.append("o.name as officeName ")
.append("from m_appuser as u ")
.append("inner join m_selfservice_user_client_mapping as map on u.id = map.appuser_id ")
.append("inner join m_client as c on map.client_id = c.id ")
.append("inner join m_office as o on c.office_id = o.id ")
.append("inner join m_loan as l on l.client_id = c.id ")
.append("where u.id = ? ")
.append("and l.loan_status_id = 300 ");
return this.jdbcTemplate.query(sql.toString(), mapper, new Object[] {
user.getId(), user.getId() });
}
private final class SelfAccountTemplateMapper implements
RowMapper<SelfAccountTemplateData> {
@Override
public SelfAccountTemplateData mapRow(final ResultSet rs,
@SuppressWarnings("unused") final int rowNum)
throws SQLException {
final Long accountId = rs.getLong("accountId");
final String accountNo = rs.getString("accountNo");
final Integer accountType = rs.getInt("accountType");
final Long clientId = rs.getLong("clientId");
final String clientName = rs.getString("clientName");
final Long officeId = rs.getLong("officeId");
final String officeName = rs.getString("officeName");
return new SelfAccountTemplateData(accountId, accountNo,
accountType, clientId, clientName, officeId, officeName);
}
}
}
@@ -0,0 +1,208 @@
/**
* 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.portfolio.self.client.api;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.infrastructure.documentmanagement.api.ImagesApiResource;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.client.api.ClientApiConstants;
import org.mifosplatform.portfolio.client.api.ClientChargesApiResource;
import org.mifosplatform.portfolio.client.api.ClientTransactionsApiResource;
import org.mifosplatform.portfolio.client.api.ClientsApiResource;
import org.mifosplatform.portfolio.client.exception.ClientNotFoundException;
import org.mifosplatform.portfolio.self.client.data.SelfClientDataValidator;
import org.mifosplatform.portfolio.self.client.service.AppuserClientMapperReadService;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/self/clients")
@Component
@Scope("singleton")
public class SelfClientsApiResource {
private final PlatformSecurityContext context;
private final ClientsApiResource clientApiResource;
private final ImagesApiResource imagesApiResource;
private final ClientChargesApiResource clientChargesApiResource;
private final ClientTransactionsApiResource clientTransactionsApiResource;
private final AppuserClientMapperReadService appUserClientMapperReadService;
private final SelfClientDataValidator dataValidator;
@Autowired
public SelfClientsApiResource(
final PlatformSecurityContext context,
final ClientsApiResource clientApiResource,
final ImagesApiResource imagesApiResource,
final ClientChargesApiResource clientChargesApiResource,
final ClientTransactionsApiResource clientTransactionsApiResource,
final AppuserClientMapperReadService appUserClientMapperReadService,
final SelfClientDataValidator dataValidator) {
this.context = context;
this.clientApiResource = clientApiResource;
this.imagesApiResource = imagesApiResource;
this.clientChargesApiResource = clientChargesApiResource;
this.clientTransactionsApiResource = clientTransactionsApiResource;
this.appUserClientMapperReadService = appUserClientMapperReadService;
this.dataValidator = dataValidator;
}
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveAll(@Context final UriInfo uriInfo,
@QueryParam("displayName") final String displayName,
@QueryParam("firstName") final String firstname,
@QueryParam("lastName") final String lastname,
@QueryParam("offset") final Integer offset,
@QueryParam("limit") final Integer limit,
@QueryParam("orderBy") final String orderBy,
@QueryParam("sortOrder") final String sortOrder) {
final String sqlSearch = null;
final Long officeId = null;
final String externalId = null;
final String hierarchy = null;
final Boolean orphansOnly = null;
return this.clientApiResource.retrieveAll(uriInfo, sqlSearch, officeId,
externalId, displayName, firstname, lastname, hierarchy,
offset, limit, orderBy, sortOrder, orphansOnly, true);
}
@GET
@Path("{clientId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveOne(@PathParam("clientId") final Long clientId,
@Context final UriInfo uriInfo) {
this.dataValidator.validateRetrieveOne(uriInfo);
validateAppuserClientsMapping(clientId);
final boolean staffInSelectedOfficeOnly = false;
return this.clientApiResource.retrieveOne(clientId, uriInfo,
staffInSelectedOfficeOnly);
}
@GET
@Path("{clientId}/accounts")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveAssociatedAccounts(
@PathParam("clientId") final Long clientId,
@Context final UriInfo uriInfo) {
validateAppuserClientsMapping(clientId);
return this.clientApiResource.retrieveAssociatedAccounts(clientId,
uriInfo);
}
@GET
@Path("{clientId}/images")
@Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_HTML,
MediaType.APPLICATION_JSON })
@Produces({ MediaType.TEXT_PLAIN })
public Response retrieveImage(@PathParam("clientId") final Long clientId,
@QueryParam("maxWidth") final Integer maxWidth,
@QueryParam("maxHeight") final Integer maxHeight,
@QueryParam("output") final String output) {
validateAppuserClientsMapping(clientId);
return this.imagesApiResource.retrieveImage("clients", clientId,
maxWidth, maxHeight, output);
}
@GET
@Path("{clientId}/charges")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveAllClientCharges(
@PathParam("clientId") final Long clientId,
@DefaultValue(ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS_VALUE_ALL) @QueryParam(ClientApiConstants.CLIENT_CHARGE_QUERY_PARAM_STATUS) final String chargeStatus,
@QueryParam("pendingPayment") final Boolean pendingPayment,
@Context final UriInfo uriInfo,
@QueryParam("limit") final Integer limit,
@QueryParam("offset") final Integer offset) {
validateAppuserClientsMapping(clientId);
return this.clientChargesApiResource.retrieveAllClientCharges(clientId,
chargeStatus, pendingPayment, uriInfo, limit, offset);
}
@GET
@Path("{clientId}/charges/{chargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveClientCharge(
@PathParam("clientId") final Long clientId,
@PathParam("chargeId") final Long chargeId,
@Context final UriInfo uriInfo) {
this.dataValidator.validateClientCharges(uriInfo);
validateAppuserClientsMapping(clientId);
return this.clientChargesApiResource.retrieveClientCharge(clientId,
chargeId, uriInfo);
}
@GET
@Path("{clientId}/transactions")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveAllClientTransactions(
@PathParam("clientId") final Long clientId,
@Context final UriInfo uriInfo,
@QueryParam("offset") final Integer offset,
@QueryParam("limit") final Integer limit) {
validateAppuserClientsMapping(clientId);
return this.clientTransactionsApiResource
.retrieveAllClientTransactions(clientId, uriInfo, offset, limit);
}
@GET
@Path("{clientId}/transactions/{transactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveClientTransaction(
@PathParam("clientId") final Long clientId,
@PathParam("transactionId") final Long transactionId,
@Context final UriInfo uriInfo) {
validateAppuserClientsMapping(clientId);
return this.clientTransactionsApiResource.retrieveClientTransaction(
clientId, transactionId, uriInfo);
}
private void validateAppuserClientsMapping(final Long clientId) {
AppUser user = this.context.authenticatedUser();
final boolean mappedClientId = this.appUserClientMapperReadService
.isClientMappedToUser(clientId, user.getId());
if (!mappedClientId) {
throw new ClientNotFoundException(clientId);
}
}
}
@@ -0,0 +1,64 @@
/**
* 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.portfolio.self.client.data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.infrastructure.core.api.ApiParameterHelper;
import org.mifosplatform.infrastructure.core.exception.UnsupportedParameterException;
import org.springframework.stereotype.Component;
@Component
public class SelfClientDataValidator {
private static final Set<String> allowedChargesAssociationParameters = new HashSet<>(
Arrays.asList("transactions"));
public void validateClientCharges(final UriInfo uriInfo) {
List<String> unsupportedParams = new ArrayList<>();
Set<String> associationParameters = ApiParameterHelper
.extractAssociationsForResponseIfProvided(uriInfo
.getQueryParameters());
if (!associationParameters.isEmpty()) {
associationParameters
.removeAll(allowedChargesAssociationParameters);
if (!associationParameters.isEmpty()) {
unsupportedParams.addAll(associationParameters);
}
}
if (uriInfo.getQueryParameters().getFirst("exclude") != null) {
unsupportedParams.add("exclude");
}
if (unsupportedParams.size() > 0) {
throw new UnsupportedParameterException(unsupportedParams);
}
}
public void validateRetrieveOne(final UriInfo uriInfo) {
List<String> unsupportedParams = new ArrayList<>();
final boolean templateRequest = ApiParameterHelper.template(uriInfo
.getQueryParameters());
if (templateRequest) {
unsupportedParams.add("template");
}
if (unsupportedParams.size() > 0) {
throw new UnsupportedParameterException(unsupportedParams);
}
}
}
@@ -0,0 +1,12 @@
/**
* 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.portfolio.self.client.service;
public interface AppuserClientMapperReadService {
public Boolean isClientMappedToUser(Long clientId, Long appUserId);
}
@@ -0,0 +1,33 @@
/**
* 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.portfolio.self.client.service;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class AppuserClientMapperReadServiceImpl implements
AppuserClientMapperReadService {
private final JdbcTemplate jdbcTemplate;
@Autowired
public AppuserClientMapperReadServiceImpl(final RoutingDataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Boolean isClientMappedToUser(Long clientId, Long appUserId) {
return this.jdbcTemplate
.queryForObject(
"select case when (count(*) > 0) then true else false end "
+ " from m_selfservice_user_client_mapping where client_id = ? and appuser_id = ?",
new Object[] { clientId, appUserId }, Boolean.class);
}
}
@@ -0,0 +1,124 @@
/**
* 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.portfolio.self.loanaccount.api;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.loanaccount.api.LoanChargesApiResource;
import org.mifosplatform.portfolio.loanaccount.api.LoanTransactionsApiResource;
import org.mifosplatform.portfolio.loanaccount.api.LoansApiResource;
import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException;
import org.mifosplatform.portfolio.self.loanaccount.data.SelfLoansDataValidator;
import org.mifosplatform.portfolio.self.loanaccount.service.AppuserLoansMapperReadService;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/self/loans")
@Component
@Scope("singleton")
public class SelfLoansApiResource {
private final PlatformSecurityContext context;
private final LoansApiResource loansApiResource;
private final LoanTransactionsApiResource loanTransactionsApiResource;
private final LoanChargesApiResource loanChargesApiResource;
private final AppuserLoansMapperReadService appuserLoansMapperReadService;
private final SelfLoansDataValidator dataValidator;
@Autowired
public SelfLoansApiResource(final PlatformSecurityContext context,
final LoansApiResource loansApiResource,
final LoanTransactionsApiResource loanTransactionsApiResource,
final LoanChargesApiResource loanChargesApiResource,
final AppuserLoansMapperReadService appuserLoansMapperReadService,
final SelfLoansDataValidator dataValidator) {
this.context = context;
this.loansApiResource = loansApiResource;
this.loanTransactionsApiResource = loanTransactionsApiResource;
this.loanChargesApiResource = loanChargesApiResource;
this.appuserLoansMapperReadService = appuserLoansMapperReadService;
this.dataValidator = dataValidator;
}
@GET
@Path("{loanId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveLoan(@PathParam("loanId") final Long loanId,
@Context final UriInfo uriInfo) {
this.dataValidator.validateRetrieveLoan(uriInfo);
validateAppuserLoanMapping(loanId);
final boolean staffInSelectedOfficeOnly = false;
return this.loansApiResource.retrieveLoan(loanId,
staffInSelectedOfficeOnly, uriInfo);
}
@GET
@Path("{loanId}/transactions/{transactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveTransaction(@PathParam("loanId") final Long loanId,
@PathParam("transactionId") final Long transactionId,
@Context final UriInfo uriInfo) {
this.dataValidator.validateRetrieveTransaction(uriInfo);
validateAppuserLoanMapping(loanId);
return this.loanTransactionsApiResource.retrieveTransaction(loanId,
transactionId, uriInfo);
}
@GET
@Path("{loanId}/charges")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveAllLoanCharges(
@PathParam("loanId") final Long loanId,
@Context final UriInfo uriInfo) {
validateAppuserLoanMapping(loanId);
return this.loanChargesApiResource.retrieveAllLoanCharges(loanId,
uriInfo);
}
@GET
@Path("{loanId}/charges/{chargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveLoanCharge(@PathParam("loanId") final Long loanId,
@PathParam("chargeId") final Long loanChargeId,
@Context final UriInfo uriInfo) {
validateAppuserLoanMapping(loanId);
return this.retrieveLoanCharge(loanId, loanChargeId, uriInfo);
}
private void validateAppuserLoanMapping(final Long loanId) {
AppUser user = this.context.authenticatedUser();
final boolean isLoanMappedToUser = this.appuserLoansMapperReadService
.isLoanMappedToUser(loanId, user.getId());
if (!isLoanMappedToUser) {
throw new LoanNotFoundException(loanId);
}
}
}
@@ -0,0 +1,74 @@
/**
* 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.portfolio.self.loanaccount.data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.infrastructure.core.api.ApiParameterHelper;
import org.mifosplatform.infrastructure.core.exception.UnsupportedParameterException;
import org.springframework.stereotype.Component;
@Component
public class SelfLoansDataValidator {
private static final Set<String> allowedAssociationParameters = new HashSet<>(
Arrays.asList("repaymentSchedule", "futureSchedule",
"originalSchedule", "transactions", "charges",
"guarantors", "collateral", "linkedAccount",
"multiDisburseDetails"));
public void validateRetrieveLoan(final UriInfo uriInfo) {
List<String> unsupportedParams = new ArrayList<>();
validateTemplate(uriInfo, unsupportedParams);
Set<String> associationParameters = ApiParameterHelper
.extractAssociationsForResponseIfProvided(uriInfo
.getQueryParameters());
if (!associationParameters.isEmpty()) {
associationParameters.removeAll(allowedAssociationParameters);
if (!associationParameters.isEmpty()) {
unsupportedParams.addAll(associationParameters);
}
}
if (uriInfo.getQueryParameters().getFirst("exclude") != null) {
unsupportedParams.add("exclude");
}
throwExceptionIfReqd(unsupportedParams);
}
public void validateRetrieveTransaction(UriInfo uriInfo) {
List<String> unsupportedParams = new ArrayList<>();
validateTemplate(uriInfo, unsupportedParams);
throwExceptionIfReqd(unsupportedParams);
}
private void throwExceptionIfReqd(List<String> unsupportedParams) {
if (unsupportedParams.size() > 0) {
throw new UnsupportedParameterException(unsupportedParams);
}
}
private void validateTemplate(final UriInfo uriInfo,
List<String> unsupportedParams) {
final boolean templateRequest = ApiParameterHelper.template(uriInfo
.getQueryParameters());
if (templateRequest) {
unsupportedParams.add("template");
}
}
}
@@ -0,0 +1,11 @@
/**
* 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.portfolio.self.loanaccount.service;
public interface AppuserLoansMapperReadService {
public Boolean isLoanMappedToUser(Long loanId, Long appUserId);
}
@@ -0,0 +1,35 @@
/**
* 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.portfolio.self.loanaccount.service;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class AppuserLoansMapperReadServiceImpl implements
AppuserLoansMapperReadService {
private final JdbcTemplate jdbcTemplate;
@Autowired
public AppuserLoansMapperReadServiceImpl(final RoutingDataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Boolean isLoanMappedToUser(Long loanId, Long appUserId) {
return this.jdbcTemplate
.queryForObject(
"select case when (count(*) > 0) then true else false end "
+ " from m_selfservice_user_client_mapping as m "
+ " left join m_loan as l on l.client_id = m.client_id "
+ " where l.id = ? and m.appuser_id = ? ",
new Object[] { loanId, appUserId }, Boolean.class);
}
}
@@ -0,0 +1,135 @@
/**
* 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.portfolio.self.savings.api;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.savings.api.SavingsAccountChargesApiResource;
import org.mifosplatform.portfolio.savings.api.SavingsAccountTransactionsApiResource;
import org.mifosplatform.portfolio.savings.api.SavingsAccountsApiResource;
import org.mifosplatform.portfolio.savings.exception.SavingsAccountNotFoundException;
import org.mifosplatform.portfolio.self.savings.data.SelfSavingsDataValidator;
import org.mifosplatform.portfolio.self.savings.service.AppuserSavingsMapperReadService;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/self/savingsaccounts")
@Component
@Scope("singleton")
public class SelfSavingsApiResource {
private final PlatformSecurityContext context;
private final SavingsAccountsApiResource savingsAccountsApiResource;
private final SavingsAccountChargesApiResource savingsAccountChargesApiResource;
private final SavingsAccountTransactionsApiResource savingsAccountTransactionsApiResource;
private final AppuserSavingsMapperReadService appuserSavingsMapperReadService;
private final SelfSavingsDataValidator dataValidator;
@Autowired
public SelfSavingsApiResource(
final PlatformSecurityContext context,
final SavingsAccountsApiResource savingsAccountsApiResource,
final SavingsAccountChargesApiResource savingsAccountChargesApiResource,
final SavingsAccountTransactionsApiResource savingsAccountTransactionsApiResource,
final AppuserSavingsMapperReadService appuserSavingsMapperReadService,
final SelfSavingsDataValidator dataValidator) {
this.context = context;
this.savingsAccountsApiResource = savingsAccountsApiResource;
this.savingsAccountChargesApiResource = savingsAccountChargesApiResource;
this.savingsAccountTransactionsApiResource = savingsAccountTransactionsApiResource;
this.appuserSavingsMapperReadService = appuserSavingsMapperReadService;
this.dataValidator = dataValidator;
}
@GET
@Path("{accountId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveSavings(
@PathParam("accountId") final Long accountId,
@DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus,
@Context final UriInfo uriInfo) {
this.dataValidator.validateRetrieveSavings(uriInfo);
validateAppuserSavingsAccountMapping(accountId);
final boolean staffInSelectedOfficeOnly = false;
return this.savingsAccountsApiResource.retrieveOne(accountId,
staffInSelectedOfficeOnly, chargeStatus, uriInfo);
}
@GET
@Path("{accountId}/transactions/{transactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveSavingsTransaction(
@PathParam("savingsId") final Long accountId,
@PathParam("transactionId") final Long transactionId,
@Context final UriInfo uriInfo) {
this.dataValidator.validateRetrieveSavingsTransaction(uriInfo);
validateAppuserSavingsAccountMapping(accountId);
return this.savingsAccountTransactionsApiResource.retrieveOne(
accountId, transactionId, uriInfo);
}
@GET
@Path("{accountId}/charges")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveAllSavingsAccountCharges(
@PathParam("savingsAccountId") final Long accountId,
@DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus,
@Context final UriInfo uriInfo) {
validateAppuserSavingsAccountMapping(accountId);
return this.savingsAccountChargesApiResource
.retrieveAllSavingsAccountCharges(accountId, chargeStatus,
uriInfo);
}
@GET
@Path("{accountId}/charges/{savingsAccountChargeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveSavingsAccountCharge(
@PathParam("savingsAccountId") final Long accountId,
@PathParam("savingsAccountChargeId") final Long savingsAccountChargeId,
@Context final UriInfo uriInfo) {
validateAppuserSavingsAccountMapping(accountId);
return this.savingsAccountChargesApiResource
.retrieveSavingsAccountCharge(accountId,
savingsAccountChargeId, uriInfo);
}
private void validateAppuserSavingsAccountMapping(final Long accountId) {
AppUser user = this.context.authenticatedUser();
final boolean isMappedSavings = this.appuserSavingsMapperReadService
.isSavingsMappedToUser(accountId, user.getId());
if (!isMappedSavings) {
throw new SavingsAccountNotFoundException(accountId);
}
}
}
@@ -0,0 +1,73 @@
/**
* 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.portfolio.self.savings.data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.infrastructure.core.api.ApiParameterHelper;
import org.mifosplatform.infrastructure.core.exception.UnsupportedParameterException;
import org.mifosplatform.portfolio.savings.SavingsApiConstants;
import org.springframework.stereotype.Component;
@Component
public class SelfSavingsDataValidator {
private static final Set<String> allowedAssociationParameters = new HashSet<>(
Arrays.asList(SavingsApiConstants.transactions,
SavingsApiConstants.charges));
public void validateRetrieveSavings(final UriInfo uriInfo) {
List<String> unsupportedParams = new ArrayList<>();
validateTemplate(uriInfo, unsupportedParams);
Set<String> associationParameters = ApiParameterHelper
.extractAssociationsForResponseIfProvided(uriInfo
.getQueryParameters());
if (!associationParameters.isEmpty()) {
associationParameters.removeAll(allowedAssociationParameters);
if (!associationParameters.isEmpty()) {
unsupportedParams.addAll(associationParameters);
}
}
if (uriInfo.getQueryParameters().getFirst("exclude") != null) {
unsupportedParams.add("exclude");
}
throwExceptionIfReqd(unsupportedParams);
}
public void validateRetrieveSavingsTransaction(final UriInfo uriInfo) {
List<String> unsupportedParams = new ArrayList<>();
validateTemplate(uriInfo, unsupportedParams);
throwExceptionIfReqd(unsupportedParams);
}
private void throwExceptionIfReqd(final List<String> unsupportedParams) {
if (unsupportedParams.size() > 0) {
throw new UnsupportedParameterException(unsupportedParams);
}
}
private void validateTemplate(final UriInfo uriInfo,
List<String> unsupportedParams) {
final boolean templateRequest = ApiParameterHelper.template(uriInfo
.getQueryParameters());
if (templateRequest) {
unsupportedParams.add("template");
}
}
}
@@ -0,0 +1,12 @@
/**
* 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.portfolio.self.savings.service;
public interface AppuserSavingsMapperReadService {
public Boolean isSavingsMappedToUser(Long savingsId, Long appUserId);
}
@@ -0,0 +1,35 @@
/**
* 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.portfolio.self.savings.service;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class AppuserSavingsMapperReadServiceImpl implements
AppuserSavingsMapperReadService {
private final JdbcTemplate jdbcTemplate;
@Autowired
public AppuserSavingsMapperReadServiceImpl(
final RoutingDataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Boolean isSavingsMappedToUser(Long savingsId, Long appUserId) {
return this.jdbcTemplate
.queryForObject(
"select case when (count(*) > 0) then true else false end "
+ " from m_selfservice_user_client_mapping as m "
+ " left join m_savings_account as s on s.client_id = m.client_id "
+ " where s.id = ? and m.appuser_id = ? ",
new Object[] { savingsId, appUserId }, Boolean.class);
}
}
@@ -0,0 +1,41 @@
/**
* 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.portfolio.self.security.api;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.mifosplatform.infrastructure.security.api.AuthenticationApiResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/self/authentication")
@Component
@Profile("basicauth")
@Scope("singleton")
public class SelfAuthenticationApiResource {
private final AuthenticationApiResource authenticationApiResource;
@Autowired
public SelfAuthenticationApiResource(
final AuthenticationApiResource authenticationApiResource) {
this.authenticationApiResource = authenticationApiResource;
}
@POST
@Produces({ MediaType.APPLICATION_JSON })
public String authenticate(@QueryParam("username") final String username,
@QueryParam("password") final String password) {
return this.authenticationApiResource.authenticate(username, password);
}
}
@@ -0,0 +1,41 @@
/**
* 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.portfolio.self.security.api;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.mifosplatform.infrastructure.security.api.UserDetailsApiResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/self/userdetails")
@Component
@Profile("oauth")
@Scope("singleton")
public class SelfUserDetailsApiResource {
private final UserDetailsApiResource userDetailsApiResource;
@Autowired
public SelfUserDetailsApiResource(
final UserDetailsApiResource userDetailsApiResource) {
this.userDetailsApiResource = userDetailsApiResource;
}
@GET
@Produces({ MediaType.APPLICATION_JSON })
public String fetchAuthenticatedUserData(
@QueryParam("access_token") final String accessToken) {
return this.userDetailsApiResource
.fetchAuthenticatedUserData(accessToken);
}
}
@@ -6,9 +6,11 @@
package org.mifosplatform.useradministration.data;
import java.util.Collection;
import java.util.Set;
import org.mifosplatform.organisation.office.data.OfficeData;
import org.mifosplatform.organisation.staff.data.StaffData;
import org.mifosplatform.portfolio.client.data.ClientData;
/**
* Immutable data object for application user data.
@@ -29,31 +31,35 @@ public class AppUserData {
private final Collection<RoleData> availableRoles;
private final Collection<RoleData> selectedRoles;
private final StaffData staff;
private final Boolean isSelfServiceUser;
@SuppressWarnings("unused")
private Set<ClientData> clients;
public static AppUserData template(final AppUserData user, final Collection<OfficeData> officesForDropdown) {
return new AppUserData(user.id, user.username, user.email, user.officeId, user.officeName, user.firstname, user.lastname,
user.availableRoles, user.selectedRoles, officesForDropdown, user.staff, user.passwordNeverExpires);
user.availableRoles, user.selectedRoles, officesForDropdown, user.staff, user.passwordNeverExpires, user.isSelfServiceUser);
}
public static AppUserData template(final Collection<OfficeData> offices, final Collection<RoleData> availableRoles) {
return new AppUserData(null, null, null, null, null, null, null, availableRoles, null, offices, null, null);
return new AppUserData(null, null, null, null, null, null, null, availableRoles, null, offices, null, null, null);
}
public static AppUserData dropdown(final Long id, final String username) {
return new AppUserData(id, username, null, null, null, null, null, null, null, null, null, null);
return new AppUserData(id, username, null, null, null, null, null, null, null, null, null, null, null);
}
public static AppUserData instance(final Long id, final String username, final String email, final Long officeId,
final String officeName, final String firstname, final String lastname, final Collection<RoleData> availableRoles,
final Collection<RoleData> selectedRoles, final StaffData staff, final Boolean passwordNeverExpire) {
final Collection<RoleData> selectedRoles, final StaffData staff, final Boolean passwordNeverExpire, final Boolean isSelfServiceUser) {
return new AppUserData(id, username, email, officeId, officeName, firstname, lastname, availableRoles, selectedRoles, null, staff,
passwordNeverExpire);
passwordNeverExpire, isSelfServiceUser);
}
private AppUserData(final Long id, final String username, final String email, final Long officeId, final String officeName,
final String firstname, final String lastname, final Collection<RoleData> availableRoles,
final Collection<RoleData> selectedRoles, final Collection<OfficeData> allowedOffices, final StaffData staff,
final Boolean passwordNeverExpire) {
final Boolean passwordNeverExpire, final Boolean isSelfServiceUser) {
this.id = id;
this.username = username;
this.officeId = officeId;
@@ -66,6 +72,7 @@ public class AppUserData {
this.selectedRoles = selectedRoles;
this.staff = staff;
this.passwordNeverExpires = passwordNeverExpire;
this.isSelfServiceUser = isSelfServiceUser;
}
public boolean hasIdentifyOf(final Long createdById) {
@@ -92,4 +99,13 @@ public class AppUserData {
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
public void setClients(Set<ClientData> clients){
this.clients = clients;
}
public boolean isSelfServiceUser() {
return this.isSelfServiceUser==null?false:this.isSelfServiceUser;
}
}
@@ -14,6 +14,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@@ -21,11 +22,14 @@ import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.service.DateUtils;
@@ -35,6 +39,7 @@ import org.mifosplatform.infrastructure.security.service.PlatformPasswordEncoder
import org.mifosplatform.infrastructure.security.service.RandomPasswordGenerator;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.useradministration.service.AppUserConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -101,7 +106,16 @@ public class AppUser extends AbstractPersistable<Long> implements PlatformUser {
@Column(name = "password_never_expires", nullable = false)
private boolean passwordNeverExpires;
public static AppUser fromJson(final Office userOffice, final Staff linkedStaff, final Set<Role> allRoles, final JsonCommand command) {
@Column(name = "is_self_service_user", nullable = false)
private boolean isSelfServiceUser;
@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "appuser_id", referencedColumnName= "id", nullable = false)
private Set<AppUserClientMapping> appUserClientMappings = new HashSet<>();
public static AppUser fromJson(final Office userOffice, final Staff linkedStaff, final Set<Role> allRoles,
final Collection<Client> clients, final JsonCommand command) {
final String username = command.stringValueOfParameterNamed("username");
String password = command.stringValueOfParameterNamed("password");
@@ -131,8 +145,11 @@ public class AppUser extends AbstractPersistable<Long> implements PlatformUser {
final String email = command.stringValueOfParameterNamed("email");
final String firstname = command.stringValueOfParameterNamed("firstname");
final String lastname = command.stringValueOfParameterNamed("lastname");
final boolean isSelfServiceUser = command.booleanPrimitiveValueOfParameterNamed(AppUserConstants.IS_SELF_SERVICE_USER);
return new AppUser(userOffice, user, allRoles, email, firstname, lastname, linkedStaff, passwordNeverExpire);
return new AppUser(userOffice, user, allRoles, email, firstname, lastname, linkedStaff, passwordNeverExpire,
isSelfServiceUser, clients);
}
protected AppUser() {
@@ -142,7 +159,8 @@ public class AppUser extends AbstractPersistable<Long> implements PlatformUser {
}
public AppUser(final Office office, final User user, final Set<Role> roles, final String email, final String firstname,
final String lastname, final Staff staff, final boolean passwordNeverExpire) {
final String lastname, final Staff staff, final boolean passwordNeverExpire,
final boolean isSelfServiceUser, final Collection<Client> clients) {
this.office = office;
this.email = email.trim();
this.username = user.getUsername().trim();
@@ -158,6 +176,8 @@ public class AppUser extends AbstractPersistable<Long> implements PlatformUser {
this.lastTimePasswordUpdated = DateUtils.getDateOfTenant();
this.staff = staff;
this.passwordNeverExpires = passwordNeverExpire;
this.isSelfServiceUser = isSelfServiceUser;
this.appUserClientMappings = createAppUserClientMappings(clients);
}
public EnumOptionData organisationalRoleData() {
@@ -190,7 +210,8 @@ public class AppUser extends AbstractPersistable<Long> implements PlatformUser {
}
}
public Map<String, Object> update(final JsonCommand command, final PlatformPasswordEncoder platformPasswordEncoder) {
public Map<String, Object> update(final JsonCommand command, final PlatformPasswordEncoder platformPasswordEncoder,
final Collection<Client> clients) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
@@ -270,6 +291,30 @@ public class AppUser extends AbstractPersistable<Long> implements PlatformUser {
this.passwordNeverExpires = newValue;
}
}
if(command.hasParameter(AppUserConstants.IS_SELF_SERVICE_USER)){
if (command.isChangeInBooleanParameterNamed(AppUserConstants.IS_SELF_SERVICE_USER, this.isSelfServiceUser)){
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(AppUserConstants.IS_SELF_SERVICE_USER);
actualChanges.put(AppUserConstants.IS_SELF_SERVICE_USER, newValue);
this.isSelfServiceUser = newValue;
}
}
if(this.isSelfServiceUser && command.hasParameter(AppUserConstants.CLIENTS)){
actualChanges.put(AppUserConstants.CLIENTS, command.arrayValueOfParameterNamed(AppUserConstants.CLIENTS));
Set<AppUserClientMapping> newClients = createAppUserClientMappings(clients);
if(this.appUserClientMappings == null){
this.appUserClientMappings = new HashSet<>();
}else{
this.appUserClientMappings.retainAll(newClients);
}
this.appUserClientMappings.addAll(newClients);
}else if(!this.isSelfServiceUser && actualChanges.containsKey(AppUserConstants.IS_SELF_SERVICE_USER)){
actualChanges.put(AppUserConstants.CLIENTS, new ArrayList<>());
if(this.appUserClientMappings != null){
this.appUserClientMappings = null;
}
}
return actualChanges;
}
@@ -573,4 +618,24 @@ public class AppUser extends AbstractPersistable<Long> implements PlatformUser {
public boolean isNotEnabled() {
return !isEnabled();
}
public boolean isSelfServiceUser() {
return this.isSelfServiceUser;
}
public Set<AppUserClientMapping> getAppUserClientMappings() {
return this.appUserClientMappings;
}
private Set<AppUserClientMapping> createAppUserClientMappings(Collection<Client> clients) {
Set<AppUserClientMapping> newAppUserClientMappings = null;
if(clients != null && clients.size() > 0){
newAppUserClientMappings = new HashSet<>();
for(Client client : clients){
newAppUserClientMappings.add(new AppUserClientMapping(client));
}
}
return newAppUserClientMappings;
}
}
@@ -0,0 +1,67 @@
/**
* 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 javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.mifosplatform.portfolio.client.domain.Client;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_selfservice_user_client_mapping")
public class AppUserClientMapping extends AbstractPersistable<Long> {
@ManyToOne(optional = false, cascade = CascadeType.PERSIST)
@JoinColumn(name = "client_id", nullable = false)
private Client client;
public AppUserClientMapping(){
}
public AppUserClientMapping(Client client){
this.client = client;
}
public Client getClient() {
return this.client;
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (this == obj) {
return true;
}
if (!getClass().equals(obj.getClass())) {
return false;
}
AppUserClientMapping that = (AppUserClientMapping) obj;
return null == this.client.getId() ? false : this.client.getId().equals(that.client.getId());
}
@Override
public int hashCode() {
int hashCode = 17;
hashCode += null == this.client ? 0 : this.client.getId().hashCode() * 31;
return hashCode;
}
}
@@ -8,5 +8,7 @@ package org.mifosplatform.useradministration.service;
public class AppUserConstants {
public static final String PASSWORD_NEVER_EXPIRES = "passwordNeverExpires";
public static final String IS_SELF_SERVICE_USER = "isSelfServiceUser";
public static final String CLIENTS = "clients";
}
@@ -9,6 +9,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
@@ -18,9 +19,12 @@ import org.mifosplatform.organisation.office.data.OfficeData;
import org.mifosplatform.organisation.office.service.OfficeReadPlatformService;
import org.mifosplatform.organisation.staff.data.StaffData;
import org.mifosplatform.organisation.staff.service.StaffReadPlatformService;
import org.mifosplatform.portfolio.client.data.ClientData;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.useradministration.data.AppUserData;
import org.mifosplatform.useradministration.data.RoleData;
import org.mifosplatform.useradministration.domain.AppUser;
import org.mifosplatform.useradministration.domain.AppUserClientMapping;
import org.mifosplatform.useradministration.domain.AppUserRepository;
import org.mifosplatform.useradministration.domain.Role;
import org.mifosplatform.useradministration.exception.UserNotFoundException;
@@ -119,9 +123,21 @@ public class AppUserReadPlatformServiceImpl implements AppUserReadPlatformServic
linkedStaff = null;
}
return AppUserData.instance(user.getId(), user.getUsername(), user.getEmail(), user.getOffice().getId(),
AppUserData retUser = AppUserData.instance(user.getId(), user.getUsername(), user.getEmail(), user.getOffice().getId(),
user.getOffice().getName(), user.getFirstname(), user.getLastname(), availableRoles, selectedUserRoles, linkedStaff,
user.getPasswordNeverExpires());
user.getPasswordNeverExpires(), user.isSelfServiceUser());
if(retUser.isSelfServiceUser()){
Set<ClientData> clients = new HashSet<>();
for(AppUserClientMapping clientMap : user.getAppUserClientMappings()){
Client client = clientMap.getClient();
clients.add(ClientData.lookup(client.getId(), client.getDisplayName(),
client.getOffice().getId(), client.getOffice().getName()));
}
retUser.setClients(clients);
}
return retUser;
}
private static final class AppUserMapper implements RowMapper<AppUserData> {
@@ -146,6 +162,7 @@ public class AppUserReadPlatformServiceImpl implements AppUserReadPlatformServic
final String officeName = rs.getString("officeName");
final Long staffId = JdbcSupport.getLong(rs, "staffId");
final Boolean passwordNeverExpire = rs.getBoolean("passwordNeverExpires");
final Boolean isSelfServiceUser = rs.getBoolean("isSelfServiceUser");
final Collection<RoleData> selectedRoles = this.roleReadPlatformService.retrieveAppUserRoles(id);
final StaffData linkedStaff;
@@ -155,12 +172,12 @@ public class AppUserReadPlatformServiceImpl implements AppUserReadPlatformServic
linkedStaff = null;
}
return AppUserData.instance(id, username, email, officeId, officeName, firstname, lastname, null, selectedRoles, linkedStaff,
passwordNeverExpire);
passwordNeverExpire, isSelfServiceUser);
}
public String schema() {
return " u.id as id, u.username as username, u.firstname as firstname, u.lastname as lastname, u.email as email, u.password_never_expires as passwordNeverExpires, "
+ " u.office_id as officeId, o.name as officeName, u.staff_id as staffId from m_appuser u "
+ " u.office_id as officeId, o.name as officeName, u.staff_id as staffId, u.is_self_service_user as isSelfServiceUser from m_appuser u "
+ " join m_office o on o.id = u.office_id where o.hierarchy like ? and u.is_deleted=0 order by u.username";
}
@@ -6,6 +6,7 @@
package org.mifosplatform.useradministration.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -26,6 +27,8 @@ import org.mifosplatform.organisation.office.domain.OfficeRepository;
import org.mifosplatform.organisation.office.exception.OfficeNotFoundException;
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.organisation.staff.domain.StaffRepositoryWrapper;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepository;
import org.mifosplatform.useradministration.api.AppUserApiConstant;
import org.mifosplatform.useradministration.domain.AppUser;
import org.mifosplatform.useradministration.domain.AppUserPreviousPassword;
@@ -49,6 +52,9 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
@Service
public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWritePlatformService {
@@ -63,12 +69,14 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
private final UserDataValidator fromApiJsonDeserializer;
private final AppUserPreviousPasswordRepository appUserPreviewPasswordRepository;
private final StaffRepositoryWrapper staffRepositoryWrapper;
private final ClientRepository clientRepository;
@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 AppUserPreviousPasswordRepository appUserPreviewPasswordRepository, final StaffRepositoryWrapper staffRepositoryWrapper) {
final AppUserPreviousPasswordRepository appUserPreviewPasswordRepository, final StaffRepositoryWrapper staffRepositoryWrapper,
final ClientRepository clientRepository) {
this.context = context;
this.appUserRepository = appUserRepository;
this.userDomainService = userDomainService;
@@ -78,6 +86,7 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.appUserPreviewPasswordRepository = appUserPreviewPasswordRepository;
this.staffRepositoryWrapper = staffRepositoryWrapper;
this.clientRepository = clientRepository;
}
@Transactional
@@ -108,8 +117,20 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
if (staffId != null) {
linkedStaff = this.staffRepositoryWrapper.findByOfficeWithNotFoundDetection(staffId, userOffice.getId());
}
Collection<Client> clients = null;
if(command.hasParameter(AppUserConstants.IS_SELF_SERVICE_USER)
&& command.booleanPrimitiveValueOfParameterNamed(AppUserConstants.IS_SELF_SERVICE_USER)
&& command.hasParameter(AppUserConstants.CLIENTS)){
JsonArray clientsArray = command.arrayOfParameterNamed(AppUserConstants.CLIENTS);
Collection<Long> clientIds = new HashSet<>();
for(JsonElement clientElement : clientsArray){
clientIds.add(clientElement.getAsLong());
}
clients = this.clientRepository.findAll(clientIds);
}
appUser = AppUser.fromJson(userOffice, linkedStaff, allRoles, command);
appUser = AppUser.fromJson(userOffice, linkedStaff, allRoles, clients, command);
final Boolean sendPasswordToEmail = command.booleanObjectValueOfParameterNamed("sendPasswordToEmail");
this.userDomainService.create(appUser, sendPasswordToEmail);
@@ -151,8 +172,24 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit
if (userToUpdate == null) { throw new UserNotFoundException(userId); }
final AppUserPreviousPassword currentPasswordToSaveAsPreview = getCurrentPasswordToSaveAsPreview(userToUpdate, command);
Collection<Client> clients = null;
boolean isSelfServiceUser = userToUpdate.isSelfServiceUser();
if(command.hasParameter(AppUserConstants.IS_SELF_SERVICE_USER)){
isSelfServiceUser = command.booleanPrimitiveValueOfParameterNamed(AppUserConstants.IS_SELF_SERVICE_USER);
}
if(isSelfServiceUser
&& command.hasParameter(AppUserConstants.CLIENTS)){
JsonArray clientsArray = command.arrayOfParameterNamed(AppUserConstants.CLIENTS);
Collection<Long> clientIds = new HashSet<>();
for(JsonElement clientElement : clientsArray){
clientIds.add(clientElement.getAsLong());
}
clients = this.clientRepository.findAll(clientIds);
}
final Map<String, Object> changes = userToUpdate.update(command, this.platformPasswordEncoder);
final Map<String, Object> changes = userToUpdate.update(command, this.platformPasswordEncoder, clients);
if (changes.containsKey("officeId")) {
final Long officeId = (Long) changes.get("officeId");
@@ -24,6 +24,7 @@ import org.mifosplatform.useradministration.domain.PasswordValidationPolicyRepos
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
@@ -34,7 +35,8 @@ public final class UserDataValidator {
* The parameters supported for this command.
*/
private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("username", "firstname", "lastname", "password",
"repeatPassword", "email", "officeId", "notSelectedRoles", "roles", "sendPasswordToEmail", "staffId", "passwordNeverExpires"));
"repeatPassword", "email", "officeId", "notSelectedRoles", "roles", "sendPasswordToEmail", "staffId", "passwordNeverExpires",
AppUserConstants.IS_SELF_SERVICE_USER, AppUserConstants.CLIENTS));
private final FromJsonHelper fromApiJsonHelper;
@@ -100,6 +102,29 @@ public final class UserDataValidator {
.extractBooleanNamed(AppUserConstants.PASSWORD_NEVER_EXPIRES, element);
baseDataValidator.reset().parameter("passwordNeverExpire").value(passwordNeverExpire).validateForBooleanValue();
}
Boolean isSelfServiceUser = null;
if(this.fromApiJsonHelper.parameterExists(AppUserConstants.IS_SELF_SERVICE_USER, element)){
isSelfServiceUser = this.fromApiJsonHelper.extractBooleanNamed(AppUserConstants.IS_SELF_SERVICE_USER, element);
if(isSelfServiceUser == null){
baseDataValidator.reset().parameter(AppUserConstants.IS_SELF_SERVICE_USER).trueOrFalseRequired(false);
}
}
if(this.fromApiJsonHelper.parameterExists(AppUserConstants.CLIENTS, element)){
if(isSelfServiceUser == null || !isSelfServiceUser){
baseDataValidator.reset().parameter(AppUserConstants.CLIENTS).failWithCode("not.supported.when.isSelfServiceUser.is.false",
"clients parameter is not supported when isSelfServiceUser parameter is false");
}else{
final JsonArray clientsArray = this.fromApiJsonHelper.extractJsonArrayNamed(AppUserConstants.CLIENTS, element);
baseDataValidator.reset().parameter(AppUserConstants.CLIENTS).value(clientsArray).jsonArrayNotEmpty();
for(JsonElement client : clientsArray){
Long clientId = client.getAsLong();
baseDataValidator.reset().parameter(AppUserConstants.CLIENTS).value(clientId).longGreaterThanZero();
}
}
}
final String[] roles = this.fromApiJsonHelper.extractArrayNamed("roles", element);
baseDataValidator.reset().parameter("roles").value(roles).arrayNotEmpty();
@@ -175,6 +200,29 @@ public final class UserDataValidator {
final boolean passwordNeverExpire = this.fromApiJsonHelper.extractBooleanNamed("passwordNeverExpire", element);
baseDataValidator.reset().parameter("passwordNeverExpire").value(passwordNeverExpire).validateForBooleanValue();
}
Boolean isSelfServiceUser = null;
if(this.fromApiJsonHelper.parameterExists(AppUserConstants.IS_SELF_SERVICE_USER, element)){
isSelfServiceUser = this.fromApiJsonHelper.extractBooleanNamed(AppUserConstants.IS_SELF_SERVICE_USER, element);
if(isSelfServiceUser == null){
baseDataValidator.reset().parameter(AppUserConstants.IS_SELF_SERVICE_USER).trueOrFalseRequired(false);
}
}
if(this.fromApiJsonHelper.parameterExists(AppUserConstants.CLIENTS, element)){
if(isSelfServiceUser != null && !isSelfServiceUser){
baseDataValidator.reset().parameter(AppUserConstants.CLIENTS).failWithCode("not.supported.when.isSelfServiceUser.is.false",
"clients parameter is not supported when isSelfServiceUser parameter is false");
}else{
final JsonArray clientsArray = this.fromApiJsonHelper.extractJsonArrayNamed(AppUserConstants.CLIENTS, element);
baseDataValidator.reset().parameter(AppUserConstants.CLIENTS).value(clientsArray).jsonArrayNotEmpty();
for(JsonElement client : clientsArray){
Long clientId = client.getAsLong();
baseDataValidator.reset().parameter(AppUserConstants.CLIENTS).value(clientId).longGreaterThanZero();
}
}
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -25,6 +25,8 @@
entry-point-ref="basicAuthenticationEntryPoint">
<intercept-url pattern="/api/*/authentication" access="permitAll"
method="POST" requires-channel="https" />
<intercept-url pattern="/api/*/self/authentication" access="permitAll"
method="POST" requires-channel="https" />
<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
method="GET" requires-channel="https" />
<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
@@ -132,6 +134,8 @@
class="org.springframework.security.access.vote.AuthenticatedVoter" />
<beans:bean
class="org.springframework.security.web.access.expression.WebExpressionVoter" />
<beans:bean
class="org.mifosplatform.infrastructure.security.vote.SelfServiceUserAccessVote" />
</beans:list>
</beans:constructor-arg>
</beans:bean>
@@ -0,0 +1,15 @@
ALTER TABLE `m_appuser`
ADD COLUMN `is_self_service_user` BIT(1) NOT NULL DEFAULT 0;
CREATE TABLE `m_selfservice_user_client_mapping` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`appuser_id` BIGINT(20) NOT NULL,
`client_id` BIGINT(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `appuser_id_client_id` (`appuser_id`, `client_id`),
CONSTRAINT `m_selfservice_appuser_id` FOREIGN KEY (`appuser_id`) REFERENCES `m_appuser` (`id`),
CONSTRAINT `m_selfservice_client_id` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1;