Skip to content

Commit

Permalink
Merge pull request #243 from oat-sa/feature/adf-1590/optimize-permiss…
Browse files Browse the repository at this point in the history
…ions-task-queue

chore: use a single task to change all permissions
  • Loading branch information
shpran authored Nov 2, 2023
2 parents d6348db + 2ad9640 commit 4ccc450
Show file tree
Hide file tree
Showing 26 changed files with 1,154 additions and 1,267 deletions.
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
"minimum-stability": "dev",
"require": {
"oat-sa/oatbox-extension-installer": "~1.1||dev-master",
"oat-sa/generis": ">=15.24",
"oat-sa/tao-core": ">=52.1",
"oat-sa/generis": ">=15.32.0",
"oat-sa/tao-core": ">=53.11.4",
"oat-sa/extension-tao-backoffice": ">=6.0.0",
"oat-sa/extension-tao-item": ">=11.3"
},
Expand Down
6 changes: 4 additions & 2 deletions manifest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2014-2022 (original work) Open Assessment Technologies SA;
* Copyright (c) 2014-2023 (original work) Open Assessment Technologies SA;
*/

use oat\taoDacSimple\model\Copy\ServiceProvider\CopyServiceProvider;
use oat\taoDacSimple\model\ServiceProvider\PermissionsServiceProvider;
use oat\taoDacSimple\scripts\install\AttachEventHandler;
use oat\taoDacSimple\scripts\update\Updater;
use oat\taoDacSimple\scripts\install\SetupDataAccess;
Expand Down Expand Up @@ -65,6 +66,7 @@
'BASE_URL' => ROOT_URL . 'taoDacSimple/',
],
'containerServiceProviders' => [
CopyServiceProvider::class
CopyServiceProvider::class,
PermissionsServiceProvider::class
]
];
4 changes: 3 additions & 1 deletion model/AdminService.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ public static function setOwner($resourceUri, $userUri)
}
}

return $db->addPermissions($userUri, $resourceUri, ['OWNER']);
$db->addPermissions($userUri, $resourceUri, ['OWNER']);

return true;
}

/**
Expand Down
167 changes: 167 additions & 0 deletions model/ChangePermissionsService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<?php

/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2023 (original work) Open Assessment Technologies SA.
*/

declare(strict_types=1);

namespace oat\taoDacSimple\model;

use core_kernel_classes_Resource;
use oat\oatbox\event\EventManager;
use oat\tao\model\event\DataAccessControlChangedEvent;
use oat\taoDacSimple\model\Command\ChangeAccessCommand;
use oat\taoDacSimple\model\Command\ChangePermissionsCommand;
use oat\taoDacSimple\model\event\DacAffectedUsersEvent;
use oat\taoDacSimple\model\event\DacRootChangedEvent;

class ChangePermissionsService
{
private DataBaseAccess $dataBaseAccess;
private PermissionsStrategyInterface $strategy;
private EventManager $eventManager;

public function __construct(
DataBaseAccess $dataBaseAccess,
PermissionsStrategyInterface $strategy,
EventManager $eventManager
) {
$this->dataBaseAccess = $dataBaseAccess;
$this->strategy = $strategy;
$this->eventManager = $eventManager;
}

public function change(ChangePermissionsCommand $command): void
{
$resources = $this->getResourceToUpdate($command->getRoot(), $command->isRecursive());

$permissions = $this->dataBaseAccess->getResourcesPermissions(array_column($resources, 'id'));
$rootPermissions = $permissions[$command->getRoot()->getUri()];
$permissionsDelta = $this->strategy->normalizeRequest($rootPermissions, $command->getPrivilegesPerUser());

if (empty($permissionsDelta['remove']) && empty($permissionsDelta['add'])) {
return;
}

$changeAccessCommand = $this->calculateChanges($resources, $permissionsDelta, $permissions);
$this->dataBaseAccess->changeAccess($changeAccessCommand);

$this->triggerEvents($command->getRoot(), $permissionsDelta, $command->isRecursive());
}

private function getResourceToUpdate(core_kernel_classes_Resource $resource, bool $isRecursive): array
{
if ($isRecursive) {
$resources = [];

foreach ($resource->getNestedResources() as $result) {
$resources[$result['id']] = $result;
}

return $resources;
}

return [
$resource->getUri() => [
'id' => $resource->getUri(),
'isClass' => $resource->isClass(),
'level' => 1,
],
];
}

private function calculateChanges(
array $resources,
array $permissionsDelta,
array $currentPermissions
): ChangeAccessCommand {
$command = new ChangeAccessCommand();

foreach ($resources as $resource) {
$resourcePermissions = $currentPermissions[$resource['id']];

$remove = $this->strategy->getPermissionsToRemove($resourcePermissions, $permissionsDelta);
$add = $this->strategy->getPermissionsToAdd($resourcePermissions, $permissionsDelta);

foreach ($remove as $userId => $permissions) {
$resourcePermissions[$userId] = array_diff($resourcePermissions[$userId] ?? [], $permissions);

foreach ($permissions as $permission) {
$command->revokeResourceForUser($resource['id'], $permission, $userId);
}
}

foreach ($add as $userId => $permissions) {
$resourcePermissions[$userId] = array_merge($resourcePermissions[$userId] ?? [], $permissions);

foreach ($permissions as $permission) {
$command->grantResourceForUser($resource['id'], $permission, $userId);
}
}

$this->assertHasUserWithGrantPermission($resource['id'], $resourcePermissions);
}

return $command;
}

/**
* Checks if all resources after all actions are applied will have at least
* one user with GRANT permission.
*/
private function assertHasUserWithGrantPermission(string $resourceId, array $resourcePermissions): void
{
foreach ($resourcePermissions as $permissions) {
if (in_array(PermissionProvider::PERMISSION_GRANT, $permissions, true)) {
return;
}
}

throw new PermissionsServiceException(
sprintf(
'Resource %s should have at least one user with GRANT access',
$resourceId
)
);
}

private function triggerEvents(
core_kernel_classes_Resource $resource,
array $permissionsDelta,
bool $isRecursive
): void {
if (!empty($permissionsDelta['add']) || !empty($permissionsDelta['remove'])) {
$this->eventManager->trigger(new DacRootChangedEvent($resource, $permissionsDelta));
}

$this->eventManager->trigger(
new DataAccessControlChangedEvent(
$resource->getUri(),
$permissionsDelta,
$isRecursive
)
);

$this->eventManager->trigger(
new DacAffectedUsersEvent(
array_keys($permissionsDelta['add']),
array_keys($permissionsDelta['remove'])
)
);
}
}
130 changes: 130 additions & 0 deletions model/Command/ChangeAccessCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2023 (original work) Open Assessment Technologies SA.
*/

declare(strict_types=1);

namespace oat\taoDacSimple\model\Command;

class ChangeAccessCommand
{
/**
* An array in the form ['userId' [ 'READ' => ['resource1', 'resource2']]]
*
* @var string[][][]
*/
private array $userRevokedPermissions = [];

/**
* An array in the form ['resourceId' [ 'READ' => ['userId1', 'userId2']]]
*
* @var string[][][]
*/
private array $giveAccessMap = [];

/**
* An array in the form ['resourceId' [ 'READ' => ['userId1', 'userId2']]]
*
* @var string[][][]
*/
private array $revokeAccessMap = [];

public function grantResourceForUser(string $resourceId, string $permission, string $userId): void
{
$this->giveAccessMap[$resourceId] ??= [];
$this->giveAccessMap[$resourceId][$permission] ??= [];

if (!in_array($userId, $this->giveAccessMap[$resourceId][$permission], true)) {
$this->giveAccessMap[$resourceId][$permission][] = $userId;
}
}

public function getResourceIdsToGrant(): array
{
return array_keys($this->giveAccessMap);
}

public function getUserIdsToGrant(string $resourceId, string $permission): array
{
return $this->giveAccessMap[$resourceId][$permission] ?? [];
}

public function revokeResourceForUser(string $resourceId, string $permission, string $userId): void
{
$this->revokeAccessMap[$resourceId] ??= [];
$this->revokeAccessMap[$resourceId][$permission] ??= [];

if (!in_array($userId, $this->revokeAccessMap[$resourceId][$permission], true)) {
$this->revokeAccessMap[$resourceId][$permission][] = $userId;
}

$this->userRevokedPermissions[$userId] ??= [];
$this->userRevokedPermissions[$userId][$permission] ??= [];

if (!in_array($resourceId, $this->userRevokedPermissions[$userId][$permission], true)) {
$this->userRevokedPermissions[$userId][$permission][] = $resourceId;
}
}

public function removeRevokeResourceForUser(string $resourceId, string $permission, string $userId): void
{
$this->userRevokedPermissions[$userId][$permission] ??= [];

$key = array_search($resourceId, $this->userRevokedPermissions[$userId][$permission]);

if ($key !== false) {
unset($this->userRevokedPermissions[$userId][$permission][$key]);
}

$this->revokeAccessMap[$resourceId][$permission] ??= [];

$key = array_search($userId, $this->revokeAccessMap[$resourceId][$permission]);

if ($key === false) {
return;
}

unset($this->revokeAccessMap[$resourceId][$permission][$key]);
}

public function getResourceIdsToRevoke(): array
{
return array_keys($this->revokeAccessMap);
}

public function getUserIdsToRevoke(string $resourceId, string $permission): array
{
return $this->revokeAccessMap[$resourceId][$permission] ?? [];
}

public function getUserIdsToRevokePermissions(): array
{
return array_keys($this->userRevokedPermissions);
}

public function getUserPermissionsToRevoke(string $userId): array
{
return array_keys($this->userRevokedPermissions[$userId]);
}

public function getResourceIdsByUserAndPermissionToRevoke(string $userId, string $permission): array
{
return $this->userRevokedPermissions[$userId][$permission] ?? [];
}
}
Loading

0 comments on commit 4ccc450

Please sign in to comment.