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.'));
}
$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;
}
}

View File

@ -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' => ['*']
],

View File

@ -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();
}
}

View File

@ -4,43 +4,56 @@ $alignments = '';
$canRemove = $this->request->getParam('prefix') !== 'Open';
if ($field['scope'] === 'individuals') {
foreach ($raw_alignments as $alignment) {
$alignments .= sprintf(
'<div><span class="fw-bold">%s</span> @ %s <a href="#" class="fas fa-trash .text-reset .text-decoration-none" onClick="%s"></a></div>',
h($alignment['type']),
sprintf(
'<a href="%s/organisations/view/%s">%s</a>',
$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(
'<a href="%s/organisations/view/%s">%s</a>',
$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('<div>%s</div>', $alignmentEntryHtml);
}
} else if ($field['scope'] === 'organisations') {
foreach ($raw_alignments as $alignment) {
$alignments .= sprintf(
'<div>[<span class="fw-bold">%s</span>] %s <a href="#" class="fas fa-trash .text-reset .text-decoration-none" onClick="%s"></a></div>',
h($alignment['type']),
sprintf(
'<a href="%s/individuals/view/%s">%s</a>',
$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(
'<a href="%s/organisations/view/%s">%s</a>',
$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('<div>%s</div>', $alignmentEntryHtml);
}
}
echo $alignments;

View File

@ -10,52 +10,64 @@ if (!empty($field['path'])) {
}
if ($field['scope'] === 'individuals') {
foreach ($extracted['alignments'] as $alignment) {
$alignments .= sprintf(
'<div><span class="fw-bold">%s</span> @ %s <a href="#" class="fas fa-trash" onClick="%s"></a></div>',
h($alignment['type']),
sprintf(
'<a href="%s/organisations/view/%s">%s</a>',
$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(
'<a href="%s/organisations/view/%s">%s</a>',
$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('<div>%s</div>', $alignmentEntryHtml);
}
} else if ($field['scope'] === 'organisations') {
foreach ($extracted['alignments'] as $alignment) {
$alignments .= sprintf(
'<div>[<span class="fw-bold">%s</span>] %s <a href="#" class="fas fa-trash" onClick="%s"></a></div>',
h($alignment['type']),
sprintf(
'<a href="%s/individuals/view/%s">%s</a>',
$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(
'<a href="%s/organisations/view/%s">%s</a>',
$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('<div>%s</div>', $alignmentEntryHtml);
}
}
echo sprintf(
'<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>',
$alignments,
sprintf(
"UI.submissionModalForSinglePage('/alignments/add/%s/%s');",
h($field['scope']),
h($extracted['id'])
),
$field['scope'] === 'individuals' ? __('Add organisation') : __('Add individual')
);
echo sprintf('<div class="alignments-list">%s</div>', $alignments);
if (!empty($canEdit)) {
echo sprintf(
'<div class="alignments-add-container"><button class="alignments-add-button btn btn-primary btn-sm" onclick="%s">%s</button></div>',
sprintf(
"UI.submissionModalForSinglePage('/alignments/add/%s/%s');",
h($field['scope']),
h($extracted['id'])
),
$field['scope'] === 'individuals' ? __('Add organisation') : __('Add individual')
);
}