From 8b4b47775ce8c68f19e37482abc0f8439f72544d Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Fri, 8 Sep 2023 09:11:52 +0200 Subject: [PATCH] chg: [alignments:acl] Reflected ACL logic from individuals to alignments --- src/Controller/AlignmentsController.php | 52 +++++++++- src/Controller/Component/ACLComponent.php | 4 +- src/Model/Table/OrganisationsTable.php | 13 +++ .../IndexTable/Fields/alignments.php | 77 ++++++++------- .../SingleViews/Fields/alignmentField.php | 96 +++++++++++-------- 5 files changed, 163 insertions(+), 79 deletions(-) diff --git a/src/Controller/AlignmentsController.php b/src/Controller/AlignmentsController.php index 6f48af5..c783148 100644 --- a/src/Controller/AlignmentsController.php +++ b/src/Controller/AlignmentsController.php @@ -49,6 +49,9 @@ class AlignmentsController extends AppController throw new NotFoundException(__('Invalid alignment.')); } $alignment = $this->Alignments->get($id); + if (!$this->canEditIndividual($alignment->individual_id) || !$this->canEditOrganisation($alignment->organisation_id)) { + throw new MethodNotAllowedException(__('You cannot delete this alignments.')); + } if ($this->request->is('post') || $this->request->is('delete')) { if ($this->Alignments->delete($alignment)) { $message = __('Alignments deleted.'); @@ -73,8 +76,21 @@ class AlignmentsController extends AppController if (empty($scope) || empty($source_id)) { throw new NotAcceptableException(__('Invalid input. scope and source_id expected as URL parameters in the format /alignments/add/[scope]/[source_id].')); } + if (!in_array($scope, ['individuals', 'organisations'])) { + throw new MethodNotAllowedException(__('Invalid scope. Should be `individuals` or `organisations`.')); + } $this->loadModel('Individuals'); $this->loadModel('Organisations'); + + $validIndividualIDs = $this->Individuals->getValidIndividualsToEdit($this->ACL->getUser()); + $validOrgs = $this->Organisations->getEditableOrganisationsForUser($this->ACL->getUser()); + + if ($scope == 'individuals' && !$this->canEditIndividual($source_id)) { + throw new MethodNotAllowedException(__('You cannot modify that individual.')); + } else if ($scope == 'organisations' && !$this->canEditOrganisation($source_id)) { + throw new MethodNotAllowedException(__('You cannot modify that organisation.')); + } + $alignment = $this->Alignments->newEmptyEntity(); if ($this->request->is('post')) { $this->Alignments->patchEntity($alignment, $this->request->getData()); @@ -83,6 +99,11 @@ class AlignmentsController extends AppController } else { $alignment['organisation_id'] = $source_id; } + if ($scope == 'individuals' && !$this->canEditOrganisation($alignment['organisation_id'])) { + throw new MethodNotAllowedException(__('You cannot use that organisation.')); + } else if ($scope == 'organisations' && !$this->canEditIndividual($alignment['individual_id'])) { + throw new MethodNotAllowedException(__('You cannot assign that individual.')); + } $alignment = $this->Alignments->save($alignment); if ($alignment) { $message = __('Alignment added.'); @@ -105,7 +126,7 @@ class AlignmentsController extends AppController } } if ($scope === 'organisations') { - $individuals = $this->Individuals->find('list', ['valueField' => 'email'])->toArray(); + $individuals = $this->Individuals->find('list', ['valueField' => 'email'])->where(['id IN' => $validIndividualIDs])->toArray(); $this->set('individuals', $individuals); $organisation = $this->Organisations->find()->where(['id' => $source_id])->first(); if (empty($organisation)) { @@ -113,7 +134,7 @@ class AlignmentsController extends AppController } $this->set(compact('organisation')); } else { - $organisations = $this->Organisations->find('list', ['valueField' => 'name'])->toArray(); + $organisations = Hash::combine($validOrgs, '{n}.id', '{n}.name'); $this->set('organisations', $organisations); $individual = $this->Individuals->find()->where(['id' => $source_id])->first(); if (empty($individual)) { @@ -124,6 +145,31 @@ class AlignmentsController extends AppController $this->set(compact('alignment')); $this->set('scope', $scope); $this->set('source_id', $source_id); - $this->set('metaGroup', 'ContactDB'); + } + + private function canEditIndividual($indId): bool + { + $currentUser = $this->ACL->getUser(); + if ($currentUser['role']['perm_admin']) { + return true; + } + $this->loadModel('Individuals'); + $validIndividuals = $this->Individuals->getValidIndividualsToEdit($currentUser); + if (in_array($indId, $validIndividuals)) { + return true; + } + return false; + } + + private function canEditOrganisation($orgId): bool + { + $currentUser = $this->ACL->getUser(); + if ($currentUser['role']['perm_admin']) { + return true; + } + if ($currentUser['role']['perm_org_admin'] && $currentUser['organisation']['id'] == $orgId) { + return true; + } + return false; } } diff --git a/src/Controller/Component/ACLComponent.php b/src/Controller/Component/ACLComponent.php index 1689fc0..90fefe4 100644 --- a/src/Controller/Component/ACLComponent.php +++ b/src/Controller/Component/ACLComponent.php @@ -41,8 +41,8 @@ class ACLComponent extends Component 'queryACL' => ['perm_admin'] ], 'Alignments' => [ - 'add' => ['perm_admin'], - 'delete' => ['perm_admin'], + 'add' => ['perm_admin', 'perm_org_admin'], + 'delete' => ['perm_admin', 'perm_org_admin'], 'index' => ['*'], 'view' => ['*'] ], diff --git a/src/Model/Table/OrganisationsTable.php b/src/Model/Table/OrganisationsTable.php index f56f36b..f74e53a 100644 --- a/src/Model/Table/OrganisationsTable.php +++ b/src/Model/Table/OrganisationsTable.php @@ -80,4 +80,17 @@ class OrganisationsTable extends AppTable $this->saveMetaFields($id, $org); } } + + public function getEditableOrganisationsForUser($user): array + { + $query = $this->find(); + if (empty($user['role']['perm_admin'])) { + if (!empty($user['role']['perm_org_admin'])) { + $query->where(['Organisations.id' => $user['organisation']['id']]); + } else { + return []; // User not an org_admin. Cannot edit orgs + } + } + return $query->all()->toList(); + } } diff --git a/templates/element/genericElements/IndexTable/Fields/alignments.php b/templates/element/genericElements/IndexTable/Fields/alignments.php index 15c29e1..b23d96e 100644 --- a/templates/element/genericElements/IndexTable/Fields/alignments.php +++ b/templates/element/genericElements/IndexTable/Fields/alignments.php @@ -4,43 +4,56 @@ $alignments = ''; $canRemove = $this->request->getParam('prefix') !== 'Open'; if ($field['scope'] === 'individuals') { foreach ($raw_alignments as $alignment) { - $alignments .= sprintf( - '
%s @ %s
', - h($alignment['type']), - sprintf( - '%s', - $baseurl, - h($alignment['organisation']['id']), - h($alignment['organisation']['name']) - ), - !$canRemove ? '' : sprintf( - "UI.submissionModalForIndex(%s);", - sprintf( - "'/alignments/delete/%s'", - h($alignment['id']) + $canEdit = in_array($alignment->individual_id, $editableIds); + $alignmentEntryHtml = $this->Bootstrap->node('span', ['class' => ['fw-bold']], h($alignment['type'])); + $alignmentEntryHtml .= ' @ ' . $this->Bootstrap->node('span', ['class' => ['ms-1']], sprintf( + '%s', + $baseurl, + h($alignment['organisation']['id']), + h($alignment['organisation']['name']) + ),); + if ($canRemove && !empty($canEdit)) { + $alignmentEntryHtml .= $this->Bootstrap->button([ + 'icon' => 'trash', + 'variant' => 'link', + 'class' => ['ms-1', 'p-0' + ], + 'onclick' => sprintf( + "UI.submissionModalForSinglePage(%s);", + sprintf( + "'/alignments/delete/%s'", + $alignment['id'] + ) ) - ) - ); + ]); + } + $alignments .= sprintf('
%s
', $alignmentEntryHtml); } } else if ($field['scope'] === 'organisations') { foreach ($raw_alignments as $alignment) { - $alignments .= sprintf( - '
[%s] %s
', - h($alignment['type']), - sprintf( - '%s', - $baseurl, - h($alignment['individual']['id']), - h($alignment['individual']['email']) - ), - !$canRemove ? '' : sprintf( - "UI.submissionModalForIndex(%s);", - sprintf( - "'/alignments/delete/%s'", - h($alignment['id']) + $canEdit = in_array($alignment->organisation_id, $editableIds); + $alignmentEntryHtml = '[' . $this->Bootstrap->node('span', ['class' => ['fw-bold']], h($alignment['type'])) . ']'; + $alignmentEntryHtml .= $this->Bootstrap->node('span', ['class' => ['ms-1']], sprintf( + '%s', + $baseurl, + h($alignment['individual']['id']), + h($alignment['individual']['email']) + ),); + if ($canRemove && !empty($canEdit)) { + $alignmentEntryHtml .= $this->Bootstrap->button([ + 'icon' => 'trash', + 'variant' => 'link', + 'class' => ['ms-1', 'p-0'], + 'onclick' => sprintf( + "UI.submissionModalForSinglePage(%s);", + sprintf( + "'/alignments/delete/%s'", + $alignment['id'] + ) ) - ) - ); + ]); + } + $alignments .= sprintf('
%s
', $alignmentEntryHtml); } } echo $alignments; diff --git a/templates/element/genericElements/SingleViews/Fields/alignmentField.php b/templates/element/genericElements/SingleViews/Fields/alignmentField.php index 1832ec0..48a78ab 100644 --- a/templates/element/genericElements/SingleViews/Fields/alignmentField.php +++ b/templates/element/genericElements/SingleViews/Fields/alignmentField.php @@ -10,52 +10,64 @@ if (!empty($field['path'])) { } if ($field['scope'] === 'individuals') { foreach ($extracted['alignments'] as $alignment) { - $alignments .= sprintf( - '
%s @ %s
', - h($alignment['type']), - sprintf( - '%s', - $baseurl, - h($alignment['organisation']['id']), - h($alignment['organisation']['name']) - ), - sprintf( - "UI.submissionModalForSinglePage(%s);", - sprintf( - "'/alignments/delete/%s'", - $alignment['id'] + $alignmentEntryHtml = $this->Bootstrap->node('span', ['class' => ['fw-bold']], h($alignment['type'])); + $alignmentEntryHtml .= ' @ ' . $this->Bootstrap->node('span', ['class' => ['ms-1']], sprintf( + '%s', + $baseurl, + h($alignment['organisation']['id']), + h($alignment['organisation']['name']) + ),); + if (!empty($canEdit)) { + $alignmentEntryHtml .= $this->Bootstrap->button([ + 'icon' => 'trash', + 'variant' => 'link', + 'class' => ['ms-1', 'p-0'], + 'onclick' => sprintf( + "UI.submissionModalForSinglePage(%s);", + sprintf( + "'/alignments/delete/%s'", + $alignment['id'] + ) ) - ) - ); + ]); + } + $alignments .= sprintf('
%s
', $alignmentEntryHtml); } } else if ($field['scope'] === 'organisations') { foreach ($extracted['alignments'] as $alignment) { - $alignments .= sprintf( - '
[%s] %s
', - h($alignment['type']), - sprintf( - '%s', - $baseurl, - h($alignment['individual']['id']), - h($alignment['individual']['email']) - ), - sprintf( - "UI.submissionModalForSinglePage(%s);", - sprintf( - "'/alignments/delete/%s'", - $alignment['id'] + $alignmentEntryHtml = '[' . $this->Bootstrap->node('span', ['class' => ['fw-bold']], h($alignment['type'])) . ']'; + $alignmentEntryHtml .= $this->Bootstrap->node('span', ['class' => ['ms-1']], sprintf( + '%s', + $baseurl, + h($alignment['individual']['id']), + h($alignment['individual']['email']) + ),); + if (!empty($canEdit)) { + $alignmentEntryHtml .= $this->Bootstrap->button([ + 'icon' => 'trash', + 'variant' => 'link', + 'class' => ['ms-1', 'p-0'], + 'onclick' => sprintf( + "UI.submissionModalForSinglePage(%s);", + sprintf( + "'/alignments/delete/%s'", + $alignment['id'] + ) ) - ) - ); + ]); + } + $alignments .= sprintf('
%s
', $alignmentEntryHtml); } } -echo sprintf( - '
%s
', - $alignments, - sprintf( - "UI.submissionModalForSinglePage('/alignments/add/%s/%s');", - h($field['scope']), - h($extracted['id']) - ), - $field['scope'] === 'individuals' ? __('Add organisation') : __('Add individual') -); +echo sprintf('
%s
', $alignments); +if (!empty($canEdit)) { + echo sprintf( + '
', + sprintf( + "UI.submissionModalForSinglePage('/alignments/add/%s/%s');", + h($field['scope']), + h($extracted['id']) + ), + $field['scope'] === 'individuals' ? __('Add organisation') : __('Add individual') + ); +}