Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion CodecheckPlugin.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace APP\plugins\generic\codecheck;

use PKP\security\Role;
use APP\core\Application;
use APP\template\TemplateManager;
use APP\plugins\generic\codecheck\classes\FrontEnd\ArticleDetails;
Expand All @@ -18,6 +19,8 @@
use PKP\core\JSONMessage;
use APP\plugins\generic\codecheck\classes\Constants;
use APP\plugins\generic\codecheck\controllers\page\CodecheckPageHandler;
use APP\plugins\generic\codecheck\classes\CodecheckRoles\CodecheckRoleArray;
use APP\plugins\generic\codecheck\classes\CodecheckRoles\CodecheckRoleManager;

class CodecheckPlugin extends GenericPlugin
{
Expand Down Expand Up @@ -91,7 +94,18 @@ public function setupAPIHandler(string $hookName, array $args): void

if (str_contains($request->getRequestPath(), 'api/v1/codecheck')) {
CodecheckLogger::debug('Instantiating the CODECHECK APIHandler');
$apiHandler = new CodecheckApiHandler($this, $request);

$adminRoles = new CodecheckRoleArray([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN]);
$editRoles = new CodecheckRoleArray([$adminRoles, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_MANAGER]);
$readRoles = new CodecheckRoleArray([$editRoles, Role::ROLE_ID_READER, Role::ROLE_ID_AUTHOR]);

$roles = new CodecheckRoleManager(
readMetadata: $readRoles,
editMetadata: $editRoles,
admin: $adminRoles,
);

$apiHandler = new CodecheckApiHandler($this, $request, $roles);
CodecheckLogger::debug('API request: ' . $request->getRequestPath());
}

Expand Down
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ $this->endpoints = [
[
'route' => 'your endpoint route',
'handler' => [$this, 'yourFunction'],
'roles' => $this->roles,
'role' => CodecheckRole::class, // give the `'Role'` property a class that extends CodecheckRole
],
],
];
Expand All @@ -293,6 +293,19 @@ private function yourFunction(): void
}
```

Finally your defined `CodecheckRoleArray` can have the following PKP rules (`PKP\security\Role`):

| Constant | Role name |
|---------------------------|-------------------------------------|
|`Role::ROLE_ID_SITE_ADMIN` | Site Administrator |
|`Role::ROLE_ID_MANAGER` | Manager Journal/Press/Server Manager|
|`Role::ROLE_ID_SUB_EDITOR` | Sub Editor |
|`Role::ROLE_ID_ASSISTANT` | Editorial assistant / support role |
|`Role::ROLE_ID_REVIEWER` | Reviewer |
|`Role::ROLE_ID_AUTHOR` | Author |
|`Role::ROLE_ID_READER` | Reader |
|`Role::ROLE_ID_SUBSCRIPTION_MANAGER` | Subscription Manager |

## Running Tests

The plugin includes comprehensive test coverage for both backend PHP code and frontend Vue components.
Expand Down
29 changes: 29 additions & 0 deletions api/v1/ApiEndpoint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace APP\plugins\generic\codecheck\api\v1;

use APP\plugins\generic\codecheck\classes\CodecheckRoles\CodecheckRoleArray;

class ApiEndpoint
{
private array $endpoint;

public function __construct(array $endpointList, string $route, string $requestMethod) {
foreach ($endpointList[$requestMethod] as $endpoint) {
if($route == $endpoint['route']) {
$this->endpoint = $endpoint;
break;
}
}
}

public function getHandler(): array
{
return $this->endpoint['handler'];
}

public function getRoles(): CodecheckRoleArray
{
return $this->endpoint['roles'];
}
}
95 changes: 54 additions & 41 deletions api/v1/CodecheckApiHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

namespace APP\plugins\generic\codecheck\api\v1;

use PKP\security\Role;
use APP\plugins\generic\codecheck\api\v1\JsonResponse;
use APP\core\Request;

use APP\plugins\generic\codecheck\classes\Exceptions\ApiCreateException;
use APP\plugins\generic\codecheck\classes\Exceptions\ApiFetchException;
use APP\plugins\generic\codecheck\classes\Exceptions\NoMatchingIssuesFoundException;
Expand All @@ -23,14 +21,13 @@

use APP\facades\Repo;
use \Github\Client;
use APP\plugins\generic\codecheck\classes\Exceptions\CurlExceptions\CurlInitException;
use APP\plugins\generic\codecheck\classes\Exceptions\CurlExceptions\CurlReadException;
use Illuminate\Support\Facades\DB;
use APP\plugins\generic\codecheck\classes\CodecheckRoles\CodecheckRoleManager;
use APP\plugins\generic\codecheck\classes\Exceptions\RoleExceptions\RoleNotFoundException;

class CodecheckApiHandler
{
private JsonResponse $response;
private array $roles;
private CodecheckRoleManager $roles;
private array $endpoints;
private string $route;
private CodecheckPlugin $plugin;
Expand All @@ -41,9 +38,10 @@ class CodecheckApiHandler
* Initialize the Codecheck APIHandler class
*
* @param Request $request API Request
* @param CodecheckRoleManager $roles The CODECHECK roles for `read`, `write` and `standard` access to the API routes
* @return void
*/
public function __construct(CodecheckPlugin $plugin, Request $request)
public function __construct(CodecheckPlugin $plugin, Request $request, CodecheckRoleManager $roles)
{
$this->plugin = $plugin;

Expand All @@ -54,75 +52,81 @@ public function __construct(CodecheckPlugin $plugin, Request $request)

$this->codecheckMetadataHandler = new CodecheckMetadataHandler($request, new Client(), new CurlApiClient());

$this->roles = [
Role::ROLE_ID_MANAGER,
Role::ROLE_ID_SUB_EDITOR,
Role::ROLE_ID_ASSISTANT,
Role::ROLE_ID_AUTHOR
];
$this->roles = $roles;

$this->endpoints = [
'GET' => [
[
'route' => 'venue',
'handler' => [$this, 'getVenueData'],
'roles' => $this->roles,
'roles' => $roles->readMetadata(),
],
[
'route' => 'metadata',
'handler' => [$this, 'getMetadata'],
'roles' => $this->roles,
'roles' => $roles->readMetadata(),
],
[
'route' => 'download',
'handler' => [$this, 'downloadFile'],
'roles' => $this->roles,
'roles' => $roles->readMetadata(),
],
[
'route' => 'yaml',
'handler' => [$this, 'generateYaml'],
'roles' => $this->roles,
'roles' => $roles->readMetadata(),
],
],
'POST' => [
[
'route' => 'identifier',
'handler' => [$this, 'reserveIdentifier'],
'roles' => $this->roles,
'roles' => $roles->editMetadata(),
],
[
'route' => 'metadata',
'handler' => [$this, 'saveMetadata'],
'roles' => $this->roles,
'roles' => $roles->editMetadata(),
],
[
'route' => 'upload',
'handler' => [$this, 'uploadFile'],
'roles' => $this->roles,
'roles' => $roles->editMetadata(),
],
[
'route' => 'repository',
'handler' => [$this, 'loadMetadataFromRepository'],
'roles' => $this->roles,
'roles' => $roles->editMetadata(),
],
[
'route' => 'yaml/validate',
'handler' => [$this, 'validateYamlStructure'],
'roles' => $this->roles,
'roles' => $roles->readMetadata(),
],
],
];

$this->request = $request;

$this->authorize();

// Get the API Route that was called from the request
$this->route = $this->getRouteFromRequest();

$this->authorize();

// Serve the Request
$this->serveRequest();
}

private function getEndpoint(): ApiEndpoint
{
// get the request Method like POST or GET
$requestMethod = $this->request->getRequestMethod();

error_log("Method: " . $requestMethod);

return new ApiEndpoint($this->endpoints, $this->route, $requestMethod);
}

/**
* Authorize the API connection
*
Expand All @@ -144,12 +148,24 @@ public function authorize()
// Check if the user that accesses this resource has at least one valid Role and if user exists
$user = $this->request->getUser() ?? null;
$contextId = $this->request->getContext()->getId();
$apiEndpoint = $this->getEndpoint();
$codecheckRole = $apiEndpoint->getRoles();

try {
$pkpRoles = $codecheckRole->getRoles();

if(!($user && $user->hasRole($this->roles, $contextId))) {
if(!($user && $user->hasRole($pkpRoles, $contextId))) {
JsonResponse::staticResponse([
'success' => false,
'error' => "User has no assigned Role or doesn't have the right roles assigned to access this resource"
], 400);
return;
}
} catch (RoleNotFoundException $roleNotFoundException) {
JsonResponse::staticResponse([
'success' => false,
'error' => "User has no assigned Role or doesn't have the right roles assigned to access this resource"
], 400);
'error' => $roleNotFoundException->getMessage()
], $roleNotFoundException->getCode());
return;
}
}
Expand All @@ -176,16 +192,13 @@ private function getRouteFromRequest(): ?string
private function serveRequest(): void
{
// get the request Method like POST or GET
$method = $this->request->getRequestMethod();
$requestMethod = $this->request->getRequestMethod();

CodecheckLogger::debug('Method: ' . $method);
CodecheckLogger::debug('Method: ' . $requestMethod);

foreach ($this->endpoints[$method] as $endpoint) {
if($this->route == $endpoint['route']) {
call_user_func($endpoint['handler']);
return;
}
}
$apiEndpoint = $this->getEndpoint();

call_user_func($apiEndpoint->getHandler());
}

/**
Expand Down Expand Up @@ -383,11 +396,11 @@ public function getMetadata(): void
$result = $this->codecheckMetadataHandler->getMetadata($this->request, $submissionId);

if(isset($result['error'])) {
$result = array_merge($result, ['submissionID' => $submissionId]);
$result = array_merge($result, ['success' => false, 'submissionID' => $submissionId]);
JsonResponse::staticResponse($result, 404);
}

JsonResponse::staticResponse($result, 200);
JsonResponse::staticResponse(array_merge($result, ['success' => true]), 200);
}

/**
Expand All @@ -401,11 +414,11 @@ public function saveMetadata(): void
$result = $this->codecheckMetadataHandler->saveMetadata($this->request, $submissionId);

if(isset($result['error'])) {
$result = array_merge($result, ['submissionID' => $submissionId]);
$result = array_merge($result, ['success' => false, 'submissionID' => $submissionId]);
JsonResponse::staticResponse($result, 404);
}

JsonResponse::staticResponse($result, 200);
JsonResponse::staticResponse(array_merge($result, ['success' => true]), 200);
}

/**
Expand Down Expand Up @@ -554,11 +567,11 @@ public function generateYaml(): void
$result = $this->codecheckMetadataHandler->generateYaml($this->request, $submissionId);

if(isset($result['error'])) {
$result = array_merge($result, ['submissionID' => $submissionId]);
$result = array_merge($result, ['success' => false, 'submissionID' => $submissionId]);
JsonResponse::staticResponse($result, 404);
}

JsonResponse::staticResponse($result, 200);
JsonResponse::staticResponse(array_merge($result, ['success' => true]), 200);
}

/**
Expand Down
43 changes: 43 additions & 0 deletions classes/CodecheckRoles/CodecheckRoleArray.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace APP\plugins\generic\codecheck\classes\CodecheckRoles;

use APP\plugins\generic\codecheck\classes\DataStructures\UniqueArray;

class CodecheckRoleArray
{
private UniqueArray $roles;

/**
* This takes an array of PKP Roles and / or CodecheckRoleArrays -> so Roles can be defined dependant on each other
*
* @param array $roles The array of the PKPRoles which are allowed to access this Codecheck Resource
*/
public function __construct(array $roles)
{
$this->roles = new UniqueArray();
$this->addRoles($roles);
}

private function addRoles(mixed $roles) {
foreach ($roles as $role) {
if($role instanceof CodecheckRoleArray) {
$this->addRoles($role->getRoles());
} elseif (is_array($role)) {
$this->addRoles($role);
} else {
$this->roles->add($role);
}
}
}

/**
* Get the PKP roles
*
* @return array The array containing all PKP roles of this `CodecheckRole`
*/
public function getRoles(): array
{
return $this->roles->toArray();
}
}
18 changes: 18 additions & 0 deletions classes/CodecheckRoles/CodecheckRoleManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace APP\plugins\generic\codecheck\classes\CodecheckRoles;

use APP\plugins\generic\codecheck\classes\CodecheckRoles\CodecheckRoleArray;

class CodecheckRoleManager
{
public function __construct(
private readonly CodecheckRoleArray $readMetadata,
private readonly CodecheckRoleArray $editMetadata,
private readonly CodecheckRoleArray $admin,
) {}

public function readMetadata(): CodecheckRoleArray { return $this->readMetadata; }
public function editMetadata(): CodecheckRoleArray { return $this->editMetadata; }
public function admin(): CodecheckRoleArray { return $this->admin; }
}
Loading
Loading