chg: [alignments:acl] Reflected ACL logic from individuals to alignments
parent
367012af36
commit
8b4b47775c
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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' => ['*']
|
||||||
],
|
],
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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')
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue