Skip to content

Commit

Permalink
Merge pull request #2682 from compucorp/PCHR-3820-leave-attachments-a…
Browse files Browse the repository at this point in the history
…cl-fix

PCHR-3820: Add Leave Request ACL Rules to LeaveRequest.getAttachments API
  • Loading branch information
tunbola authored Jun 14, 2018
2 parents 7677940 + 20d8441 commit 2708605
Show file tree
Hide file tree
Showing 4 changed files with 375 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,31 @@

use CRM_HRLeaveAndAbsences_BAO_LeaveRequest as LeaveRequest;
use CRM_HRLeaveAndAbsences_Service_LeaveManager as LeaveManagerService;
use CRM_HRLeaveAndAbsences_Service_LeaveRequestRights as LeaveRequestRightsService;

class CRM_HRLeaveAndAbsences_Service_LeaveRequestAttachment {

/**
* @var LeaveManagerService
*/
private $leaveManagerService;

/**
* @var LeaveRequestRightsService
*/
private $leaveRequestRightsService;

/**
* CRM_HRLeaveAndAbsences_Service_LeaveRequestAttachment constructor.
*
* @param LeaveRequestRightsService $leaveRequestRights
* @param LeaveManagerService $leaveManager
*/
public function __construct(LeaveRequestRightsService $leaveRequestRights, LeaveManagerService $leaveManager) {
$this->leaveManagerService = $leaveManager;
$this->leaveRequestRightsService = $leaveRequestRights;
}

/**
* Uses the Attachment API to delete an attachment associated with a LeaveRequest.
* This method also implement some checks to ensure that only the LeaveRequest Approver
Expand All @@ -23,9 +45,8 @@ public function delete($params) {

if ($attachment['count'] > 0) {
$leaveRequest = LeaveRequest::findById($attachment['values'][0]['entity_id']);
$leaveManagerService = new LeaveManagerService();

if ($leaveManagerService->currentUserIsAdmin() || $leaveManagerService->currentUserIsLeaveManagerOf($leaveRequest->contact_id)) {
if ($this->leaveManagerService->currentUserIsAdmin() || $this->leaveManagerService->currentUserIsLeaveManagerOf($leaveRequest->contact_id)) {
return $this->callAttachmentAPI('delete', $params);
}

Expand Down Expand Up @@ -72,4 +93,27 @@ private function callAttachmentAPI($action, $params) {

return civicrm_api3('Attachment', $action, $params);
}

/**
* Uses the Attachment API to retrieve attachments associated with a LeaveRequest.
* It ensures that the current user can only retrieve Leave attachments for the
* Leave requests linked to the contacts the user has access to. The admin can
* retrieve all Leave attachments for all contacts.
*
* @param array $params
* @param LeaveRequestRights $leaveRequestRights
*
* @return array
*/
public function get($params) {
$leaveRequestID = isset($params['entity_id']) ? $params['entity_id'] : '';
$leaveRequest = LeaveRequest::findById($leaveRequestID);
$accessibleContacts = $this->leaveRequestRightsService->getLeaveContactsCurrentUserHasAccessTo();

if ($this->leaveManagerService->currentUserIsAdmin() || in_array($leaveRequest->contact_id, $accessibleContacts)) {
return $this->callAttachmentAPI('get', $params);
}

return [];
}
}
15 changes: 11 additions & 4 deletions uk.co.compucorp.civicrm.hrleaveandabsences/api/v3/LeaveRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -709,12 +709,17 @@ function _civicrm_api3_leave_request_getattachments_spec(&$spec) {
function civicrm_api3_leave_request_getattachments($params) {
$params['entity_id'] = $params['leave_request_id'];
$params['entity_table'] = CRM_HRLeaveAndAbsences_BAO_LeaveRequest::getTableName();
$leaveManagerService = new CRM_HRLeaveAndAbsences_Service_LeaveManager();
$leaveRequestRights = new CRM_HRLeaveAndAbsences_Service_LeaveRequestRights($leaveManagerService);
$leaveRequestAttachmentService = new CRM_HRLeaveAndAbsences_Service_LeaveRequestAttachment($leaveRequestRights, $leaveManagerService);
$result = $leaveRequestAttachmentService->get($params);

$result = civicrm_api3('Attachment', 'get', $params);

if ($result['count'] > 0) {
if (!empty($result)) {
array_walk($result['values'], '_civicrm_api3_leave_request_filter_attachment_fields');
}
else{
$result = civicrm_api3_create_success([]);
}

return $result;
}
Expand Down Expand Up @@ -766,7 +771,9 @@ function _civicrm_api3_leave_request_deleteattachment_spec(&$spec) {
* @return array
*/
function civicrm_api3_leave_request_deleteattachment($params) {
$leaveRequestAttachmentService = new CRM_HRLeaveAndAbsences_Service_LeaveRequestAttachment();
$leaveManagerService = new CRM_HRLeaveAndAbsences_Service_LeaveManager();
$leaveRequestRights = new CRM_HRLeaveAndAbsences_Service_LeaveRequestRights($leaveManagerService);
$leaveRequestAttachmentService = new CRM_HRLeaveAndAbsences_Service_LeaveRequestAttachment($leaveRequestRights, $leaveManagerService);
return $leaveRequestAttachmentService->delete($params);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
use CRM_HRCore_Test_Fabricator_Contact as ContactFabricator;
use CRM_HRLeaveAndAbsences_Test_Fabricator_LeaveRequest as LeaveRequestFabricator;
use CRM_HRLeaveAndAbsences_Service_LeaveRequestAttachment as LeaveRequestAttachmentService;
use CRM_HRLeaveAndAbsences_Service_LeaveManager as LeaveManagerService;
use CRM_HRLeaveAndAbsences_Service_LeaveRequestRights as LeaveRightsService;

/**
* Class CRM_HRLeaveAndAbsences_Service_LeaveRequestAttachmentTest
Expand All @@ -15,14 +17,12 @@ class CRM_HRLeaveAndAbsences_Service_LeaveRequestAttachmentTest extends BaseHead
use CRM_HRLeaveAndAbsences_SessionHelpersTrait;
use CRM_HRLeaveAndAbsences_LeaveRequestHelpersTrait;

private $leaveRequestAttachmentService;

private $leaveContact;

public function setUp() {
CRM_Core_DAO::executeQuery('SET foreign_key_checks = 0;');

$this->leaveRequestAttachmentService = new LeaveRequestAttachmentService();
$this->leaveContact = 1;
}

Expand All @@ -42,18 +42,18 @@ public function testDeleteShouldThrowAnExceptionWhenLoggedInUserIsNotAnAdminOrLe
$this->registerCurrentLoggedInContactInSession($contactID);
CRM_Core_Config::singleton()->userPermissionClass->permissions = [];

$leaveManagerService = new LeaveManagerService();
$leaveRequestRights = new LeaveRightsService($leaveManagerService);
$leaveRequestAttachmentService = new LeaveRequestAttachmentService($leaveRequestRights, $leaveManagerService);

$attachment = $this->createAttachmentForLeaveRequest(['entity_id' => $leaveRequest->id]);
$this->leaveRequestAttachmentService->delete(['leave_request_id' => $leaveRequest->id, 'attachment_id' => $attachment['id']]);
$leaveRequestAttachmentService->delete(['leave_request_id' => $leaveRequest->id, 'attachment_id' => $attachment['id']]);
}

public function testDeleteShouldDeleteAttachmentWhenLoggedInUserIsAnAdmin() {
$adminID = 2;
$params = $this->getDefaultLeaveRequestParams();
$leaveRequest = LeaveRequestFabricator::fabricateWithoutValidation($params);

// Register contact in session and set permission to admin
$this->registerCurrentLoggedInContactInSession($adminID);
CRM_Core_Config::singleton()->userPermissionClass->permissions = ['administer leave and absences'];
$leaveRequestAttachmentService = $this->createLeaveRequestAttachmentsServiceWhenUserIsAdmin();

$attachment = $this->createAttachmentForLeaveRequest(['entity_id' => $leaveRequest->id]);
$attachment2 = $this->createAttachmentForLeaveRequest(['entity_id' => $leaveRequest->id]);
Expand All @@ -62,7 +62,7 @@ public function testDeleteShouldDeleteAttachmentWhenLoggedInUserIsAnAdmin() {
$attachmentList = $this->getAttachmentForLeaveRequest(['entity_id' => $leaveRequest->id]);
$this->assertEquals($attachmentList['count'], 2);

$result = $this->leaveRequestAttachmentService->delete(['leave_request_id' => $leaveRequest->id, 'attachment_id' => $attachment['id']]);
$result = $leaveRequestAttachmentService->delete(['leave_request_id' => $leaveRequest->id, 'attachment_id' => $attachment['id']]);

$expected = [
'is_error' => 0,
Expand All @@ -78,22 +78,21 @@ public function testDeleteShouldDeleteAttachmentWhenLoggedInUserIsAnAdmin() {
}

public function testDeleteShouldDeleteAttachmentWhenLoggedInUserIsTheLeaveApprover() {
$manager = ContactFabricator::fabricate();
$leaveContact = ContactFabricator::fabricate();
$params = $this->getDefaultLeaveRequestParams(['contact_id' => $leaveContact['id']]);
$leaveRequest = LeaveRequestFabricator::fabricateWithoutValidation($params);

// Set logged in user as manager of Contact who requested leave
$this->registerCurrentLoggedInContactInSession($manager['id']);
$this->setContactAsLeaveApproverOf($manager, $leaveContact);
CRM_Core_Config::singleton()->userPermissionClass->permissions = [];

$leaveManagerService = $this->getLeaveManagerServiceWhenUserIsLeaveApprover($leaveContact['id']);
$leaveRequestRights = new LeaveRightsService($leaveManagerService);
$leaveRequestAttachmentService = new LeaveRequestAttachmentService($leaveRequestRights, $leaveManagerService);

$attachment = $this->createAttachmentForLeaveRequest(['entity_id' => $leaveRequest->id]);

//confirm that only one attachment exist for the leave request before deletion
$attachmentList = $this->getAttachmentForLeaveRequest(['entity_id' => $leaveRequest->id]);
$this->assertEquals($attachmentList['count'], 1);
$result = $this->leaveRequestAttachmentService->delete(['leave_request_id' => $leaveRequest->id, 'attachment_id' => $attachment['id']]);
$result = $leaveRequestAttachmentService->delete(['leave_request_id' => $leaveRequest->id, 'attachment_id' => $attachment['id']]);

$expected = [
'is_error' => 0,
Expand All @@ -108,36 +107,28 @@ public function testDeleteShouldDeleteAttachmentWhenLoggedInUserIsTheLeaveApprov
}

public function testDeleteShouldThrowAnExceptionWhenAttachmentHasBeenDeletedBefore() {
$adminID = 2;
$params = $this->getDefaultLeaveRequestParams();
$leaveRequest = LeaveRequestFabricator::fabricateWithoutValidation($params);

// Register contact in session and set permission to admin
$this->registerCurrentLoggedInContactInSession($adminID);
CRM_Core_Config::singleton()->userPermissionClass->permissions = ['administer leave and absences'];
$leaveRequestAttachmentService = $this->createLeaveRequestAttachmentsServiceWhenUserIsAdmin();

$attachment = $this->createAttachmentForLeaveRequest(['entity_id' => $leaveRequest->id]);

$this->leaveRequestAttachmentService->delete(['leave_request_id' => $leaveRequest->id, 'attachment_id' => $attachment['id']]);
$leaveRequestAttachmentService->delete(['leave_request_id' => $leaveRequest->id, 'attachment_id' => $attachment['id']]);

//try delete attachment again
$this->setExpectedException('InvalidArgumentException', 'Attachment does not exist or has been deleted already!');
$this->leaveRequestAttachmentService->delete(['leave_request_id' => $leaveRequest->id, 'attachment_id' => $attachment['id']]);
$leaveRequestAttachmentService->delete(['leave_request_id' => $leaveRequest->id, 'attachment_id' => $attachment['id']]);
}

/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Attachment does not exist or has been deleted already!
*/
public function testDeleteShouldThrowAnExceptionWhenAttachmentDoesNotExist() {
$adminID = 2;
$leaveRequestID = 1;
$leaveRequestAttachmentService = $this->createLeaveRequestAttachmentsServiceWhenUserIsAdmin();

// Register contact in session and set permission to admin
$this->registerCurrentLoggedInContactInSession($adminID);
CRM_Core_Config::singleton()->userPermissionClass->permissions = ['administer leave and absences'];

$this->leaveRequestAttachmentService->delete(['leave_request_id' => $leaveRequestID, 'attachment_id' => 1]);
$leaveRequestAttachmentService->delete(['leave_request_id' => $leaveRequestID, 'attachment_id' => 1]);
}

private function getDefaultLeaveRequestParams($params = []) {
Expand All @@ -151,4 +142,101 @@ private function getDefaultLeaveRequestParams($params = []) {
];
return array_merge($defaultParams, $params);
}


public function testGetReturnsLeaveAttachmentDataOnlyForContactAUserHasAccessTo() {
$staff1 = 1;
$staff2 = 2;
$params1 = $this->getDefaultLeaveRequestParams(['contact_id' => $staff1]);
$params2 = $this->getDefaultLeaveRequestParams(['contact_id' => $staff2]);

//Create Leave requests
$leaveRequest1 = LeaveRequestFabricator::fabricateWithoutValidation($params1);
$leaveRequest2 = LeaveRequestFabricator::fabricateWithoutValidation($params2);

//create attachments for Leave requests
$attachment1 = $this->createAttachmentForLeaveRequest(['entity_id' => $leaveRequest1->id]);
$attachment2 = $this->createAttachmentForLeaveRequest(['entity_id' => $leaveRequest2->id]);


//Register the staff1 in session
$this->registerCurrentLoggedInContactInSession($staff1);
CRM_Core_Config::singleton()->userPermissionClass->permissions = [];

$leaveManagerService = new LeaveManagerService();
$leaveRightsService = $this->prophesize(LeaveRightsService::class);

// staff1 has access to own self alone
$leaveRightsService->getLeaveContactsCurrentUserHasAccessTo()->willReturn([$staff1]);
$leaveRequestAttachmentService = new LeaveRequestAttachmentService($leaveRightsService->reveal(), $leaveManagerService);

$staff1Attachment = $leaveRequestAttachmentService->get(['entity_id' => $leaveRequest1->id, 'sequential' => 1]);
$staff2Attachment = $leaveRequestAttachmentService->get(['entity_id' => $leaveRequest2->id, 'sequential' => 1]);

//result would be empty for contact user does not have access to
$this->assertEmpty($staff2Attachment);

$this->assertCount(1, $staff1Attachment['values']);
$this->assertEquals($staff1Attachment['values'][0]['id'], $attachment1['id']);
$this->assertEquals($staff1Attachment['values'][0]['name'], $attachment1['name']);
}

public function testGetReturnsLeaveAttachmentDataOnlyForAllContactsForAdmin() {
$staff1 = 1;
$staff2 = 2;
$params1 = $this->getDefaultLeaveRequestParams(['contact_id' => $staff1]);
$params2 = $this->getDefaultLeaveRequestParams(['contact_id' => $staff2]);

//Create Leave requests
$leaveRequest1 = LeaveRequestFabricator::fabricateWithoutValidation($params1);
$leaveRequest2 = LeaveRequestFabricator::fabricateWithoutValidation($params2);

//create attachments for Leave requests
$attachment1 = $this->createAttachmentForLeaveRequest(['entity_id' => $leaveRequest1->id]);
$attachment2 = $this->createAttachmentForLeaveRequest(['entity_id' => $leaveRequest2->id]);

$leaveManagerService = $this->getLeaveManagerServiceWhenUserIsAdmin();
$leaveRightsService = $this->prophesize(LeaveRightsService::class);
$leaveRightsService->getLeaveContactsCurrentUserHasAccessTo()->willReturn([]);
$leaveRequestAttachmentService = new LeaveRequestAttachmentService($leaveRightsService->reveal(), $leaveManagerService);

$staff1Attachment = $leaveRequestAttachmentService->get(['entity_id' => $leaveRequest1->id, 'sequential' => 1]);
$staff2Attachment = $leaveRequestAttachmentService->get(['entity_id' => $leaveRequest2->id, 'sequential' => 1]);

//Admin is able to access attachments for all contacts
$this->assertCount(1, $staff1Attachment['values']);
$this->assertEquals($staff1Attachment['values'][0]['id'], $attachment1['id']);
$this->assertEquals($staff1Attachment['values'][0]['name'], $attachment1['name']);

$this->assertCount(1, $staff2Attachment['values']);
$this->assertEquals($staff2Attachment['values'][0]['id'], $attachment2['id']);
$this->assertEquals($staff2Attachment['values'][0]['name'], $attachment2['name']);
}

private function getLeaveManagerService($isAdmin, $leaveContact = NULL) {
$leaveManagerService = $this->prophesize(LeaveManagerService::class);
$leaveManagerService->currentUserIsAdmin()->willReturn($isAdmin);

if ($leaveContact) {
$leaveManagerService->currentUserIsLeaveManagerOf($leaveContact)->willReturn(TRUE);
}

return $leaveManagerService->reveal();
}

private function getLeaveManagerServiceWhenUserIsAdmin() {
return $this->getLeaveManagerService(TRUE);
}

private function getLeaveManagerServiceWhenUserIsLeaveApprover($leaveContact) {
return $this->getLeaveManagerService(FALSE, $leaveContact);
}

private function createLeaveRequestAttachmentsServiceWhenUserIsAdmin() {
$leaveManagerService = $this->getLeaveManagerServiceWhenUserIsAdmin();
$leaveRequestRights = new LeaveRightsService($leaveManagerService);
$leaveRequestAttachmentService = new LeaveRequestAttachmentService($leaveRequestRights, $leaveManagerService);

return $leaveRequestAttachmentService;
}
}
Loading

0 comments on commit 2708605

Please sign in to comment.