This commit is contained in:
yuri
2019-05-16 12:19:42 +03:00
parent fe2a4f91ee
commit 72bed367e4
37 changed files with 1902 additions and 8 deletions
+73
View File
@@ -0,0 +1,73 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Acl;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
class Webhook extends \Espo\Core\Acl\Base
{
public function checkIsOwner(EntityUser $user, Entity $entity)
{
return $user->id === $entity->get('userId') && $user->isApi();
}
public function checkEntityCreate(EntityUser $user, Entity $entity, $data)
{
if ($user->isAdmin()) return true;
if (!$data) return false;
if ($user->isApi() && $user->id === $entity->get('userId')) return true;
return false;
}
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
{
if ($user->isAdmin()) return true;
if (!$data) return false;
if ($user->isApi() && $user->id === $entity->get('userId')) return true;
return false;
}
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
{
if ($user->isAdmin()) return true;
if (!$data) return false;
if ($user->isApi() && $user->id === $entity->get('userId')) return true;
return false;
}
public function checkEntityEdit(EntityUser $user, Entity $entity, $data)
{
if ($user->isAdmin()) return true;
if (!$data) return false;
if ($user->isApi() && $user->id === $entity->get('userId')) return true;
return false;
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Controllers;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
class Webhook extends \Espo\Core\Controllers\Record
{
protected function checkControllerAccess()
{
if (!$this->getUser()->isAdmin() && !$this->getUser()->isApi()) {
throw new Forbidden();
}
}
public function actionCreate($params, $data, $request, $response = null)
{
$result = parent::actionCreate($params, $data, $request, $response);
if ($response) $response->setStatus(201);
return $result;
}
}
@@ -0,0 +1,43 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Loaders;
class WebhookManager extends Base
{
public function load()
{
return new \Espo\Core\Webhook\Manager(
$this->getContainer()->get('config'),
$this->getContainer()->get('fileManager'),
$this->getContainer()->get('entityManager'),
$this->getContainer()->get('fieldManagerUtil')
);
}
}
+233
View File
@@ -0,0 +1,233 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Webhook;
use Espo\ORM\Entity;
class Manager
{
protected $config;
protected $fileManager;
protected $entityManager;
protected $fieldManager;
private $cacheFile = 'data/cache/application/webhooks.php';
protected $skipAttributeList = ['isFollowed', 'modifiedAt', 'modifiedBy'];
private $data = null;
public function __construct(
\Espo\Core\Utils\Config $config,
\Espo\Core\Utils\File\Manager $fileManager,
\Espo\ORM\EntityManager $entityManager,
\Espo\Core\Utils\FieldManagerUtil $fieldManager
) {
$this->config = $config;
$this->fileManager = $fileManager;
$this->entityManager = $entityManager;
$this->fieldManager = $fieldManager;
$this->loadData();
}
private function loadData()
{
if ($this->config->get('useCache')) {
if ($this->fileManager->isFile($this->cacheFile)) {
if (file_exists($this->cacheFile)) {
$this->data = $this->fileManager->getPhpContents($this->cacheFile);
}
}
}
if (is_null($this->data)) {
$this->data = $this->buildData();
}
if ($this->config->get('useCache')) {
$this->storeDataToCache();
}
}
private function storeDataToCache()
{
$this->fileManager->putPhpContents($this->cacheFile, $this->data);
}
private function buildData()
{
$data = [];
$list = $this->entityManager->getRepository('Webhook')
->select(['event'])
->groupBy(['event'])
->where([
'isActive' => true,
'event!=' => null,
])
->find();
foreach ($list as $e) {
$data[$e->get('event')] = true;
}
return $data;
}
public function addEvent(string $event)
{
$this->data[$event] = true;
if ($this->config->get('useCache')) {
$this->storeDataToCache();
}
}
public function removeEvent(string $event)
{
$notExists = !$this->entityManager->getRepository('Webhook')->select(['id'])->where([
'event' => $event,
'isActive' => true,
])->findOne();
if ($notExists) {
unset($this->data[$event]);
if ($this->config->get('useCache')) {
$this->storeDataToCache();
}
}
}
protected function eventExists(string $event) : bool
{
return isset($this->data[$event]);
}
protected function logDebugEvent(string $event, Entity $entity)
{
$GLOBALS['log']->debug("Webhook: {$event} on record {$entity->id}.");
}
public function processCreate(Entity $entity)
{
$event = $entity->getEntityType() . '.create';
if (!$this->eventExists($event)) return;
$this->entityManager->createEntity('WebhookEventQueueItem', [
'event' => $event,
'targetType' => $entity->getEntityType(),
'targetId' => $entity->id,
'data' => $entity->getValueMap(),
]);
$this->logDebugEvent($event, $entity);
}
public function processDelete(Entity $entity)
{
$event = $entity->getEntityType() . '.delete';
if (!$this->eventExists($event)) return;
$this->entityManager->createEntity('WebhookEventQueueItem', [
'event' => $event,
'targetType' => $entity->getEntityType(),
'targetId' => $entity->id,
'data' => (object) [
'id' => $entity->id,
],
]);
$this->logDebugEvent($event, $entity);
}
public function processUpdate(Entity $entity)
{
$event = $entity->getEntityType() . '.update';
$data = (object) [];
foreach ($entity->getAttributeList() as $attribute) {
if (in_array($attribute, $this->skipAttributeList)) continue;
if ($entity->isAttributeChanged($attribute)) {
$data->$attribute = $entity->get($attribute);
}
}
if (!count(get_object_vars($data))) return;
$data->id = $entity->id;
if ($this->eventExists($event)) {
$this->entityManager->createEntity('WebhookEventQueueItem', [
'event' => $event,
'targetType' => $entity->getEntityType(),
'targetId' => $entity->id,
'data' => $data,
]);
$this->logDebugEvent($event, $entity);
}
foreach ($this->fieldManager->getEntityTypeFieldList($entity->getEntityType()) as $field) {
$itemEvent = $entity->getEntityType() . '.fieldUpdate.' . $field;
if (!$this->eventExists($itemEvent)) continue;
$attributeList = $this->fieldManager->getActualAttributeList($entity->getEntityType(), $field);
$isChanged = false;
foreach ($attributeList as $attribute) {
if (in_array($attribute, $this->skipAttributeList)) continue;
if (property_exists($data, $attribute)) {
$isChanged = true;
break;
}
}
if ($isChanged) {
$itemData = (object) [];
$itemData->id = $entity->id;
$attributeList = $this->fieldManager->getAttributeList($entity->getEntityType(), $field);
foreach ($attributeList as $attribute) {
if (in_array($attribute, $this->skipAttributeList)) continue;
$itemData->$attribute = $entity->get($attribute);
}
$this->entityManager->createEntity('WebhookEventQueueItem', [
'event' => $itemEvent,
'targetType' => $entity->getEntityType(),
'targetId' => $entity->id,
'data' => $itemData,
]);
$this->logDebugEvent($itemEvent, $entity);
}
}
}
}
+291
View File
@@ -0,0 +1,291 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Webhook;
use Espo\ORM\Entity;
use Espo\Core\Utils\DateTime;
use Espo\Entities\WebhookQueueItem;
use Espo\Entities\Webhook;
class Queue
{
const EVENT_PORTION_SIZE = 20;
const PORTION_SIZE = 20;
const BATCH_SIZE = 50;
const MAX_ATTEMPT_NUMBER = 4;
const FAIL_ATTEMPT_PERIOD = '10 minutes';
protected $sender;
protected $config;
protected $entityManager;
protected $aclManager;
public function __construct(
Sender $sender,
\Espo\Core\Utils\Config $config,
\Espo\ORM\EntityManager $entityManager,
\Espo\Core\AclManager $aclManager
) {
$this->sender = $sender;
$this->config = $config;
$this->entityManager = $entityManager;
$this->aclManager = $aclManager;
}
public function process()
{
$this->processEvents();
$this->processSending();
}
protected function processEvents()
{
$portionSize = $this->config->get('webhookQueueEventPortionSize', self::EVENT_PORTION_SIZE);
$itemList = $this->entityManager->getRepository('WebhookEventQueueItem')->where([
'isProcessed' => false,
])->order('number')->limit(0, $portionSize)->find();
foreach ($itemList as $item) {
$this->createQueueFromEvent($item);
$item->set([
'isProcessed' => true,
]);
$this->entityManager->saveEntity($item);
}
}
protected function createQueueFromEvent(\Espo\Entities\WebhookEventQueueItem $item)
{
$webhookList = $this->entityManager->getRepository('Webhook')->where([
'event' => $item->get('event'),
'isActive' => true,
])->order('createdAt')->find();
foreach ($webhookList as $webhook) {
$this->entityManager->createEntity('WebhookQueueItem', [
'webhookId' => $webhook->id,
'event' => $item->get('event'),
'targetId' => $item->get('targetId'),
'targetType' => $item->get('targetType'),
'status' => 'Pending',
'data' => $item->get('data'),
'attempts' => 0,
]);
}
}
protected function processSending()
{
$portionSize = $this->config->get('webhookQueuePortionSize', self::PORTION_SIZE);
$batchSize = $this->config->get('webhookBatchSize', self::BATCH_SIZE);
$groupedItemList = $this->entityManager->getRepository('WebhookQueueItem')->where([
'status' => 'Pending',
'OR' => [
['processAt' => null],
['processAt<=' => DateTime::getSystemNowString()],
],
])->order('number')->limit(0, $portionSize)->groupBy(['webhookId'])->find();
foreach ($groupedItemList as $group) {
$webhookId = $group->get('webhookId');
$itemList = $this->entityManager->getRepository('WebhookQueueItem')->where([
'webhookId' => $webhookId,
'status' => 'Pending',
'OR' => [
['processAt' => null],
['processAt<=' => DateTime::getSystemNowString()],
],
])->order('number')->limit(0, $batchSize)->find();
$webhook = $this->entityManager->getEntity('Webhook', $webhookId);
if (!$webhook || !$webhook->get('isActive')) {
foreach ($itemList as $item) {
$this->deleteQueueItem($item);
}
}
$forbiddenAttributeList = [];
$user = null;
if ($webhook->get('userId')) {
$user = $this->entityManager->getEntity('User', $webhook->get('userId'));
if (!$user) {
foreach ($itemList as $item) {
$this->deleteQueueItem($item);
}
continue;
} else {
$forbiddenAttributeList = $this->aclManager->getScopeForbiddenAttributeList($user, $webhook->get('entityType'));
}
}
$actualItemList = [];
$dataList = [];
foreach ($itemList as $item) {
$targetType = $item->get('targetType');
$target = null;
if ($this->entityManager->hasRepository($targetType)) {
$target = $this->entityManager->getRepository($targetType)->where([
'id' => $item->get('targetId')
])->findOne(['withDeleted' => true]);
}
if (!$target) {
$this->deleteQueueItem($item);
continue;
}
if ($user) {
if (!$this->aclManager->check($user, $target)) {
$this->deleteQueueItem($item);
continue;
}
}
$data = $item->get('data') ?? (object) [];
$data = clone $data;
foreach ($forbiddenAttributeList as $attribute) {
unset($data->$attribute);
}
$actualItemList[] = $item;
$dataList[] = $data;
}
if (empty($dataList)) continue;
$this->send($webhook, $dataList, $actualItemList);
}
}
protected function send(Webhook $webhook, array $dataList, array $itemList)
{
try {
$code = $this->sender->send($webhook, $dataList);
} catch (\Exception $e) {
$this->failQueueItemList($itemList, true);
$GLOBALS['log']->error("Webhook Queue: Webhook {$webhook->id} sending failed. Error: " . $e->getMessage());
return;
}
if ($code >= 200 && $code < 400) {
$this->succeedQueueItemList($itemList);
} else if ($code === 410) {
$this->dropWebhook($webhook);
} else if (in_array($code, [0, 401, 403, 404, 405, 408, 500, 503])) {
$this->failQueueItemList($itemList);
} else if ($code >= 400 && $code < 500) {
$this->failQueueItemList($itemList, true);
} else {
$this->failQueueItemList($itemList, true);
}
$this->logSending($webhook, $code);
}
protected function logSending(Webhook $webhook, int $code)
{
$GLOBALS['log']->debug("Webhook Queue: Webhook {$webhook->id} sent, response code: {$code}.");
}
protected function failQueueItemList(array $itemList, bool $force = false)
{
foreach ($itemList as $item) {
$this->failQueueItem($item, $force);
}
}
protected function succeedQueueItemList(array $itemList)
{
foreach ($itemList as $item) {
$this->succeedQueueItem($item);
}
}
protected function deleteQueueItem(WebhookQueueItem $item)
{
$this->entityManager->getRepository('WebhookQueueItem')->deleteFromDb($item->id);
}
protected function dropWebhook(Webhook $webhook)
{
$itemList = $this->entityManager->getRepository('WebhookQueueItem')->where([
'status' => 'Pending',
'webhookId' => $webhook->id,
])->order('number')->find();
foreach ($itemList as $item) {
$this->deleteQueueItem($item);
}
$this->entityManager->removeEntity($webhook);
}
protected function succeedQueueItem(WebhookQueueItem $item)
{
$item->set([
'attempts' => $item->get('attempts') + 1,
'status' => 'Success',
'processedAt' => DateTime::getSystemNowString(),
]);
$this->entityManager->saveEntity($item);
}
protected function failQueueItem(WebhookQueueItem $item, bool $force = false)
{
$attempts = $item->get('attempts') + 1;
$maxAttemptsNumber = $this->config->get('webhookMaxAttemptNumber', self::MAX_ATTEMPT_NUMBER);
$period = $this->config->get('webhookFailAttemptPeriod', self::FAIL_ATTEMPT_PERIOD);
if ($force) $maxAttemptsNumber = 0;
$dt = new \DateTime();
$dt->modify($period);
$item->set([
'attempts' => $attempts,
'processAt' => $dt->format(DateTime::$systemDateTimeFormat),
]);
if ($attempts >= $maxAttemptsNumber) {
$item->set('status', 'Failed');
$item->set('processAt', null);
}
$this->entityManager->saveEntity($item);
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Webhook;
use Espo\Entities\Webhook;
class Sender
{
const CONNECT_TIMEOUT = 5;
const TIMEOUT = 10;
protected $config;
public function __construct(\Espo\Core\Utils\Config $config)
{
$this->config = $config;
}
public function send(Webhook $webhook, array $dataList) : int
{
$payload = json_encode($dataList);
$signature = null;
$secretKey = $webhook->get('secretKey');
if ($secretKey) {
$signature = $this->buildSignature($webhook, $payload, $secretKey);
}
$connectTimeout = $this->config->get('webhookConnectTimeout', self::CONNECT_TIMEOUT);
$timeout = $this->config->get('webhookTimeout', self::TIMEOUT);
$headerList = [];
$headerList[] = 'Content-Type: application/json';
$headerList[] = 'Content-Length: ' . strlen($payload);
if ($signature) {
$headerList[] = 'X-Signature: ' . $signature;
}
$handler = curl_init($webhook->get('url'));
curl_setopt($handler, \CURLOPT_RETURNTRANSFER, true);
curl_setopt($handler, \CURLOPT_FOLLOWLOCATION, true);
curl_setopt($handler, \CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($handler, \CURLOPT_HEADER, true);
curl_setopt($handler, \CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($handler, \CURLOPT_CONNECTTIMEOUT, $connectTimeout);
curl_setopt($handler, \CURLOPT_TIMEOUT, $timeout);
curl_setopt($handler, \CURLOPT_HTTPHEADER, $headerList);
curl_setopt($handler, \CURLOPT_POSTFIELDS, $payload);
curl_exec($handler);
$code = curl_getinfo($handler, \CURLINFO_HTTP_CODE);
if (!is_numeric($code)) $code = 0;
if (!is_int($code)) $code = intval($code);
if ($errorNumber = curl_errno($handler)) {
if (in_array($errorNumber, [\CURLE_OPERATION_TIMEDOUT, \CURLE_OPERATION_TIMEOUTED])) {
$code = 408;
}
}
curl_close($handler);
return $code;
}
protected function buildSignature(Webhook $webhook, string $payload, string $secretKey)
{
return base64_encode($webhook->id . ':' . hash_hmac('sha256', $payload, $secretKey, true));
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Entities;
class Webhook extends \Espo\Core\ORM\Entity
{
}
@@ -0,0 +1,35 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Entities;
class WebhookEventQueueItem extends \Espo\Core\ORM\Entity
{
}
@@ -0,0 +1,35 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Entities;
class WebhookQueueItem extends \Espo\Core\ORM\Entity
{
}
+74
View File
@@ -0,0 +1,74 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Hooks\Common;
use Espo\ORM\Entity;
use Espo\Core\Utils\Util;
class Webhook extends \Espo\Core\Hooks\Base
{
public static $order = 101;
protected function init()
{
$this->addDependency('metadata');
$this->addDependency('container');
}
protected function getMetadata()
{
return $this->getInjection('metadata');
}
protected function getWebhookManager()
{
return $this->getInjection('container')->get('webhookManager');
}
public function afterSave(Entity $entity, array $options = [])
{
if (!empty($options['silent'])) return;
if (!$this->getMetadata()->get(['scopes', $entity->getEntityType(), 'object'])) return;
if ($entity->isNew()) {
$this->getWebhookManager()->processCreate($entity);
} else {
$this->getWebhookManager()->processUpdate($entity);
}
}
public function afterRemove(Entity $entity, array $options = [])
{
if (!empty($options['silent'])) return;
if (!$this->getMetadata()->get(['scopes', $entity->getEntityType(), 'object'])) return;
$this->getWebhookManager()->processDelete($entity);
}
}
+27
View File
@@ -53,6 +53,8 @@ class Cleanup extends \Espo\Core\Jobs\Base
protected $cleanupDeletedRecordsPeriod = '3 months';
protected $cleanupWebhookQueuePeriod = '10 days';
public function run()
{
$this->cleanupJobs();
@@ -66,6 +68,7 @@ class Cleanup extends \Espo\Core\Jobs\Base
$this->cleanupUpgradeBackups();
$this->cleanupUniqueIds();
$this->cleanupDeletedRecords();
$this->cleanupWebhookQueue();
}
protected function cleanupJobs()
@@ -459,4 +462,28 @@ class Cleanup extends \Espo\Core\Jobs\Base
}
}
}
protected function cleanupWebhookQueue()
{
$pdo = $this->getEntityManager()->getPDO();
$period = '-' . $this->getConfig()->get('cleanupWebhookQueuePeriod', $this->cleanupWebhookQueuePeriod);
$datetime = new \DateTime();
$datetime->modify($period);
$from = $datetime->format('Y-m-d H:i:s');
$query = "
DELETE FROM `webhook_queue_item`
WHERE
DATE(created_at) < ".$pdo->quote($from)." AND
(status <> 'Pending' OR deleted = 1)
";
$pdo->query($query);
$query = "
DELETE FROM `webhook_event_queue_item`
WHERE DATE(created_at) < ".$pdo->quote($from)." AND (is_processed = 1 OR deleted = 1)
";
$pdo->query($query);
}
}
@@ -0,0 +1,51 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Jobs;
use \Espo\Core\Exceptions;
class ProcessWebhookQueue extends \Espo\Core\Jobs\Base
{
public function run()
{
$sender = new \Espo\Core\Webhook\Sender(
$this->getContainer()->get('config')
);
$webhookQueue = new \Espo\Core\Webhook\Queue(
$sender,
$this->getContainer()->get('config'),
$this->getContainer()->get('entityManager'),
$this->getContainer()->get('aclManager')
);
$webhookQueue->process();
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Repositories;
use Espo\ORM\Entity;
class Webhook extends \Espo\Core\ORM\Repositories\RDB
{
protected $hooksDisabled = true;
protected $processFieldsAfterSaveDisabled = true;
protected $processFieldsBeforeSaveDisabled = true;
protected $processFieldsAfterRemoveDisabled = true;
protected function beforeSave(Entity $entity, array $options = [])
{
if ($entity->isNew()) {
$this->fillSecretKey($entity);
}
parent::beforeSave($entity);
$this->processSettingAdditionalFields($entity);
}
protected function fillSecretKey(Entity $entity)
{
$secretKey = \Espo\Core\Utils\Util::generateKey();
$entity->set('secretKey', $secretKey);
}
protected function processSettingAdditionalFields(Entity $entity)
{
$event = $entity->get('event');
if (!$event) return;
$arr = explode('.', $event);
if (count($arr) !== 2 && count($arr) !== 3) return;
$arr = explode('.', $event);
$entityType = $arr[0];
$type = $arr[1];
$entity->set('entityType', $entityType);
$entity->set('type', $type);
$field = null;
if (!$entityType) return;
if ($type === 'fieldUpdate') {
if (count($arr) == 3) {
$field = $arr[2];
}
$entity->set('field', $field);
} else {
$entity->set('field', null);
}
}
}
@@ -38,6 +38,7 @@
"Currency": "Currency",
"Integrations": "Integrations",
"Extensions": "Extensions",
"Webhooks": "Webhooks",
"Upload": "Upload",
"Installing...": "Installing...",
"Upgrading...": "Upgrading...",
@@ -243,6 +244,7 @@
"attachments": "All file attachments stored in the system.",
"systemRequirements": "System Requirements for EspoCRM.",
"apiUsers": "Separate users for integration purposes.",
"webhooks": "Manage webhooks.",
"pdfTemplates": "Templates for printing to PDF."
},
"options": {
@@ -48,7 +48,8 @@
"AuthFailLogRecord": "Auth Fail Log Record",
"LeadCapture": "Lead Capture Entry Point",
"LeadCaptureLogRecord": "Lead Capture Log Record",
"ArrayValue": "Array Value"
"ArrayValue": "Array Value",
"Webhook": "Webhook"
},
"scopeNamesPlural": {
"Email": "Emails",
@@ -86,7 +87,8 @@
"AuthFailLogRecord": "Auth Fail Log",
"LeadCapture": "Lead Capture",
"LeadCaptureLogRecord": "Lead Capture Log",
"ArrayValue": "Array Values"
"ArrayValue": "Array Values",
"Webhook": "Webhooks"
},
"labels": {
"Misc": "Misc",
@@ -19,7 +19,8 @@
"SendEmailReminders": "Send Email Reminders",
"AuthTokenControl": "Auth Token Control",
"SendEmailNotifications": "Send Email Notifications",
"CheckNewVersion": "Check for New Version"
"CheckNewVersion": "Check for New Version",
"ProcessWebhookQueue": "Process Webhook Queue"
},
"cronSetup": {
"linux": "Note: Add this line to the crontab file to run Espo Scheduled Jobs:",
@@ -112,6 +112,7 @@
},
"presetFilters": {
"active": "Active",
"activePortal": "Portal Active"
"activePortal": "Portal Active",
"activeApi": "API Active"
}
}
@@ -0,0 +1,17 @@
{
"labels": {
"Create Webhook": "Create Webhook"
},
"fields": {
"event": "Event",
"url": "URL",
"isActive": "Is Active",
"user": "API User",
"entityType": "Entity Type",
"field": "Field",
"secretKey": "Secret Key"
},
"links": {
"user": "User"
}
}
@@ -0,0 +1,18 @@
[
{
"rows": [
[
{"name": "event"},
{"name": "isActive"}
],
[
{"name": "url"},
{"name": "user"}
],
[
{"name": "secretKey"},
false
]
]
}
]
@@ -0,0 +1,19 @@
[
{
"rows": [
[
{"name": "event"},
{"name": "isActive"}
],
[
{"name": "url", "fullWidth": true}
],
[
{"name": "user", "fullWidth": true}
],
[
{"name": "secretKey", "fullWidth": true}
]
]
}
]
@@ -0,0 +1,5 @@
[
"type",
"entityType",
"user"
]
@@ -0,0 +1,18 @@
[
{
"name": "event",
"width": 22,
"link": true
},
{
"name": "isActive",
"width": 14
},
{
"name": "user",
"width": 22
},
{
"name": "url"
}
]
@@ -87,7 +87,8 @@
"read": "all",
"edit": "no"
},
"Import": false
"Import": false,
"Webhook": false
},
"fieldLevel": {
},
@@ -103,7 +104,8 @@
"read": "own",
"edit": "no"
},
"Import": false
"Import": false,
"Webhook": false
},
"fieldLevel": {
},
@@ -206,6 +206,13 @@
"url": "#Template",
"label": "PDF Templates",
"description": "pdfTemplates"
},
{
"url": "#Webhook",
"label": "Webhooks",
"description": "webhooks"
},
{
"url": "#Attachment",
@@ -0,0 +1,27 @@
{
"controller": "controllers/record",
"dynamicLogic": {
"fields": {
"event": {
"readOnly": {
"conditionGroup": [
{
"type": "isNotEmpty",
"attribute": "id"
}
]
}
},
"secretKey": {
"visible": {
"conditionGroup": [
{
"type": "isNotEmpty",
"attribute": "id"
}
]
}
}
}
}
}
@@ -73,7 +73,8 @@
"SendEmailReminders": "*/2 * * * *",
"Cleanup": "1 1 * * 0",
"AuthTokenControl": "*/6 * * * *",
"SendEmailNotifications": "*/2 * * * *"
"SendEmailNotifications": "*/2 * * * *",
"ProcessWebhookQueue": "*/5 * * * *"
},
"jobs": {
"Dummy": {
@@ -0,0 +1,95 @@
{
"fields": {
"event": {
"type": "varchar",
"maxLength": 100,
"required": true
},
"url": {
"type": "varchar",
"maxLength": 512,
"required": true
},
"isActive": {
"type": "bool",
"default": true
},
"user": {
"type": "link",
"view": "views/webhook/fields/user"
},
"entityType": {
"type": "varchar",
"readOnly": true,
"view": "views/fields/entity-type"
},
"type": {
"type": "enum",
"options": [
"create",
"update",
"fieldUpdate",
"delete"
],
"readOnly": true
},
"field": {
"type": "varchar",
"readOnly": true
},
"secretKey": {
"type": "varchar",
"maxLength": 100,
"readOnly": true,
"layoutMassUpdateDisabled": true,
"layoutFiltersDisabled": true,
"layoutListDisabled": true
},
"createdAt": {
"type": "datetime",
"readOnly": true
},
"modifiedAt": {
"type": "datetime",
"readOnly": true
},
"createdBy": {
"type": "link",
"readOnly": true
},
"modifiedBy": {
"type": "link",
"readOnly": true
}
},
"links": {
"user": {
"type": "belongsTo",
"entity": "User"
},
"createdBy": {
"type": "belongsTo",
"entity": "User"
},
"modifiedBy": {
"type": "belongsTo",
"entity": "User"
}
},
"collection": {
"orderBy": "createdAt",
"order": "desc",
"textFilterFields": ["event"]
},
"indexes": {
"event": {
"columns": ["event"]
},
"entityTypeType": {
"columns": ["entityType", "type"]
},
"entityTypeField": {
"columns": ["entityType", "field"]
}
}
}
@@ -0,0 +1,32 @@
{
"fields": {
"number": {
"type": "autoincrement",
"dbType": "bigint"
},
"event": {
"type": "varchar",
"maxLength": 100,
"required": true
},
"target": {
"type": "linkParent"
},
"data": {
"type": "jsonObject"
},
"createdAt": {
"type": "datetime",
"readOnly": true
},
"isProcessed": {
"type": "bool"
}
},
"links": {
},
"collection": {
"orderBy": "number",
"order": "desc"
}
}
@@ -0,0 +1,52 @@
{
"fields": {
"number": {
"type": "autoincrement",
"dbType": "bigint"
},
"event": {
"type": "varchar",
"maxLength": 100,
"required": true
},
"webhook": {
"type": "link"
},
"target": {
"type": "linkParent"
},
"data": {
"type": "jsonObject"
},
"createdAt": {
"type": "datetime"
},
"status": {
"type": "enum",
"options": ["Pending", "Success", "Failed"],
"default": "Pending",
"maxLength": 15
},
"processedAt": {
"type": "datetime"
},
"attempts": {
"type": "int",
"default": 0
},
"processAt": {
"type": "datetime"
}
},
"links": {
"webhook": {
"type": "belongsTo",
"entity": "Webhook",
"foreignName": "id"
}
},
"collection": {
"orderBy": "number",
"order": "desc"
}
}
@@ -0,0 +1,4 @@
{
"entity": true,
"acl": "boolean"
}
@@ -0,0 +1,3 @@
{
"entity": true
}
@@ -0,0 +1,3 @@
{
"entity": true
}
@@ -0,0 +1,48 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\SelectManagers;
class Webhook extends \Espo\Core\SelectManagers\Base
{
protected function access(&$result)
{
if (!$this->getUser()->isAdmin() && !$this->getUser()->isApi()) {
$result['whereClause'][] = [
'id' => null
];
}
if ($this->getUser()->isApi()) {
$result['whereClause'][] = [
'userId' => $this->getUser()->id
];
}
}
}
+194
View File
@@ -0,0 +1,194 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Services;
use \Espo\ORM\Entity;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\BadRequest;
class Webhook extends Record
{
const WEBHOOK_MAX_COUNT_PER_USER = 50;
protected $eventTypeList = [
'create',
'update',
'delete',
'fieldUpdate',
];
protected $onlyAdminAttributeList = ['userId', 'userName'];
protected $readOnlyAttributeList = ['secretKey'];
protected function init()
{
parent::init();
$this->addDependencyList([
'webhookManager',
]);
}
public function populateDefaults(Entity $entity, $data)
{
parent::populateDefaults($entity, $data);
if ($this->getUser()->isApi()) {
$entity->set('userId', $this->getUser()->id);
}
}
protected function filtetInput($data)
{
parent::filtetInput($data);
unset($data->entityType);
unset($data->field);
unset($data->type);
}
protected function filterUpdateInput($data)
{
if (!$this->getUser()->isAdmin()) {
unset($data->event);
}
}
protected function beforeCreateEntity(Entity $entity, $data)
{
$this->checkEntityUserIsApi($entity);
$this->processEntityEventData($entity);
if (!$this->getUser()->isAdmin()) {
$this->checkMaxCount();
}
}
protected function checkMaxCount()
{
$maxCount = $this->getConfig()->get('webhookMaxCountPerUser', self::WEBHOOK_MAX_COUNT_PER_USER);
$count = $this->getEntityManager()->getRepository('Webhook')->where([
'userId' => $this->getUser()->id,
])->count();
if ($maxCount && $count >= $maxCount) {
throw new Forbidden("Webhook number per user exceeded the limit.");
}
}
protected function beforeUpdateEntity(Entity $entity, $data)
{
$this->checkEntityUserIsApi($entity);
$this->processEntityEventData($entity);
}
protected function checkEntityUserIsApi(Entity $entity)
{
$userId = $entity->get('userId');
if (!$userId) return;
$user = $this->getEntityManager()->getEntity('User', $userId);
if (!$user || !$user->isApi()) throw new Forbidden("User must be an API User.");
}
protected function processEntityEventData(Entity $entity)
{
$event = $entity->get('event');
if (!$event) throw new Forbidden("Event is empty.");
if (!$entity->isNew()) {
if ($entity->isAttributeChanged('event')) {
throw new Forbidden("Event can't be changed.");
}
}
$arr = explode('.', $event);
if (count($arr) !== 2 && count($arr) !== 3) throw new Forbidden("Not supported event.");
$arr = explode('.', $event);
$entityType = $arr[0];
$type = $arr[1];
$entity->set('entityType', $entityType);
$entity->set('type', $type);
$field = null;
if (!$entityType) throw new Forbidden("Entity Type is empty.");
if (!$this->getMetadata()->get(['scopes', $entityType, 'object'])) throw new Forbidden("Entity type is not available for Webhooks.");
if (!$this->getEntityManager()->hasRepository($entityType)) throw new Forbidden("Not existing Entity Type.");
if (!$this->getAcl()->checkScope($entityType, 'read')) throw new Forbidden("Entity type is forbidden.");
if (!in_array($type, $this->eventTypeList)) throw new Forbidden("Not supported event.");
if ($type === 'fieldUpdate') {
if (count($arr) == 3) {
$field = $arr[2];
}
$entity->set('field', $field);
if (!$field) throw new Forbidden("Field is empty.");
$forbiddenFieldList = $this->getAcl()->getScopeForbiddenFieldList($entityType);
if (in_array($field, $forbiddenFieldList)) throw new Forbidden("Field is forbidden.");
if (!$this->getMetadata()->get(['entityDefs', $entityType, 'fields', $field])) throw new Forbidden("Field does not exist.");
} else {
$entity->set('field', null);
}
}
protected function afterCreateEntity(Entity $entity, $data)
{
if ($entity->get('isActive')) {
$this->getInjection('webhookManager')->addEvent($entity->get('event'));
}
}
protected function afterDeleteEntity(Entity $entity)
{
if ($entity->get('isActive')) {
$this->getInjection('webhookManager')->removeEvent($entity->get('event'));
}
}
protected function afterUpdateEntity(Entity $entity, $data)
{
if (isset($data->isActive)) {
if ($entity->get('isActive')) {
$this->getInjection('webhookManager')->addEvent($entity->get('event'));
} else {
$this->getInjection('webhookManager')->removeEvent($entity->get('event'));
}
}
}
}
+36
View File
@@ -0,0 +1,36 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/webhook/fields/user', 'views/fields/link', function (Dep) {
return Dep.extend({
selectPrimaryFilterName: 'activeApi',
});
});
+7 -1
View File
@@ -84,6 +84,12 @@ return array(
'job' => 'ControlKnowledgeBaseArticleStatus',
'status' => 'Active',
'scheduling' => '10 1 * * *'
)
),
[
'name' => 'Process Webhook Queue',
'job' => 'ProcessWebhookQueue',
'status' => 'Active',
'scheduling' => '*/5 * * * *',
],
)
);
+167
View File
@@ -0,0 +1,167 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM 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, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\integration\Espo\Webhook;
class AclTest extends \tests\integration\Core\BaseTestCase
{
/*protected $dataFile = 'User/Login.php';
protected $userName = 'admin';
protected $password = '1';*/
public function testRegularUserNoAccess()
{
$this->createUser(
[
'userName' => 'test',
'password' => '1',
],
[
'data' => [
'Webhook' => true,
'Account' => ['create'=> true, 'read' => 'own'],
],
]
);
$this->auth('test', '1');
$app = $this->createApplication();
$controllerManager = $app->getContainer()->get('controllerManager');
$params = [];
$data = '{"event":"Account.create"}';
$this->expectException(\Espo\Core\Exceptions\Forbidden::class);
$request = $this->createRequest('POST', $params, ['CONTENT_TYPE' => 'application/json']);
$result = $controllerManager->process('Webhook', 'create', $params, $data, $request);
}
public function testApiUserNoAccess1()
{
$this->createUser(
[
'userName' => 'api',
'type' => 'api',
'authMethod' => 'ApiKey',
'apiKey' => 'test-key',
],
[
'data' => [
'Webhook' => false,
],
]
);
$this->auth('test-key', null, null, 'ApiKey');
$app = $this->createApplication();
$controllerManager = $app->getContainer()->get('controllerManager');
$data = '{"event":"Account.create", "url": "https://test"}';
$params = [];
$this->expectException(\Espo\Core\Exceptions\Forbidden::class);
$request = $this->createRequest('POST', $params, ['CONTENT_TYPE' => 'application/json']);
$result = $controllerManager->process('Webhook', 'create', $params, $data, $request);
}
public function testApiUserNoAccess2()
{
$this->createUser(
[
'userName' => 'api',
'type' => 'api',
'authMethod' => 'ApiKey',
'apiKey' => 'test-key',
],
[
'data' => [
'Webhook' => false,
'Account' => false,
],
]
);
$this->auth('test-key', null, null, 'ApiKey');
$app = $this->createApplication();
$controllerManager = $app->getContainer()->get('controllerManager');
$data = '{"event":"Account.create", "url": "https://test"}';
$params = [];
$this->expectException(\Espo\Core\Exceptions\Forbidden::class);
$request = $this->createRequest('POST', $params, ['CONTENT_TYPE' => 'application/json']);
$result = $controllerManager->process('Webhook', 'create', $params, $data, $request);
}
public function testApiUserHasAccess1()
{
$this->createUser(
[
'userName' => 'api',
'type' => 'api',
'authMethod' => 'ApiKey',
'apiKey' => 'test-key',
],
[
'data' => [
'Webhook' => true,
'Account' => ['create'=> true, 'read' => 'own'],
],
]
);
$this->auth('test-key', null, null, 'ApiKey');
$app = $this->createApplication();
$controllerManager = $app->getContainer()->get('controllerManager');
$data = '{"event":"Account.create", "url": "https://test"}';
$params = [];
$request = $this->createRequest('POST', $params, ['CONTENT_TYPE' => 'application/json']);
$result = $controllerManager->process('Webhook', 'create', $params, $data, $request);
$this->assertTrue(!empty($result));
}
}