chg: [alignments:acl] Reflected ACL logic from individuals to alignments

refacto/CRUDComponent
Sami Mokaddem 2023-09-08 09:11:52 +02:00
parent 367012af36
commit 8b4b47775c
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
5 changed files with 163 additions and 79 deletions

View File

@ -49,6 +49,9 @@ class AlignmentsController extends AppController
throw new NotFoundException(__('Invalid alignment.')); throw new NotFoundException(__('Invalid alignment.'));
} }
$alignment = $this->Alignments->get($id); $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->request->is('post') || $this->request->is('delete')) {
if ($this->Alignments->delete($alignment)) { if ($this->Alignments->delete($alignment)) {
$message = __('Alignments deleted.'); $message = __('Alignments deleted.');
@ -73,8 +76,21 @@ class AlignmentsController extends AppController
if (empty($scope) || empty($source_id)) { 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].')); 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('Individuals');
$this->loadModel('Organisations'); $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(); $alignment = $this->Alignments->newEmptyEntity();
if ($this->request->is('post')) { if ($this->request->is('post')) {
$this->Alignments->patchEntity($alignment, $this->request->getData()); $this->Alignments->patchEntity($alignment, $this->request->getData());
@ -83,6 +99,11 @@ class AlignmentsController extends AppController
} else { } else {
$alignment['organisation_id'] = $source_id; $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); $alignment = $this->Alignments->save($alignment);
if ($alignment) { if ($alignment) {
$message = __('Alignment added.'); $message = __('Alignment added.');
@ -105,7 +126,7 @@ class AlignmentsController extends AppController
} }
} }
if ($scope === 'organisations') { 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); $this->set('individuals', $individuals);
$organisation = $this->Organisations->find()->where(['id' => $source_id])->first(); $organisation = $this->Organisations->find()->where(['id' => $source_id])->first();
if (empty($organisation)) { if (empty($organisation)) {
@ -113,7 +134,7 @@ class AlignmentsController extends AppController
} }
$this->set(compact('organisation')); $this->set(compact('organisation'));
} else { } else {
$organisations = $this->Organisations->find('list', ['valueField' => 'name'])->toArray(); $organisations = Hash::combine($validOrgs, '{n}.id', '{n}.name');
$this->set('organisations', $organisations); $this->set('organisations', $organisations);
$individual = $this->Individuals->find()->where(['id' => $source_id])->first(); $individual = $this->Individuals->find()->where(['id' => $source_id])->first();
if (empty($individual)) { if (empty($individual)) {
@ -124,6 +145,31 @@ class AlignmentsController extends AppController
$this->set(compact('alignment')); $this->set(compact('alignment'));
$this->set('scope', $scope); $this->set('scope', $scope);
$this->set('source_id', $source_id); $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;
} }
} }

View File

@ -41,8 +41,8 @@ class ACLComponent extends Component
'queryACL' => ['perm_admin'] 'queryACL' => ['perm_admin']
], ],
'Alignments' => [ 'Alignments' => [
'add' => ['perm_admin'], 'add' => ['perm_admin', 'perm_org_admin'],
'delete' => ['perm_admin'], 'delete' => ['perm_admin', 'perm_org_admin'],
'index' => ['*'], 'index' => ['*'],
'view' => ['*'] 'view' => ['*']
], ],

View File

@ -80,4 +80,17 @@ class OrganisationsTable extends AppTable
$this->saveMetaFields($id, $org); $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();
}
} }

View File

@ -4,43 +4,56 @@ $alignments = '';
$canRemove = $this->request->getParam('prefix') !== 'Open'; $canRemove = $this->request->getParam('prefix') !== 'Open';
if ($field['scope'] === 'individuals') { if ($field['scope'] === 'individuals') {
foreach ($raw_alignments as $alignment) { foreach ($raw_alignments as $alignment) {
$alignments .= sprintf( $canEdit = in_array($alignment->individual_id, $editableIds);
'<div><span class="fw-bold">%s</span> @ %s <a href="#" class="fas fa-trash .text-reset .text-decoration-none" onClick="%s"></a></div>', $alignmentEntryHtml = $this->Bootstrap->node('span', ['class' => ['fw-bold']], h($alignment['type']));
h($alignment['type']), $alignmentEntryHtml .= ' @ ' . $this->Bootstrap->node('span', ['class' => ['ms-1']], sprintf(
sprintf(
'<a href="%s/organisations/view/%s">%s</a>', '<a href="%s/organisations/view/%s">%s</a>',
$baseurl, $baseurl,
h($alignment['organisation']['id']), h($alignment['organisation']['id']),
h($alignment['organisation']['name']) h($alignment['organisation']['name'])
), ),);
!$canRemove ? '' : sprintf( if ($canRemove && !empty($canEdit)) {
"UI.submissionModalForIndex(%s);", $alignmentEntryHtml .= $this->Bootstrap->button([
'icon' => 'trash',
'variant' => 'link',
'class' => ['ms-1', 'p-0'
],
'onclick' => sprintf(
"UI.submissionModalForSinglePage(%s);",
sprintf( sprintf(
"'/alignments/delete/%s'", "'/alignments/delete/%s'",
h($alignment['id']) $alignment['id']
) )
) )
); ]);
}
$alignments .= sprintf('<div>%s</div>', $alignmentEntryHtml);
} }
} else if ($field['scope'] === 'organisations') { } else if ($field['scope'] === 'organisations') {
foreach ($raw_alignments as $alignment) { foreach ($raw_alignments as $alignment) {
$alignments .= sprintf( $canEdit = in_array($alignment->organisation_id, $editableIds);
'<div>[<span class="fw-bold">%s</span>] %s <a href="#" class="fas fa-trash .text-reset .text-decoration-none" onClick="%s"></a></div>', $alignmentEntryHtml = '[' . $this->Bootstrap->node('span', ['class' => ['fw-bold']], h($alignment['type'])) . ']';
h($alignment['type']), $alignmentEntryHtml .= $this->Bootstrap->node('span', ['class' => ['ms-1']], sprintf(
sprintf( '<a href="%s/organisations/view/%s">%s</a>',
'<a href="%s/individuals/view/%s">%s</a>',
$baseurl, $baseurl,
h($alignment['individual']['id']), h($alignment['individual']['id']),
h($alignment['individual']['email']) h($alignment['individual']['email'])
), ),);
!$canRemove ? '' : sprintf( if ($canRemove && !empty($canEdit)) {
"UI.submissionModalForIndex(%s);", $alignmentEntryHtml .= $this->Bootstrap->button([
'icon' => 'trash',
'variant' => 'link',
'class' => ['ms-1', 'p-0'],
'onclick' => sprintf(
"UI.submissionModalForSinglePage(%s);",
sprintf( sprintf(
"'/alignments/delete/%s'", "'/alignments/delete/%s'",
h($alignment['id']) $alignment['id']
) )
) )
); ]);
}
$alignments .= sprintf('<div>%s</div>', $alignmentEntryHtml);
} }
} }
echo $alignments; echo $alignments;

View File

@ -10,52 +10,64 @@ if (!empty($field['path'])) {
} }
if ($field['scope'] === 'individuals') { if ($field['scope'] === 'individuals') {
foreach ($extracted['alignments'] as $alignment) { foreach ($extracted['alignments'] as $alignment) {
$alignments .= sprintf( $alignmentEntryHtml = $this->Bootstrap->node('span', ['class' => ['fw-bold']], h($alignment['type']));
'<div><span class="fw-bold">%s</span> @ %s <a href="#" class="fas fa-trash" onClick="%s"></a></div>', $alignmentEntryHtml .= ' @ ' . $this->Bootstrap->node('span', ['class' => ['ms-1']], sprintf(
h($alignment['type']),
sprintf(
'<a href="%s/organisations/view/%s">%s</a>', '<a href="%s/organisations/view/%s">%s</a>',
$baseurl, $baseurl,
h($alignment['organisation']['id']), h($alignment['organisation']['id']),
h($alignment['organisation']['name']) h($alignment['organisation']['name'])
), ),);
sprintf( if (!empty($canEdit)) {
$alignmentEntryHtml .= $this->Bootstrap->button([
'icon' => 'trash',
'variant' => 'link',
'class' => ['ms-1', 'p-0'],
'onclick' => sprintf(
"UI.submissionModalForSinglePage(%s);", "UI.submissionModalForSinglePage(%s);",
sprintf( sprintf(
"'/alignments/delete/%s'", "'/alignments/delete/%s'",
$alignment['id'] $alignment['id']
) )
) )
); ]);
}
$alignments .= sprintf('<div>%s</div>', $alignmentEntryHtml);
} }
} else if ($field['scope'] === 'organisations') { } else if ($field['scope'] === 'organisations') {
foreach ($extracted['alignments'] as $alignment) { foreach ($extracted['alignments'] as $alignment) {
$alignments .= sprintf( $alignmentEntryHtml = '[' . $this->Bootstrap->node('span', ['class' => ['fw-bold']], h($alignment['type'])) . ']';
'<div>[<span class="fw-bold">%s</span>] %s <a href="#" class="fas fa-trash" onClick="%s"></a></div>', $alignmentEntryHtml .= $this->Bootstrap->node('span', ['class' => ['ms-1']], sprintf(
h($alignment['type']), '<a href="%s/organisations/view/%s">%s</a>',
sprintf(
'<a href="%s/individuals/view/%s">%s</a>',
$baseurl, $baseurl,
h($alignment['individual']['id']), h($alignment['individual']['id']),
h($alignment['individual']['email']) h($alignment['individual']['email'])
), ),);
sprintf( if (!empty($canEdit)) {
$alignmentEntryHtml .= $this->Bootstrap->button([
'icon' => 'trash',
'variant' => 'link',
'class' => ['ms-1', 'p-0'],
'onclick' => sprintf(
"UI.submissionModalForSinglePage(%s);", "UI.submissionModalForSinglePage(%s);",
sprintf( sprintf(
"'/alignments/delete/%s'", "'/alignments/delete/%s'",
$alignment['id'] $alignment['id']
) )
) )
); ]);
}
$alignments .= sprintf('<div>%s</div>', $alignmentEntryHtml);
} }
} }
echo sprintf( echo sprintf('<div class="alignments-list">%s</div>', $alignments);
'<div class="alignments-list">%s</div><div class="alignments-add-container"><button class="alignments-add-button btn btn-primary btn-sm" onclick="%s">%s</button></div>', if (!empty($canEdit)) {
$alignments, echo sprintf(
'<div class="alignments-add-container"><button class="alignments-add-button btn btn-primary btn-sm" onclick="%s">%s</button></div>',
sprintf( sprintf(
"UI.submissionModalForSinglePage('/alignments/add/%s/%s');", "UI.submissionModalForSinglePage('/alignments/add/%s/%s');",
h($field['scope']), h($field['scope']),
h($extracted['id']) h($extracted['id'])
), ),
$field['scope'] === 'individuals' ? __('Add organisation') : __('Add individual') $field['scope'] === 'individuals' ? __('Add organisation') : __('Add individual')
); );
}