Skip to content

Commit

Permalink
Simplified Authorization [SLE-192][SLE-66]
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelgfeller committed Dec 4, 2023
1 parent 9b6d5c7 commit 78667c3
Show file tree
Hide file tree
Showing 25 changed files with 155 additions and 226 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"fig/http-message-util": "^1.1",
"ext-gettext": "*",
"ext-intl": "*",
"php": "^8.1",
"php": "^8.2",
"cakephp/validation": "^5.0"
},
"autoload": {
Expand Down
29 changes: 0 additions & 29 deletions public/assets/client/note/client-read-template-note.html.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,35 +41,6 @@ export function getNoteHtml(note) {
</div>`;
}

/**
* Check if user has required privilege
* If the received privilege contains one
* of the following letters, it means:
* D - Delete - Highest privilege, may also do other actions
* U - Update - May also create and read but not delete
* C - Create - May also read
* R - Read - May only read but do nothing else
* *
* @param {string} actualPrivilege
* @param {string} requiredPrivilege
* @return {boolean}
*/
function userHasPrivilegeTo(actualPrivilege, requiredPrivilege) {
switch (requiredPrivilege) {
// Starting from the highest privilege to the lowest
case 'D':
return actualPrivilege.includes('D');
case 'U':
return actualPrivilege.includes('U');
case 'C':
return actualPrivilege.includes('C');
case 'R':
return actualPrivilege.includes('R');
default:
return false;
}
}

export function getClientNoteLoadingPlaceholderHtml() {
return `<div class="client-note-loading-placeholder">
<!-- Note label container-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public function __invoke(
$this->templateRenderer->addPhpViewAttribute('clientListFilters', $clientListFilters);
$this->templateRenderer->addPhpViewAttribute(
'clientCreatePrivilege',
$this->clientPermissionVerifier->isGrantedToCreate() ? Privilege::CREATE : Privilege::NONE
$this->clientPermissionVerifier->isGrantedToCreate() ? Privilege::CR->name : Privilege::N->name
);

return $this->templateRenderer->render($response, 'client/clients-list.html.php');
Expand Down
2 changes: 1 addition & 1 deletion src/Domain/Authorization/AuthorizationChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function isAuthorizedByRole(UserRole $minimalRequiredRole): bool
$authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId(
$this->userNetworkSessionData->userId
);
// Returns array with role name as key and hierarchy as value [role_name => hierarchy_int]
// Returns array with role name as key and hierarchy as value ['role_name' => hierarchy_int]
// * Lower hierarchy number means higher privileged role
$userRoleHierarchies = $this->userRoleFinderRepository->getUserRolesHierarchies();

Expand Down
69 changes: 26 additions & 43 deletions src/Domain/Authorization/Privilege.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,35 @@

namespace App\Domain\Authorization;

enum Privilege: string
enum Privilege
{
// The case values are the first letter of the privilege name.
// These are returned on a JSON Ajax request and used by the frontend to determine if the user has
// a certain privilege because JS doesn't have access to the hasPrivilege() function.
// The case names and values correspond to the following privileges:
// R: Read, C: Create, U: Update, D: Delete
// They can be combined or used individually depending on the needs of the application.
// To check if a privilege is allowed, the frontend can check if the letter of the privilege is in the value.
// For instance, if update privilege is required, the client will check if privilege value contains "U".

// No rights
case NONE = 'NONE';
// Allowed to read
case READ = 'R';
// Allowed to read and create
case CREATE = 'CR';
// Allowed only to create (needed when the user is not allowed to see hidden notes but may create some)
case ONLY_CREATE = 'C';
// Allowed to read, create and update
case UPDATE = 'CRU';
// Allowed to read, create, update and delete
case DELETE = 'CRUD';
// Allowed to do everything on each note
// case ALL = 'ALL';
case N;
// Allowed to Read
case R;
// Allowed to Create and Read
case CR;
// Allowed only to Create (needed when the user is not allowed to see hidden notes but may create some)
case C;
// Allowed to Read, Create and Update
case CRU;
// Allowed to Read, Create, Update and Delete
case CRUD;

/**
* Checks if the current privilege allows for the required privilege.
*
* This method uses a match expression to check if the current privilege ($this) allows for the required privilege.
* The match expression checks the required privilege against each possible privilege case.
* For each case, it checks if the current privilege is in an array of privileges that allow for the required privilege.
* If the current privilege is in the array, the method returns true, indicating that the required privilege is allowed.
* If the current privilege is not in the array, the method continues to the next case.
* If no cases match the required privilege, the method returns false,
* indicating that the required privilege is not allowed.
*
* @param Privilege $requiredPrivilege The required privilege.
* @return bool True if the current privilege allows for the required privilege, false otherwise.
*/
public function hasPrivilege(Privilege $requiredPrivilege): bool
{
return match ($requiredPrivilege) {
// Privilege READ is true if $this is either READ, CREATE, UPDATE or DELETE
self::READ => in_array($this, [self::READ, self::CREATE, self::UPDATE, self::DELETE], true),
self::CREATE => in_array($this, [self::CREATE, self::ONLY_CREATE, self::UPDATE, self::DELETE], true),
self::ONLY_CREATE => in_array($this, [self::CREATE, self::ONLY_CREATE], true),
self::UPDATE => in_array($this, [self::UPDATE, self::DELETE], true),
self::DELETE => $this === self::DELETE,
self::NONE => true,
};
}

// Initially, the Privilege Enum was the datatype in result objects that was passed to the PHP templates.
// The case names were the name of the highest privilege (Read, Create, Update, Delete).
// The values were the letters of the associated permissions meaning Delete was 'CRUD', Update was 'CRU' and so on.
// This was needed for data returned via Ajax.
// The frontend could then check if the privilege value contained the letter of the required privilege and
// the PHP templates called `hasPrivilege()` on the Privilege Enum with the required privilege as argument.
// This meant that there were 2 different ways to check the same thing.
// Simplicity is key, so the privilege value that goes to the frontend is now a string even for PHP templates
// and the names of the Enum cases simply the letters with the permissions.
}
10 changes: 4 additions & 6 deletions src/Domain/Client/Data/ClientListResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@

namespace App\Domain\Client\Data;

use App\Domain\Authorization\Privilege;

/**
* Aggregate DTO to store ClientData combined with
* client status and assigned user privileges.
*/
class ClientListResult extends ClientData
{
public ?Privilege $clientStatusPrivilege = null;
public ?Privilege $assignedUserPrivilege = null;
public ?string $clientStatusPrivilege = null;
public ?string $assignedUserPrivilege = null;

public function __construct(array $clientResultData = [])
{
Expand All @@ -24,8 +22,8 @@ public function __construct(array $clientResultData = [])
public function jsonSerialize(): array
{
return array_merge(parent::jsonSerialize(), [
'clientStatusPrivilege' => $this->clientStatusPrivilege?->value,
'assignedUserPrivilege' => $this->assignedUserPrivilege?->value,
'clientStatusPrivilege' => $this->clientStatusPrivilege,
'assignedUserPrivilege' => $this->assignedUserPrivilege,
]);
}
// No need for toArrayForDatabase() as this is a result DTO
Expand Down
17 changes: 8 additions & 9 deletions src/Domain/Client/Data/ClientReadResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace App\Domain\Client\Data;

use App\Domain\Authorization\Privilege;
use App\Domain\Note\Data\NoteData;

/**
Expand All @@ -20,10 +19,10 @@ class ClientReadResult extends ClientData
public ?NoteData $mainNoteData = null; // Main note data

// Client personal info privilege (first-, second name, phone, email, location)
public ?Privilege $generalPrivilege = null;
public ?Privilege $clientStatusPrivilege = null;
public ?Privilege $assignedUserPrivilege = null;
public ?Privilege $noteCreatePrivilege = null;
public ?string $generalPrivilege = null;
public ?string $clientStatusPrivilege = null;
public ?string $assignedUserPrivilege = null;
public ?string $noteCreatePrivilege = null;

public function __construct(array $clientResultData = [])
{
Expand All @@ -48,10 +47,10 @@ public function jsonSerialize(): array
'notesAmount' => $this->notesAmount,
'mainNoteData' => $this->mainNoteData,

'personalInfoPrivilege' => $this->generalPrivilege?->value,
'clientStatusPrivilege' => $this->clientStatusPrivilege?->value,
'assignedUserPrivilege' => $this->assignedUserPrivilege?->value,
'noteCreatePrivilege' => $this->noteCreatePrivilege?->value,
'personalInfoPrivilege' => $this->generalPrivilege,
'clientStatusPrivilege' => $this->clientStatusPrivilege,
'assignedUserPrivilege' => $this->assignedUserPrivilege,
'noteCreatePrivilege' => $this->noteCreatePrivilege,
]);
}
// No need for toArrayForDatabase() as this is a result DTO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public function isGrantedToCreate(?ClientData $client = null): bool
$authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId(
$this->loggedInUserId
);
// Returns array with role name as key and hierarchy as value [role_name => hierarchy_int]
// Returns array with role name as key and hierarchy as value ['role_name' => hierarchy_int]
// * Lower hierarchy number means higher privileged role
$userRoleHierarchies = $this->userRoleFinderRepository->getUserRolesHierarchies();

Expand Down Expand Up @@ -99,7 +99,7 @@ public function isGrantedToAssignUserToClient(
);
}
if ($userRoleHierarchies === null) {
// Returns array with role name as key and hierarchy as value [role_name => hierarchy_int]
// Returns array with role name as key and hierarchy as value ['role_name' => hierarchy_int]
// * Lower hierarchy number means higher privileged role
$userRoleHierarchies = $this->userRoleFinderRepository->getUserRolesHierarchies();
}
Expand Down Expand Up @@ -141,7 +141,7 @@ public function isGrantedToUpdate(array $clientDataToUpdate, ?int $ownerId, bool
$authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId(
$this->loggedInUserId
);
// Returns array with role name as key and hierarchy as value [role_name => hierarchy_int]
// Returns array with role name as key and hierarchy as value ['role_name' => hierarchy_int]
// * Lower hierarchy number means higher privileged role
$userRoleHierarchies = $this->userRoleFinderRepository->getUserRolesHierarchies();

Expand Down Expand Up @@ -222,7 +222,7 @@ public function isGrantedToDelete(?int $ownerId, bool $log = true): bool
$authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId(
$this->loggedInUserId
);
// Returns array with role name as key and hierarchy as value [role_name => hierarchy_int]
// Returns array with role name as key and hierarchy as value ['role_name' => hierarchy_int]
// * Lower hierarchy number means higher privileged role
$userRoleHierarchies = $this->userRoleFinderRepository->getUserRolesHierarchies();

Expand Down Expand Up @@ -280,7 +280,7 @@ public function isGrantedToRead(
$authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId(
$this->loggedInUserId
);
// Returns array with role name as key and hierarchy as value [role_name => hierarchy_int]
// Returns array with role name as key and hierarchy as value ['role_name' => hierarchy_int]
// * Lower hierarchy number means higher privileged role
$userRoleHierarchies = $this->userRoleFinderRepository->getUserRolesHierarchies();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,21 @@ public function __construct(
* @param int|null $clientOwnerId
* @param string|null $column
*
* @return Privilege
* @return string
*/
public function determineMutationPrivilege(?int $clientOwnerId, ?string $column = null): Privilege
public function determineMutationPrivilege(?int $clientOwnerId, ?string $column = null): string
{
// Check first against the highest privilege, if allowed, directly return otherwise continue down the chain
if ($this->clientPermissionVerifier->isGrantedToDelete($clientOwnerId, false)) {
return Privilege::DELETE;
return Privilege::CRUD->name;
}
// Value does not matter as keys are relevant
if ($column !== null
&& $this->clientPermissionVerifier->isGrantedToUpdate([$column => 'value'], $clientOwnerId, false)
) {
return Privilege::UPDATE;
return Privilege::CRU->name;
}

return Privilege::NONE;
return Privilege::N->name;
}
}
2 changes: 1 addition & 1 deletion src/Domain/Client/Service/ClientFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public function findClientReadAggregate(int $clientId): ClientReadResult
0,
$clientResultAggregate->userId,
false
) ? Privilege::CREATE : Privilege::NONE;
) ? Privilege::CR->name : Privilege::N->name;

$clientResultAggregate->notesAmount = $this->noteFinder->findClientNotesAmount($clientId);

Expand Down
2 changes: 1 addition & 1 deletion src/Domain/Note/Data/NoteData.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class NoteData implements \JsonSerializable
public ?UserData $user;

// User mutation rights from authenticated user
public Privilege $privilege = Privilege::NONE; // json_encode automatically takes $enum->value
public string $privilege = Privilege::N->name; // json_encode automatically takes $enum->value

public function __construct(?array $noteValues = null)
{
Expand Down
6 changes: 2 additions & 4 deletions src/Domain/Note/Data/NoteResultData.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

namespace App\Domain\Note\Data;

use App\Domain\Authorization\Privilege;

/**
* Note with user and client full name and privilege.
*/
Expand All @@ -14,7 +12,7 @@ class NoteResultData extends NoteData
public ?bool $isClientMessage = false;

// Populated in NoteUserRightSetter
public Privilege $privilege; // json_encode automatically takes $enum->value
public string $privilege; // json_encode automatically takes $enum->value

public function __construct(array $noteValues = [])
{
Expand All @@ -29,7 +27,7 @@ public function jsonSerialize(): array
return array_merge(parent::jsonSerialize(), [
'userFullName' => $this->userFullName,
'clientFullName' => $this->clientFullName,
'privilege' => $this->privilege->value,
'privilege' => $this->privilege,
'isClientMessage' => (int)$this->isClientMessage,
]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function isGrantedToRead(
$authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId(
$loggedInUserId
);
// Returns array with role name as key and hierarchy as value [role_name => hierarchy_int]
// Returns array with role name as key and hierarchy as value ['role_name' => hierarchy_int]
// * Lower hierarchy number means higher privileged role
$userRoleHierarchies = $this->userRoleFinderRepository->getUserRolesHierarchies();
// newcomers may see all notes and main notes
Expand Down Expand Up @@ -77,7 +77,7 @@ public function isGrantedToCreate(int $isMain = 0, ?int $clientOwnerId = null, b
$authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId(
$loggedInUserId
);
// Returns array with role name as key and hierarchy as value [role_name => hierarchy_int]
// Returns array with role name as key and hierarchy as value ['role_name' => hierarchy_int]
// * Lower hierarchy number means higher privileged role
$userRoleHierarchies = $this->userRoleFinderRepository->getUserRolesHierarchies();
if (($isMain === 0 // newcomers may see create notes for any client
Expand Down Expand Up @@ -117,7 +117,7 @@ public function isGrantedToUpdate(
$authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId(
$loggedInUserId
);
// Returns array with role name as key and hierarchy as value [role_name => hierarchy_int]
// Returns array with role name as key and hierarchy as value ['role_name' => hierarchy_int]
// * Lower hierarchy number means higher privileged role
$userRoleHierarchies = $this->userRoleFinderRepository->getUserRolesHierarchies();

Expand Down Expand Up @@ -161,7 +161,7 @@ public function isGrantedToDelete(
$authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId(
$loggedInUserId
);
// Returns array with role name as key and hierarchy as value [role_name => hierarchy_int]
// Returns array with role name as key and hierarchy as value ['role_name' => hierarchy_int]
// * Lower hierarchy number means higher privileged role
$userRoleHierarchies = $this->userRoleFinderRepository->getUserRolesHierarchies();

Expand Down
Loading

0 comments on commit 78667c3

Please sign in to comment.