Skip to content
Merged
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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"require": {
"php": ">=8.3",
"bedita/i18n": "^5.1.0",
"bedita/web-tools": "^5.1.0",
"bedita/web-tools": "^5.3",
"cakephp/authentication": "^2.9",
"cakephp/cakephp": "~4.5.0",
"cakephp/plugin-installer": "^1.3",
Expand Down
16 changes: 16 additions & 0 deletions config/app_local.example.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@
// //],
// ],

/**
* Api Proxy configuration, for ApiController.
* This refers to `/api/{endpoint}` calls.
* Contains an array of setting to use for API proxy configuration.
*
* ## Options
*
* - `blocked` - Array of blocked methods per endpoint.
*/
// 'ApiProxy' => [
// 'blocked' => [
// 'objects' => ['GET', 'POST', 'PATCH', 'DELETE'],
// 'users' => ['GET', 'POST', 'PATCH', 'DELETE'],
// ],
// ],

/**
* Clone configuration.
* This adds custom rules to clone objects.
Expand Down
17 changes: 17 additions & 0 deletions config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,23 @@
['pass' => ['id', 'relation', 'format', 'query'], '_name' => 'export:related:filtered']
);

$routes->get(
'/history/objects',
['controller' => 'History', 'action' => 'objects'],
'history:objects',
);

$routes->get(
'/users/list',
['controller' => 'Modules', 'action' => 'users'],
'users:list',
);
$routes->connect(
'/resources/get/{id}',
['controller' => 'Modules', 'action' => 'get'],
['pass' => ['id'], '_name' => 'resource:get'],
);

// Download stream
$routes->get(
'/download/{id}',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export default {
return fetch(`/api/history?${query}`).then((r) => r.json());
},
async getObjects(ids) {
return fetch(`/api/objects?filter[id]=${ids.join(',')}`).then((r) => r.json());
return fetch(`/history/objects?filter[id]=${ids.join(',')}`).then((r) => r.json());
},
loadHistory(pageSize = 20, page = 1) {
this.loading = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export default {
return this.cache[id];
}
const baseUrl = new URL(BEDITA.base).pathname;
const response = await fetch(`${baseUrl}api/objects/${id}`, {
const response = await fetch(`${baseUrl}resources/get/${id}`, {
credentials: 'same-origin',
headers: {
accept: 'application/json',
Expand Down
2 changes: 1 addition & 1 deletion resources/js/app/components/property-view/property-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export default {
const creatorId = this.object?.meta?.created_by;
const modifierId = this.object?.meta?.modified_by;
const usersId = [creatorId, modifierId];
const userRes = await fetch(`${API_URL}api/users?filter[id]=${usersId.join(',')}&fields[users]=name,surname,username`, API_OPTIONS);
const userRes = await fetch(`${API_URL}users/list?filter[id]=${usersId.join(',')}`, API_OPTIONS);
const userJson = await userRes.json();
const users = userJson.data;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export default {
this.currentPage = json.meta.pagination.page;
this.activities = [...(json.data || [])];
const ids = this.activities.filter(item => item.meta.resource_id).map(item => item.meta.resource_id).filter((v, i, a) => a.indexOf(v) === i).map(i=>Number(i));
const objectResponse = await fetch(`${API_URL}api/objects?filter[id]=${ids.join(',')}&page_size=${pageSize}`, API_OPTIONS);
const objectResponse = await fetch(`${API_URL}history/objects?filter[id]=${ids.join(',')}&page_size=${pageSize}`, API_OPTIONS);
const objectJson = await objectResponse.json();
for (const item of this.activities) {
const object = objectJson.data.find(obj => obj.id === item.meta.resource_id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export default {
async getAccesses(pageSize = 20, page = 1) {
const filterDate = this.filterDate ? new Date(this.filterDate).toISOString() : '';

return fetch(`/api/users?page_size=${pageSize}&page=${page}&filter[last_login][gt]=${filterDate}&sort=-last_login`).then((r) => r.json());
return fetch(`/users/list?page_size=${pageSize}&page=${page}&filter[last_login][gt]=${filterDate}&sort=-last_login`).then((r) => r.json());
},
loadAccesses(pageSize = 20, page = 1) {
this.loading = true;
Expand Down
7 changes: 2 additions & 5 deletions resources/js/app/plugins/tinymce/placeholders.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@ const options = {

function fetchData(id) {
if (!cache[id]) {
let fetchType = fetch(`${baseUrl}api/objects/${id}`, options)
.then((response) => response.json())
.then((json) => json.data.type);
cache[id] = fetchType
.then((type) => fetch(`${baseUrl}api/${type}/${id}`, options))
cache[id] = fetch(`${baseUrl}resources/get/${id}`, options)
.then((response) => response.json());
}

Expand All @@ -40,6 +36,7 @@ function loadPreview(editor, node, id) {
if (!data) {
return;
}
console.log(data);

let domElements = editor.getBody().querySelectorAll(`[data-placeholder="${data.id}"]`);
[...domElements].forEach((dom) => {
Expand Down
26 changes: 16 additions & 10 deletions src/Controller/Admin/AdministrationBaseController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use App\Controller\AppController;
use BEdita\SDK\BEditaClientException;
use BEdita\WebTools\Utility\ApiTools;
use Cake\Event\EventInterface;
use Cake\Http\Exception\UnauthorizedException;
use Cake\Http\Response;
Expand Down Expand Up @@ -215,17 +216,22 @@ protected function loadData(): array
$query = $this->getRequest()->getQueryParams();
$resourceEndpoint = sprintf('%s/%s', $this->endpoint, $this->resourceType);
$endpoint = $this->resourceType === 'roles' ? 'roles' : $resourceEndpoint;
$resultResponse = [];
$pagination = ['page' => 0];
while (Hash::get($pagination, 'page') === 0 || Hash::get($pagination, 'page', -1) < Hash::get($pagination, 'page_count', -1)) {
$query['page'] = $pagination['page'] + 1;
$response = (array)$this->apiClient->get($endpoint, $query);
$pagination = (array)Hash::get($response, 'meta.pagination');
foreach ((array)Hash::get($response, 'data') as $data) {
$resultResponse['data'][] = $data;
}
$resultResponse = ['data' => []];
$pageCount = $page = 1;
$total = 0;
$limit = 500;
while ($limit > $total && $page <= $pageCount) {
$response = (array)$this->apiClient->get($endpoint, compact('page') + ['page_size' => 100]);
$response = ApiTools::cleanResponse($response);
$resultResponse['data'] = array_merge(
$resultResponse['data'],
(array)Hash::get($response, 'data'),
);
$resultResponse['meta'] = Hash::get($response, 'meta');
$resultResponse['links'] = Hash::get($response, 'links');
$pageCount = (int)Hash::get($response, 'meta.pagination.page_count');
$count = (int)Hash::get($response, 'meta.pagination.page_items');
$total += $count;
$page++;
}

return $resultResponse;
Expand Down
59 changes: 55 additions & 4 deletions src/Controller/ApiController.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?php
declare(strict_types=1);

/**
* BEdita, API-first content management framework
* Copyright 2020 ChannelWeb Srl, Chialab Srl
Expand All @@ -13,8 +15,11 @@
namespace App\Controller;

use BEdita\WebTools\Controller\ApiProxyTrait;
use Cake\Core\Configure;
use Cake\Event\EventInterface;
use Cake\Http\Exception\UnauthorizedException;
use Cake\Http\Response;
use Cake\Utility\Hash;

/**
* ApiController class.
Expand All @@ -28,16 +33,62 @@ class ApiController extends AppController
use ApiProxyTrait;

/**
* {@inheritDoc}
*
* @codeCoverageIgnore
* @inheritDoc
*/
public function beforeFilter(EventInterface $event): ?Response
{
parent::beforeFilter($event);

if (!$this->allowed()) {
throw new UnauthorizedException(__('You are not authorized to access this resource'));
}
$this->Security->setConfig('unlockedActions', ['post', 'patch', 'delete']);

return null;
}

/**
* Check if the request is allowed.
*
* @return bool
*/
protected function allowed(): bool
{
// block requests from browser address bar
$sameOrigin = (string)Hash::get((array)$this->request->getHeader('Sec-Fetch-Site'), 0) === 'same-origin';
$noReferer = empty((array)$this->request->getHeader('Referer'));
$isNavigate = in_array('navigate', (array)$this->request->getHeader('Sec-Fetch-Mode'));
if (!$sameOrigin || $noReferer || $isNavigate) {
return false;
}
/** @var \Authentication\Identity|null $user */
$user = $this->Authentication->getIdentity();
$roles = empty($user) ? [] : (array)$user->get('roles');
if (empty($roles)) {
return false;
}
if (in_array('admin', $roles)) {
return true;
}
$method = $this->request->getMethod();
$action = $this->request->getParam('pass')[0] ?? null;
$blockedMethods = (array)Configure::read('ApiProxy.blocked', [
'objects' => ['GET', 'POST', 'PATCH', 'DELETE'],
'users' => ['GET', 'POST', 'PATCH', 'DELETE'],
]);
$blocked = in_array($method, $blockedMethods[$action] ?? []);
$modules = $this->viewBuilder()->getVar('modules');
$modules = array_values($modules);
$modules = (array)Hash::combine($modules, '{n}.name', '{n}.hints.allow');
$modules = array_merge(
$modules,
[
'history' => ['GET'],
'model' => ['GET'],
],
);
$allowedMethods = (array)Hash::get($modules, $action, []);
$allowed = in_array($method, $allowedMethods);

return $allowed && !$blocked;
}
}
20 changes: 18 additions & 2 deletions src/Controller/Component/ModulesComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
use Cake\Controller\Component;
use Cake\Core\Configure;
use Cake\Event\Event;
use Cake\Event\EventInterface;
use Cake\Http\Client\Response;
use Cake\Http\Exception\BadRequestException;
use Cake\Http\Exception\InternalErrorException;
use Cake\I18n\I18n;
Expand Down Expand Up @@ -84,6 +86,20 @@ class ModulesComponent extends Component
],
];

/**
* @inheritDoc
*/
public function beforeFilter(EventInterface $event): ?Response
{
/** @var \Authentication\Identity|null $user */
$user = $this->Authentication->getIdentity();
if (!empty($user)) {
$this->getController()->set('modules', $this->getModules());
}

return null;
}

/**
* Read modules and project info from `/home' endpoint.
*
Expand All @@ -103,12 +119,12 @@ public function startup(): void
Cache::delete(sprintf('home_%d', $user->get('id')));
}

$modules = $this->getModules();
$project = $this->getProject();
$uploadable = (array)Hash::get($this->Schema->objectTypesFeatures(), 'uploadable');
$this->getController()->set(compact('modules', 'project', 'uploadable'));
$this->getController()->set(compact('project', 'uploadable'));

$currentModuleName = $this->getConfig('currentModuleName');
$modules = (array)$this->getController()->viewBuilder()->getVar('modules');
if (!empty($currentModuleName)) {
$currentModule = Hash::get($modules, $currentModuleName);
}
Expand Down
23 changes: 23 additions & 0 deletions src/Controller/HistoryController.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<?php
declare(strict_types=1);

namespace App\Controller;

use BEdita\WebTools\Utility\ApiTools;
use Cake\Http\Response;

/**
Expand All @@ -23,6 +26,26 @@ public function initialize(): void
$this->loadComponent('Schema');
}

/**
* Get objects list: id, title, uname only / no relationships, no links.
*
* @return void
*/
public function objects(): void
{
$this->viewBuilder()->setClassName('Json');
$this->getRequest()->allowMethod('get');
$query = array_merge(
$this->getRequest()->getQueryParams(),
['fields' => 'id,title,uname']
);
$response = ApiTools::cleanResponse((array)$this->apiClient->get('objects', $query));
$data = $response['data'];
$meta = $response['meta'];
$this->set(compact('data', 'meta'));
$this->setSerialize(['data', 'meta']);
}

/**
* Get history data by ID
*
Expand Down
45 changes: 45 additions & 0 deletions src/Controller/ModulesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use App\Utility\Message;
use App\Utility\PermissionsTrait;
use BEdita\SDK\BEditaClientException;
use BEdita\WebTools\Utility\ApiTools;
use Cake\Core\Configure;
use Cake\Event\Event;
use Cake\Event\EventInterface;
Expand Down Expand Up @@ -611,4 +612,48 @@ private function setupViewRelations(array $relations): void
$this->set('schemasByType', $schemasByType);
$this->set('filtersByType', $this->Properties->filtersByType($rightTypes));
}

/**
* Get list of users / no email, no relationships, no links, no schema, no included.
*
* @return void
*/
public function users(): void
{
$this->viewBuilder()->setClassName('Json');
$this->getRequest()->allowMethod('get');
$query = array_merge(
$this->getRequest()->getQueryParams(),
['fields' => 'id,title,username,name,surname']
);
$response = (array)$this->apiClient->get('users', $query);
$response = ApiTools::cleanResponse($response);
$data = (array)Hash::get($response, 'data');
$meta = (array)Hash::get($response, 'meta');
$this->set(compact('data', 'meta'));
$this->setSerialize(['data', 'meta']);
}

/**
* Get single resource, minimal data / no relationships, no links, no schema, no included.
*
* @param string $id The object ID
* @return void
*/
public function get(string $id): void
{
$this->viewBuilder()->setClassName('Json');
$this->getRequest()->allowMethod('get');
$response = (array)$this->apiClient->getObject($id, 'objects');
$query = array_merge(
$this->getRequest()->getQueryParams(),
['fields' => 'id,title,description,uname,status,media_url']
);
$response = (array)$this->apiClient->getObject($id, $response['data']['type'], $query);
$response = ApiTools::cleanResponse($response);
$data = (array)Hash::get($response, 'data');
$meta = (array)Hash::get($response, 'meta');
$this->set(compact('data', 'meta'));
$this->setSerialize(['data', 'meta']);
}
}
Loading
Loading