diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 833aa2445..03deae638 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -937,9 +937,8 @@ Permission permissions - List Application - Permissions - + List Application Permissions + Enable/Disable Permissions for Maker Checker @@ -7055,8 +7054,17 @@ Content-Type: application/json

Audits

-

-

+

Every non-read Mifos API request is audited. A fully processed request can + not be changed or deleted. See maker checker api for situations where an audit is not + fully processed.

+ + Permissions: To search and look at audit entries a user needs to be attached to a role + that has one of the ALL_FUNCTIONS, ALL_FUNCTIONS_READ or READ_AUDIT permissions.

+ + Data Scope: A user can only see audits that are within their data scope. However, + 'head office' users can see all audits including those that aren't office/branch related + e.g. Loan Product changes. +

@@ -7065,9 +7073,106 @@ Content-Type: application/json

List Audits

-

+

Get a list of audits that match the criteria supplied, + and are within the requestors' data scope.

+
Arguments
+
+
actionName
+
optional
+
Examples: CREATE, UPDATE, DISBURSE
+ +
entityName
+
optional
+
Examples: CLIENT, LOAN, FUND
+ +
resourceId
+
optional, + The id value of the entityName +
+ +
makerId
+
optional, + The id value of the application user creating the entry +
+ +
makerDateTimeFrom
+
optional, + Get entries created on or after this +
+
Example: 2013-04-10 08:00:00
+ +
makerDateTimeTo
+
optional, + Get entries created on or before this +
+
Example: 2013-05-10 08:00:00
+ +
checkerId
+
optional, + The id value of the application user that checked (approved) the entry +
+ +
checkerDateTimeFrom
+
optional, + Get entries checked on or after this +
+
Example: 2013-04-10 08:00:00
+ +
checkerDateTimeTo
+
optional, + Get entries checked on or before this +
+
Example: 2013-05-10 18:00:00
+ +
processingResult
+
optional, + The enum value of the processing status +
+
values:
+ 0: Invalid
+ 1: processed
+ 2: awaiting.approval
+ 3: rejected +
+ +
officeId
+
optional, + The id value of the office/branch associated with the entry (if there is one) +
+ +
groupId
+
optional, + The id value of the group associated with the entry (if there is one). + A group is a general idea and may be a Center, a Group, a Communal Bank or other. +
+ +
clientId
+
optional, + The id value of the client associated with the entry (if there is one) +
+ +
loanId
+
optional, + The id value of the loan associated with the entry (if there is one) +
+ +
savingsAccountId
+
optional, + The id value of the savings account associated with the entry (if there is one) +
+ +
includeJson
+
optional, + Values are true, false. Default is false. +
+ +

Example Requests:

-
audits
+
audits

+
audits?fields=madeOnDate,maker,processingResult

+
audits?makerDateTimeFrom=2013-03-25 08:00:00&makerDateTimeTo=2013-04-04 18:00:00

+
audits?officeId=1

+
audits?officeId=1&includeJson=true
@@ -7156,7 +7261,6 @@ GET https://DomainName/api/v1/audits
-  
@@ -7262,8 +7366,212 @@ GET https://DomainName/api/v1/audits/searchtemplate
+   +
+
+

Retrieve an Audit Entry

+

+

Example Requests:

+
audits/20
+
audits/20?fields=madeOnDate,maker,processingResult
+
+
+ +GET https://DomainName/api/v1/audits + + +{ + "id": 20, + "actionName": "REPAYMENT", + "entityName": "LOAN", + "resourceId": 868, + "maker": "dataentry1", + "madeOnDate": 1358449025000, + "processingResult": "processed", + "commandAsJson": "{\"transactionDate\":\"28 September 2012\",\"transactionAmount\":\"1,967.00\",\"locale\":\"en\",\"dateFormat\":\"dd MMMM yyyy\"}", + "officeName": "another office", + "clientName": "another client", + "loanAccountNo": "23" +} + +
+
+ +   +
+
+

Maker Checker (or 4-eye) functionality

+

Mifos X Maker Checker functionality allows an MFI to define transactions + as having a maker and a checker phase. One user enters, deletes or changes data. + Then, another user that has "Checker" rights for that transaction, can inspect + and approve the data. +

+ +

By default, Maker Checker functionality is disabled. + See Update Global Configuration to enable/disable + Maker Checker functionality at a global level. +

+ +

+ Additionally, Maker Checker functionality for each transaction (permission) is disabled by default. + see Enable/Disable Permissions for Maker Checker to enable/disable + Maker Checker functionality at a transaction (permission) level. +

+ +

+ Finally, to give checking rights to a user (via a role associated with the user) see + Update a Role's Permissions +

+

For example,
+ +{ + "permissions":{ + "CREATE_GUARANTOR_CHECKER":true, + "CREATE_CLIENT_CHECKER":true + } +} + +
+ will give checking rights for CREATE_GUARANTOR and CREATE_CLIENT

+

Alternatively, the special permissions ALL_FUNCTIONS or CHECKER_SUPER_USER will give blanket + checking rights. +

+

+ When a user "makes" an entry that is enabled for Maker Checker, it is audited and the audit status + is set to a value of 2 (awaiting.approval). +

+ + Checkers can only Check and approve entries that they have been permitted to check.

+ + Checkers can only Check and approve entries that are within their data scope. However, + 'head office' Checkers can Check and approve all entries + including those that aren't office/branch related (as long as they have the Checker permissions) + e.g. Loan Product changes. +

+

+
+ +   +
+
+

List Maker Checker Entries

+

Get a list of entries that can be checked by the requestor that match the criteria supplied.

+
Arguments
+
+
actionName
+
optional
+
Examples: CREATE, UPDATE, DISBURSE
+ +
entityName
+
optional
+
Examples: CLIENT, LOAN, FUND
+ +
resourceId
+
optional, + The id value of the entityName +
+ +
makerId
+
optional, + The id value of the application user creating the entry +
+ +
makerDateTimeFrom
+
optional, + Get entries created on or after this +
+
Example: 2013-04-10 08:00:00
+ +
makerDateTimeTo
+
optional, + Get entries created on or before this +
+
Example: 2013-05-10 08:00:00
+ +
officeId
+
optional, + The id value of the office/branch associated with the entry (if there is one) +
+ +
groupId
+
optional, + The id value of the group associated with the entry (if there is one). + A group is a general idea and may be a Center, a Group, a Communal Bank or other. +
+ +
clientId
+
optional, + The id value of the client associated with the entry (if there is one) +
+ +
loanId
+
optional, + The id value of the loan associated with the entry (if there is one) +
+ +
savingsAccountId
+
optional, + The id value of the savings account associated with the entry (if there is one) +
+ +
includeJson
+
optional, + Values are true, false. Default is false. +
+ +
+

Example Requests:

+
makercheckers

+
makercheckers?fields=madeOnDate,maker,processingResult

+
makercheckers?makerDateTimeFrom=2013-03-25 08:00:00&makerDateTimeTo=2013-04-04 18:00:00

+
makercheckers?officeId=1

+
makercheckers?officeId=1&includeJson=true
+
+
+ +GET https://DomainName/api/v1/makercheckers + + +[ + { + "id": 654, + "actionName": "CREATE", + "entityName": "LOANPRODUCT", + "resourceId": 15, + "maker": "keithwoodlock", + "madeOnDate": 1364924512000, + "processingResult": "awaiting.approval" + }, + { + "id": 666, + "actionName": "CREATE", + "entityName": "CLIENT", + "resourceId": 363, + "maker": "keithwoodlock", + "madeOnDate": 1365012843000, + "processingResult": "awaiting.approval", + "officeName": "my office name" + }, + { + "id": 670, + "actionName": "CREATE", + "entityName": "CLIENTNOTE", + "resourceId": 287, + "maker": "keithwoodlock", + "madeOnDate": 1365014204000, + "processingResult": "awaiting.approval", + "officeName": "my office name", + "clientName": "gg ggg" + } +] + +
+
+  
@@ -7320,6 +7628,42 @@ GET https://DomainName/api/v1/audits/searchtemplate
+   +
+
+

Approve Maker Checker Entry

+
+
+ +POST https://DomainName/api/v1/makercheckers/{auditId}?command=approve + + +POST makercheckers/1?command=approve +Content-Type: application/json + + { "auditId": 1 } +
+
+ +   +
+
+

Delete Maker Checker Entry

+
+
+ +DELETE https://DomainName/api/v1/makercheckers/{auditId} + + +DELETE makercheckers/1 +Content-Type: application/json + + { "auditId": 1 } +
+
+ + +  
@@ -11324,6 +11668,9 @@ Request body:

Permissions

An API capability to support management of application permissions for user administration.

+

There is no Mifos X functionality for creating or deleting permissions. Permissions come pre-installed.

+

Permissions are not updated, except in the case of enabling or disabling non-read transactions for + Maker Checker functionality

@@ -11332,10 +11679,25 @@ Request body:

List Application Permissions

+
Arguments
+
+
makerCheckerable
+
optional, + Values are true, false. Default is false. +

If makerCheckerable=false or not supplied then a list of application permissions is returned. The "selected" + attribute is always true in this case. +

+

If makerCheckerable=true then the "selected" attribute shows whether the permission is + enabled for Maker Check functionality. +

+

+ Note: Each Mifos X transaction is associated with a permission. +

+
+

Example Requests:

-
permissions
-
-
+
permissions


+
permissions?makerCheckerable=true


permissions?fields=grouping,code
@@ -11364,6 +11726,30 @@ GET https://DomainName/api/v1/permissions
+   +
+
+

Enable/Disable Permissions for Maker Checker

+
+
+ +PUT https://DomainName/api/v1/permissions + + +PUT permissions +Content-Type: application/json +Request Body: +{ + "permissions":{ + "CREATE_GUARANTOR":true, + "CREATE_CLIENT":true + } +} + +
+
+ +  
@@ -12985,46 +13371,46 @@ GET https://DomainName/api/v1/search?query=000000111 Update a Code Value Delete a Code Value - - Audits - audits - - List Audits - - - - - - audits/{auditId} - - Retrieve an Audit Entry - - - - - Makercheckers - makercheckers - - List Maker Checker Entries - - - - - - makercheckers/{auditId} - - - - Delete Maker Checker Entry - - - - makercheckers/{auditId}?command=approve - Approve Maker Checker Entry - - - - + + Audits + audits + + List Audits + + + + + + audits/{auditId} + + Retrieve an Audit Entry + + + + + Makercheckers + makercheckers + + List Maker Checker Entries + + + + + + makercheckers/{auditId} + + + + Delete Maker Checker Entry + + + + makercheckers/{auditId}?command=approve + Approve Maker Checker Entry + + + + Funds funds @@ -13297,8 +13683,8 @@ GET https://DomainName/api/v1/search?query=000000111 Permission permissions - List Permissions - + List Application Permissions + Enable/Disable Permissions for Maker Checker diff --git a/mifosng-db/migrations/core_db/V7__remove_read_makerchecker_permission.sql b/mifosng-db/migrations/core_db/V7__remove_read_makerchecker_permission.sql new file mode 100644 index 000000000..62f6e4b97 --- /dev/null +++ b/mifosng-db/migrations/core_db/V7__remove_read_makerchecker_permission.sql @@ -0,0 +1,15 @@ + +/* +Remove READ_MAKERCHECKER permission as no longer used. +*/ + +delete from m_role_permission +where exists +(select 'f' from m_permission p +where p.id = m_role_permission.permission_id and p.code = 'READ_MAKERCHECKER'); + + +delete from m_permission +where code = 'READ_MAKERCHECKER'; + + \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/api/MakercheckersApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/api/MakercheckersApiResource.java index ea56aa620..8c47d705c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/api/MakercheckersApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/api/MakercheckersApiResource.java @@ -34,7 +34,6 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException; import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; -import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -50,9 +49,10 @@ public class MakercheckersApiResource { "checkedOnDate", "processingResult", "commandAsJson", "officeName", "groupLevelName", "groupName", "clientName", "loanAccountNo", "savingsAccountNo")); - private final String resourceNameForPermissions = "MAKERCHECKER"; + + //Next line removed because read permissions checks part of read services + //private final String resourceNameForPermissions = "MAKERCHECKER"; - private final PlatformSecurityContext context; private final AuditReadPlatformService readPlatformService; private final DefaultToApiJsonSerializer toApiJsonSerializer; private final DefaultToApiJsonSerializer toApiJsonSerializerAudit; @@ -62,14 +62,12 @@ public class MakercheckersApiResource { @Autowired public MakercheckersApiResource( - final PlatformSecurityContext context, final AuditReadPlatformService readPlatformService, final DefaultToApiJsonSerializer toApiJsonSerializer, final DefaultToApiJsonSerializer toApiJsonSerializerAudit, final DefaultToApiJsonSerializer toApiJsonSerializerSearchTemplate, final ApiRequestParameterHelper apiRequestParameterHelper, final PortfolioCommandSourceWritePlatformService writePlatformService) { - this.context = context; this.readPlatformService = readPlatformService; this.toApiJsonSerializer = toApiJsonSerializer; this.apiRequestParameterHelper = apiRequestParameterHelper; @@ -94,9 +92,6 @@ public class MakercheckersApiResource { @QueryParam("loanid") final Integer loanId, @QueryParam("savingsAccountId") final Integer savingsAccountId) { - context.authenticatedUser().validateHasReadPermission( - resourceNameForPermissions); - final String extraCriteria = getExtraCriteria(actionName, entityName, resourceId, makerId, makerDateTimeFrom, makerDateTimeTo, officeId, groupId, clientId, loanId, savingsAccountId); @@ -118,9 +113,6 @@ public class MakercheckersApiResource { @Produces({ MediaType.APPLICATION_JSON }) public String retrieveAuditSearchTemplate(@Context final UriInfo uriInfo) { - context.authenticatedUser().validateHasReadPermission( - resourceNameForPermissions); - final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper .process(uriInfo.getQueryParameters()); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java index 78e5c12fd..093db0a7a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandSource.java @@ -25,159 +25,175 @@ import org.springframework.data.jpa.domain.AbstractPersistable; @Table(name = "m_portfolio_command_source") public class CommandSource extends AbstractPersistable { - @Column(name = "action_name", nullable = true, length = 100) - private String actionName; - - @Column(name = "entity_name", nullable = true, length = 100) - private String entityName; - - @SuppressWarnings("unused") - @Column(name = "office_id") - private Long officeId; + @Column(name = "action_name", nullable = true, length = 100) + private String actionName; - @SuppressWarnings("unused") - @Column(name = "group_id") - private Long groupId; - - @SuppressWarnings("unused") - @Column(name = "client_id") - private Long clientId; + @Column(name = "entity_name", nullable = true, length = 100) + private String entityName; - @SuppressWarnings("unused") - @Column(name = "loan_id") - private Long loanId; - - @SuppressWarnings("unused") - @Column(name = "savings_account_id") - private Long savingsId; - - @Column(name = "api_get_url", length = 100) - private String resourceGetUrl; + @SuppressWarnings("unused") + @Column(name = "office_id") + private Long officeId; - @Column(name = "resource_id") - private Long resourceId; - - @Column(name = "subresource_id") - private Long subresourceId; + @SuppressWarnings("unused") + @Column(name = "group_id") + private Long groupId; - @Column(name = "command_as_json", length = 1000) - private String commandAsJson; + @SuppressWarnings("unused") + @Column(name = "client_id") + private Long clientId; - @SuppressWarnings("unused") - @ManyToOne - @JoinColumn(name = "maker_id", nullable = false) - private AppUser maker; + @SuppressWarnings("unused") + @Column(name = "loan_id") + private Long loanId; - @SuppressWarnings("unused") - @Column(name = "made_on_date", nullable = false) - @Temporal(TemporalType.TIMESTAMP) - private Date madeOnDate; + @SuppressWarnings("unused") + @Column(name = "savings_account_id") + private Long savingsId; - @SuppressWarnings("unused") - @ManyToOne - @JoinColumn(name = "checker_id", nullable = true) - private AppUser checker; + @Column(name = "api_get_url", length = 100) + private String resourceGetUrl; - @SuppressWarnings("unused") - @Column(name = "checked_on_date", nullable = true) - @Temporal(TemporalType.TIMESTAMP) - private Date checkedOnDate; + @Column(name = "resource_id") + private Long resourceId; - @SuppressWarnings("unused") - @Column(name = "processing_result_enum", nullable = false) - private Integer processingResult; + @Column(name = "subresource_id") + private Long subresourceId; - public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker) { - return new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), command.subentityId(), command.json(), maker, DateTime.now()); - } + @Column(name = "command_as_json", length = 1000) + private String commandAsJson; - protected CommandSource() { - // - } + @SuppressWarnings("unused") + @ManyToOne + @JoinColumn(name = "maker_id", nullable = false) + private AppUser maker; - private CommandSource( - final String actionName, final String entityName, final String href, - final Long resourceId, final Long subresourceId, - final String commandSerializedAsJson, final AppUser maker, - final DateTime madeOnDateTime) { - this.actionName = actionName; - this.entityName = entityName; - this.resourceGetUrl = href; - this.resourceId = resourceId; - this.subresourceId = subresourceId; - this.commandAsJson = commandSerializedAsJson; - this.maker = maker; - this.madeOnDate = madeOnDateTime.toDate(); - this.processingResult = CommandProcessingResultType.PROCESSED.getValue(); - } + @SuppressWarnings("unused") + @Column(name = "made_on_date", nullable = false) + @Temporal(TemporalType.TIMESTAMP) + private Date madeOnDate; - public void markAsChecked(final AppUser checker, final DateTime checkedOnDate) { - this.checker = checker; - this.checkedOnDate = checkedOnDate.toDate(); - this.processingResult = CommandProcessingResultType.PROCESSED.getValue(); - } + @SuppressWarnings("unused") + @ManyToOne + @JoinColumn(name = "checker_id", nullable = true) + private AppUser checker; - public void updateResourceId(final Long resourceId) { - this.resourceId = resourceId; - } - - public void updateSubresourceId(final Long subresourceId) { - this.subresourceId = subresourceId; - } + @SuppressWarnings("unused") + @Column(name = "checked_on_date", nullable = true) + @Temporal(TemporalType.TIMESTAMP) + private Date checkedOnDate; - public void updateJsonTo(final String json) { - this.commandAsJson = json; - } + @SuppressWarnings("unused") + @Column(name = "processing_result_enum", nullable = false) + private Integer processingResult; - public Long resourceId() { - return this.resourceId; - } - - public Long subresourceId() { - return this.subresourceId; - } + public static CommandSource fullEntryFrom(final CommandWrapper wrapper, + final JsonCommand command, final AppUser maker) { + return new CommandSource(wrapper.actionName(), wrapper.entityName(), + wrapper.getHref(), command.entityId(), command.subentityId(), + command.json(), maker, DateTime.now()); + } - public boolean hasJson() { - return StringUtils.isNotBlank(this.commandAsJson); - } + protected CommandSource() { + // + } - public String json() { - return this.commandAsJson; - } + private CommandSource(final String actionName, final String entityName, + final String href, final Long resourceId, final Long subresourceId, + final String commandSerializedAsJson, final AppUser maker, + final DateTime madeOnDateTime) { + this.actionName = actionName; + this.entityName = entityName; + this.resourceGetUrl = href; + this.resourceId = resourceId; + this.subresourceId = subresourceId; + this.commandAsJson = commandSerializedAsJson; + this.maker = maker; + this.madeOnDate = madeOnDateTime.toDate(); + this.processingResult = CommandProcessingResultType.PROCESSED + .getValue(); + } - public String getActionName() { - return this.actionName; - } - - public String getEntityName() { - return this.entityName; - } + public void markAsChecked(final AppUser checker, + final DateTime checkedOnDate) { + this.checker = checker; + this.checkedOnDate = checkedOnDate.toDate(); + this.processingResult = CommandProcessingResultType.PROCESSED + .getValue(); + } - public String getPermissionCode() { - return this.actionName + "_" + this.entityName; - } + public void updateResourceId(final Long resourceId) { + this.resourceId = resourceId; + } - public Long getResourceId() { - return this.resourceId; - } - - public Long getSubresourceId() { - return this.subresourceId; - } + public void updateSubresourceId(final Long subresourceId) { + this.subresourceId = subresourceId; + } - public void markAsAwaitingApproval() { - this.processingResult = CommandProcessingResultType.AWAITING_APPROVAL.getValue(); - } + public void updateJsonTo(final String json) { + this.commandAsJson = json; + } - public void updateForAudit(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId) { - this.officeId = officeId; - this.groupId = groupId; - this.clientId = clientId; - this.loanId = loanId; - this.savingsId = savingsId; - } + public Long resourceId() { + return this.resourceId; + } - public String getResourceGetUrl() { - return this.resourceGetUrl; - } + public Long subresourceId() { + return this.subresourceId; + } + + public boolean hasJson() { + return StringUtils.isNotBlank(this.commandAsJson); + } + + public String json() { + return this.commandAsJson; + } + + public String getActionName() { + return this.actionName; + } + + public String getEntityName() { + return this.entityName; + } + + public String getPermissionCode() { + return this.actionName + "_" + this.entityName; + } + + public Long getResourceId() { + return this.resourceId; + } + + public Long getSubresourceId() { + return this.subresourceId; + } + + public void markAsAwaitingApproval() { + this.processingResult = CommandProcessingResultType.AWAITING_APPROVAL + .getValue(); + } + + public boolean isMarkedAsAwaitingApproval() { + if (this.processingResult + .equals(CommandProcessingResultType.AWAITING_APPROVAL + .getValue())) + return true; + + return false; + } + + public void updateForAudit(final Long officeId, final Long groupId, + final Long clientId, final Long loanId, final Long savingsId) { + this.officeId = officeId; + this.groupId = groupId; + this.clientId = clientId; + this.loanId = loanId; + this.savingsId = savingsId; + } + + public String getResourceGetUrl() { + return this.resourceGetUrl; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/exception/CommandNotAwaitingApprovalException.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/exception/CommandNotAwaitingApprovalException.java new file mode 100644 index 000000000..966156f55 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/exception/CommandNotAwaitingApprovalException.java @@ -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.commands.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +/** + * A {@link RuntimeException} thrown when client resources are not found. + */ +public class CommandNotAwaitingApprovalException extends AbstractPlatformResourceNotFoundException { + + public CommandNotAwaitingApprovalException(Long id) { + super("error.msg.command.id.not.awaiting.approval", "Audit with identifier " + id + " is Not Awaiting Approval", id); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/exception/CommandNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/exception/CommandNotFoundException.java index 358fb0e72..014f0340b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/exception/CommandNotFoundException.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/exception/CommandNotFoundException.java @@ -13,6 +13,6 @@ import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceN public class CommandNotFoundException extends AbstractPlatformResourceNotFoundException { public CommandNotFoundException(Long id) { - super("error.msg.command.id.invalid", "Command with identifier " + id + " does not exist", id); + super("error.msg.command.id.invalid", "Audit with identifier " + id + " does not exist", id); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java index da9532b9b..83e723d4a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java @@ -8,12 +8,13 @@ package org.mifosplatform.commands.service; import org.mifosplatform.commands.domain.CommandSource; import org.mifosplatform.commands.domain.CommandSourceRepository; import org.mifosplatform.commands.domain.CommandWrapper; +import org.mifosplatform.commands.exception.CommandNotAwaitingApprovalException; +import org.mifosplatform.commands.exception.CommandNotFoundException; import org.mifosplatform.commands.exception.RollbackTransactionAsCommandIsNotApprovedByCheckerException; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; -import org.mifosplatform.portfolio.client.exception.ClientNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -21,87 +22,120 @@ import org.springframework.transaction.annotation.Transactional; import com.google.gson.JsonElement; @Service -public class PortfolioCommandSourceWritePlatformServiceImpl implements PortfolioCommandSourceWritePlatformService { +public class PortfolioCommandSourceWritePlatformServiceImpl implements + PortfolioCommandSourceWritePlatformService { - private final PlatformSecurityContext context; - private final CommandSourceRepository commandSourceRepository; - private final FromJsonHelper fromApiJsonHelper; - private final CommandProcessingService processAndLogCommandService; + private final PlatformSecurityContext context; + private final CommandSourceRepository commandSourceRepository; + private final FromJsonHelper fromApiJsonHelper; + private final CommandProcessingService processAndLogCommandService; - @Autowired - public PortfolioCommandSourceWritePlatformServiceImpl(final PlatformSecurityContext context, - final CommandSourceRepository commandSourceRepository, final FromJsonHelper fromApiJsonHelper, - final CommandProcessingService processAndLogCommandService) { - this.context = context; - this.commandSourceRepository = commandSourceRepository; - this.fromApiJsonHelper = fromApiJsonHelper; - this.processAndLogCommandService = processAndLogCommandService; - } + @Autowired + public PortfolioCommandSourceWritePlatformServiceImpl( + final PlatformSecurityContext context, + final CommandSourceRepository commandSourceRepository, + final FromJsonHelper fromApiJsonHelper, + final CommandProcessingService processAndLogCommandService) { + this.context = context; + this.commandSourceRepository = commandSourceRepository; + this.fromApiJsonHelper = fromApiJsonHelper; + this.processAndLogCommandService = processAndLogCommandService; + } - @Override - public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) { + @Override + public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) { - boolean isApprovedByChecker = false; - // check if is update of own account details - if (wrapper.isUpdateOfOwnUserDetails(context.authenticatedUser().getId())) { - // then allow this operation to proceed. - // maker checker doesnt mean anything here. - isApprovedByChecker = true; // set to true in case permissions have - // been maker-checker enabled by - // accident. - } else { - // if not user changing their own details - check user has - // permission to perform specific task. - context.authenticatedUser().validateHasPermissionTo(wrapper.getTaskPermissionName()); - } + boolean isApprovedByChecker = false; + // check if is update of own account details + if (wrapper.isUpdateOfOwnUserDetails(context.authenticatedUser() + .getId())) { + // then allow this operation to proceed. + // maker checker doesnt mean anything here. + isApprovedByChecker = true; // set to true in case permissions have + // been maker-checker enabled by + // accident. + } else { + // if not user changing their own details - check user has + // permission to perform specific task. + context.authenticatedUser().validateHasPermissionTo( + wrapper.getTaskPermissionName()); + } - final String json = wrapper.getJson(); - CommandProcessingResult result = null; - try { - final JsonElement parsedCommand = this.fromApiJsonHelper.parse(json); - final JsonCommand command = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(), - wrapper.getEntityId(), wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(), - wrapper.getSavingsId(), wrapper.getCodeId(), wrapper.getSupportedEntityType(), wrapper.getSupportedEntityId(), - wrapper.getTransactionId()); + final String json = wrapper.getJson(); + CommandProcessingResult result = null; + try { + final JsonElement parsedCommand = this.fromApiJsonHelper + .parse(json); + final JsonCommand command = JsonCommand.from(json, parsedCommand, + this.fromApiJsonHelper, wrapper.getEntityName(), + wrapper.getEntityId(), wrapper.getSubentityId(), + wrapper.getGroupId(), wrapper.getClientId(), + wrapper.getLoanId(), wrapper.getSavingsId(), + wrapper.getCodeId(), wrapper.getSupportedEntityType(), + wrapper.getSupportedEntityId(), wrapper.getTransactionId()); - result = this.processAndLogCommandService.processAndLogCommand(wrapper, command, isApprovedByChecker); - } catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) { + result = this.processAndLogCommandService.processAndLogCommand( + wrapper, command, isApprovedByChecker); + } catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) { - result = this.processAndLogCommandService.logCommand(e.getCommandSourceResult()); - } + result = this.processAndLogCommandService.logCommand(e + .getCommandSourceResult()); + } - return result; - } + return result; + } - @Override - public CommandProcessingResult approveEntry(final Long commandId) { + @Override + public CommandProcessingResult approveEntry(final Long makerCheckerId) { - final CommandSource commandSourceInput = this.commandSourceRepository.findOne(commandId); - if (commandSourceInput == null) {throw new ClientNotFoundException(commandId);} - - context.authenticatedUser().validateHasCheckerPermissionTo(commandSourceInput.getPermissionCode()); + CommandSource commandSourceInput = validateMakerCheckerTransaction(makerCheckerId); - final CommandWrapper wrapper = CommandWrapper.fromExistingCommand(commandId, commandSourceInput.getActionName(), - commandSourceInput.getEntityName(), commandSourceInput.resourceId(), commandSourceInput.subresourceId(), - commandSourceInput.getResourceGetUrl()); + final CommandWrapper wrapper = CommandWrapper.fromExistingCommand( + makerCheckerId, commandSourceInput.getActionName(), + commandSourceInput.getEntityName(), + commandSourceInput.resourceId(), + commandSourceInput.subresourceId(), + commandSourceInput.getResourceGetUrl()); - final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.json()); - final JsonCommand command = JsonCommand.fromExistingCommand(commandId, commandSourceInput.json(), parsedCommand, - this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.resourceId(), - commandSourceInput.subresourceId()); + final JsonElement parsedCommand = this.fromApiJsonHelper + .parse(commandSourceInput.json()); + final JsonCommand command = JsonCommand.fromExistingCommand( + makerCheckerId, commandSourceInput.json(), parsedCommand, + this.fromApiJsonHelper, commandSourceInput.getEntityName(), + commandSourceInput.resourceId(), + commandSourceInput.subresourceId()); - final boolean makerCheckerApproval = true; - return this.processAndLogCommandService.processAndLogCommand(wrapper, command, makerCheckerApproval); - } + final boolean makerCheckerApproval = true; + return this.processAndLogCommandService.processAndLogCommand(wrapper, + command, makerCheckerApproval); + } - @Transactional - @Override - public Long deleteEntry(final Long makerCheckerId) { + @Transactional + @Override + public Long deleteEntry(final Long makerCheckerId) { - context.authenticatedUser(); + validateMakerCheckerTransaction(makerCheckerId); - this.commandSourceRepository.delete(makerCheckerId); + this.commandSourceRepository.delete(makerCheckerId); - return makerCheckerId; - } + return makerCheckerId; + } + + private CommandSource validateMakerCheckerTransaction( + final Long makerCheckerId) { + + final CommandSource commandSourceInput = this.commandSourceRepository + .findOne(makerCheckerId); + if (commandSourceInput == null) { + throw new CommandNotFoundException(makerCheckerId); + } + if (!(commandSourceInput.isMarkedAsAwaitingApproval())) { + throw new CommandNotAwaitingApprovalException(makerCheckerId); + } + + context.authenticatedUser().validateHasCheckerPermissionTo( + commandSourceInput.getPermissionCode()); + + return commandSourceInput; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java index d45c16743..5644803b5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/useradministration/domain/AppUser.java @@ -441,7 +441,7 @@ public class AppUser extends AbstractPersistable implements PlatformUser { public void validateHasCheckerPermissionTo(final String function) { final String checkerPermissionName = function.toUpperCase() + "_CHECKER"; - if (hasNotPermissionTo(checkerPermissionName)) { + if (hasNotPermissionTo("CHECKER_SUPER_USER") && hasNotPermissionTo(checkerPermissionName)) { final String authorizationMessage = "User has no authority to be a checker for: " + function; throw new NoAuthorizationException(authorizationMessage); }