audit log
This commit is contained in:
@@ -115,6 +115,30 @@ class Stream
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function getActionListUpdates(Request $request): stdClass
|
||||
{
|
||||
$id = $request->getRouteParam('id');
|
||||
$scope = $request->getRouteParam('scope');
|
||||
|
||||
if ($scope === null || $id === null) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$searchParams = $this->fetchSearchParams($request);
|
||||
|
||||
$result = $this->service->findUpdates($scope, $id, $searchParams);
|
||||
|
||||
return (object) [
|
||||
'total' => $result->getTotal(),
|
||||
'list' => $result->getValueMapList(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
@@ -146,6 +170,20 @@ class Stream
|
||||
$searchParams = $searchParams->withBoolFilterAdded('skipOwn');
|
||||
}
|
||||
|
||||
$beforeNumber = $request->getQueryParam('beforeNumber');
|
||||
|
||||
if ($beforeNumber) {
|
||||
$searchParams = $searchParams
|
||||
->withWhereAdded(
|
||||
WhereItem
|
||||
::createBuilder()
|
||||
->setAttribute('number')
|
||||
->setType(WhereItem\Type::LESS_THAN)
|
||||
->setValue($beforeNumber)
|
||||
->build()
|
||||
);
|
||||
}
|
||||
|
||||
return $searchParams;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +285,9 @@
|
||||
"Show Navigation Panel": "Show Navigation Panel",
|
||||
"Hide Navigation Panel": "Hide Navigation Panel",
|
||||
"Copy to Clipboard": "Copy to Clipboard",
|
||||
"Copied to clipboard": "Copied to clipboard"
|
||||
"Copied to clipboard": "Copied to clipboard",
|
||||
"Audit Log": "Audit Log",
|
||||
"View Audit Log": "View Audit Log"
|
||||
},
|
||||
"messages": {
|
||||
"pleaseWait": "Please wait...",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"massUpdatePermission": "Mass Update Permission",
|
||||
"followerManagementPermission": "Follower Management Permission",
|
||||
"dataPrivacyPermission": "Data Privacy Permission",
|
||||
"auditPermission": "Audit Permission",
|
||||
"data": "Data",
|
||||
"fieldData": "Field Data"
|
||||
},
|
||||
@@ -27,7 +28,8 @@
|
||||
"exportPermission": "Allows to export records.",
|
||||
"massUpdatePermission": "The ability to perform mass update of records.",
|
||||
"followerManagementPermission": "Allows to manage followers of specific records.",
|
||||
"dataPrivacyPermission": "Allows to view and erase personal data."
|
||||
"dataPrivacyPermission": "Allows to view and erase personal data.",
|
||||
"auditPermission": "Allows to view the audit log."
|
||||
},
|
||||
"labels": {
|
||||
"Access": "Access",
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
{"name": "massUpdatePermission"},
|
||||
{"name": "followerManagementPermission"},
|
||||
{"name": "messagePermission"}
|
||||
],
|
||||
[
|
||||
{"name": "auditPermission"},
|
||||
false,
|
||||
false
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -161,7 +161,8 @@
|
||||
"exportPermission",
|
||||
"massUpdatePermission",
|
||||
"followerManagementPermission",
|
||||
"dataPrivacyPermission"
|
||||
"dataPrivacyPermission",
|
||||
"auditPermission"
|
||||
],
|
||||
"valuePermissionHighestLevels": {
|
||||
"assignmentPermission": "all",
|
||||
@@ -172,7 +173,8 @@
|
||||
"exportPermission": "yes",
|
||||
"massUpdatePermission": "yes",
|
||||
"followerManagementPermission": "all",
|
||||
"dataPrivacyPermission": "yes"
|
||||
"dataPrivacyPermission": "yes",
|
||||
"auditPermission": "yes"
|
||||
},
|
||||
"permissionsStrictDefaults": {
|
||||
"assignmentPermission": "no",
|
||||
@@ -183,7 +185,8 @@
|
||||
"exportPermission": "no",
|
||||
"massUpdatePermission": "no",
|
||||
"followerManagementPermission": "no",
|
||||
"dataPrivacyPermission": "no"
|
||||
"dataPrivacyPermission": "no",
|
||||
"auditPermission": "no"
|
||||
},
|
||||
"scopeLevelTypesStrictDefaults": {
|
||||
"boolean": false,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"detailActionList": [
|
||||
{
|
||||
"name": "viewAuditLog",
|
||||
"label": "View Audit Log",
|
||||
"actionFunction": "show",
|
||||
"checkVisibilityFunction": "isAvailable",
|
||||
"handler": "handlers/record/view-audit-log"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -78,6 +78,14 @@
|
||||
"translation": "Role.options.levelList",
|
||||
"view": "views/role/fields/permission"
|
||||
},
|
||||
"auditPermission": {
|
||||
"type": "enum",
|
||||
"options": ["not-set", "yes", "no"],
|
||||
"default": "not-set",
|
||||
"tooltip": true,
|
||||
"translation": "Role.options.levelList",
|
||||
"view": "views/role/fields/permission"
|
||||
},
|
||||
"data": {
|
||||
"type": "jsonObject"
|
||||
},
|
||||
|
||||
@@ -521,6 +521,16 @@
|
||||
"scope": ":controller"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route": "/:controller/:id/updateStream",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": "Stream",
|
||||
"action": "listUpdates",
|
||||
"id": ":id",
|
||||
"scope": ":controller"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route": "/:controller/:id/subscription",
|
||||
"method": "put",
|
||||
|
||||
@@ -76,10 +76,16 @@ class HookProcessor
|
||||
*/
|
||||
public function afterSave(Entity $entity, array $options): void
|
||||
{
|
||||
if ($this->checkHasStream($entity->getEntityType())) {
|
||||
$hasStream = $this->checkHasStream($entity->getEntityType());
|
||||
|
||||
if ($hasStream) {
|
||||
$this->afterSaveStream($entity, $options);
|
||||
}
|
||||
|
||||
if (!$hasStream) {
|
||||
$this->afterSaveNoStream($entity, $options);
|
||||
}
|
||||
|
||||
if (
|
||||
$entity->isNew() &&
|
||||
empty($options[self::OPTION_NO_STREAM]) &&
|
||||
@@ -608,4 +614,22 @@ class HookProcessor
|
||||
// Add time period (a few minutes). If before, remove RELATE note, don't create 'unrelate' if before.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
private function afterSaveNoStream(Entity $entity, array $options): void
|
||||
{
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($options[self::OPTION_NO_STREAM]) || !empty($options[SaveOption::SILENT])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
$this->service->handleAudited($entity, $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Select\SearchParams;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Entities\Note;
|
||||
@@ -52,7 +53,8 @@ class RecordService
|
||||
private Acl $acl,
|
||||
private NoteAccessControl $noteAccessControl,
|
||||
private Helper $helper,
|
||||
private QueryHelper $queryHelper
|
||||
private QueryHelper $queryHelper,
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -79,6 +81,56 @@ class RecordService
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
return $this->findInternal($scope, $id, $searchParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a record stream records.
|
||||
*
|
||||
* @return RecordCollection<Note>
|
||||
* @throws Forbidden
|
||||
* @throws BadRequest
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function findUpdates(string $scope, string $id, SearchParams $searchParams): RecordCollection
|
||||
{
|
||||
if ($this->user->isPortal()) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
if ($this->acl->getPermissionLevel('audit') !== Table::LEVEL_YES) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$entity = $this->entityManager->getEntityById($scope, $id);
|
||||
|
||||
if (!$entity) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
if (!$this->acl->checkEntityRead($entity)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
if ($entity instanceof User && !$this->user->isAdmin()) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$searchParams = $searchParams->withPrimaryFilter('updates');
|
||||
|
||||
return $this->findInternal($scope, $id, $searchParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a record stream records.
|
||||
*
|
||||
* @return RecordCollection<Note>
|
||||
* @throws Forbidden
|
||||
* @throws BadRequest
|
||||
* @throws NotFound
|
||||
*/
|
||||
private function findInternal(string $scope, string $id, SearchParams $searchParams): RecordCollection
|
||||
{
|
||||
$builder = $this->queryHelper->buildBaseQueryBuilder($searchParams);
|
||||
|
||||
$where = $this->user->isPortal() ?
|
||||
@@ -103,6 +155,7 @@ class RecordService
|
||||
$this->applyPortalAccess($builder, $where);
|
||||
$this->applyAccess($builder, $id, $scope, $where);
|
||||
$this->applyIgnore($where);
|
||||
$this->applyStatusIgnore($scope, $where);
|
||||
|
||||
$builder->where($where);
|
||||
|
||||
@@ -321,4 +374,22 @@ class RecordService
|
||||
'OR' => $orGroup,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|int, mixed> $where
|
||||
*/
|
||||
private function applyStatusIgnore(string $scope, array &$where): void
|
||||
{
|
||||
$field = $this->metadata->get("scopes.$scope.statusField");
|
||||
|
||||
if (!$field) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->acl->checkField($scope, $field)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$where[] = ['type!=' => Note::TYPE_STATUS];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
import StreamViewAuditLogModalView from 'views/stream/modals/view-audit-log';
|
||||
|
||||
class ViewAuditLogHandler {
|
||||
|
||||
constructor(/** import('views/record/detail').default */view) {
|
||||
this.view = view;
|
||||
this.metadata = /** @type {module:metadata} */view.getMetadata();
|
||||
this.entityType = this.view.entityType;
|
||||
this.model = /** @type {module:model} */this.view.model;
|
||||
|
||||
this.hasAudited = (
|
||||
this.metadata.get(`scopes.${this.entityType}.stream`) &&
|
||||
this.metadata.get(`scopes.${this.entityType}.statusField`)
|
||||
) ||
|
||||
this.model.getFieldList().find(field => this.model.getFieldParam(field, 'audited')) !== undefined;
|
||||
|
||||
if (this.entityType === 'User' && !this.view.getUser().isAdmin()) {
|
||||
this.hasAudited = false;
|
||||
}
|
||||
|
||||
if (this.view.getUser().isPortal()) {
|
||||
this.hasAudited = false;
|
||||
}
|
||||
|
||||
if (this.view.getAcl().getPermissionLevel('audit') !== 'yes') {
|
||||
this.hasAudited = false;
|
||||
}
|
||||
}
|
||||
|
||||
isAvailable() {
|
||||
return this.hasAudited;
|
||||
}
|
||||
|
||||
show() {
|
||||
const view = new StreamViewAuditLogModalView({model: this.model});
|
||||
|
||||
this.view.assignView('dialog', view)
|
||||
.then(() => {
|
||||
view.render();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export default ViewAuditLogHandler;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
import ModalView from 'views/modal';
|
||||
import ListStreamRecordView from 'views/stream/record/list';
|
||||
import $ from 'jquery';
|
||||
|
||||
class StreamViewAuditLogModalView extends ModalView {
|
||||
|
||||
templateContent = '<div class="record list-container">{{{record}}}</div>'
|
||||
|
||||
backdrop = true
|
||||
|
||||
/**
|
||||
* @param {{model: import('model').default}} options
|
||||
*/
|
||||
constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
setup() {
|
||||
const name = this.model.get('name') || this.model.id;
|
||||
|
||||
this.$header = $('<span>')
|
||||
.append(
|
||||
$('<span>').text(name),
|
||||
' <span class="chevron-right"></span> ',
|
||||
$('<span>').text(this.translate('Audit Log'))
|
||||
);
|
||||
|
||||
this.buttonList = [
|
||||
{
|
||||
name: 'close',
|
||||
label: 'Close',
|
||||
onClick: dialog => {
|
||||
dialog.close();
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
this.wait(
|
||||
this.getCollectionFactory().create('Note').then(collection => {
|
||||
collection.url = `${this.model.entityType}/${this.model.id}/updateStream`;
|
||||
collection.maxSize = this.getConfig().get('recordsPerPage');
|
||||
|
||||
const listView = new ListStreamRecordView({
|
||||
collection: collection,
|
||||
model: this.model,
|
||||
// Prevents 'No Data' being displayed.
|
||||
skipBuildRows: true,
|
||||
});
|
||||
|
||||
Espo.Ui.notify(' ... ');
|
||||
|
||||
return this.assignView('record', listView, '.record')
|
||||
.then(() => {
|
||||
collection.fetch()
|
||||
.then(() => Espo.Ui.notify(false));
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default StreamViewAuditLogModalView;
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace tests\integration\Espo\Stream;
|
||||
|
||||
use Espo\Core\Acl\Table;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Record\CreateParams;
|
||||
use Espo\Core\Record\ServiceContainer;
|
||||
use Espo\Core\Record\UpdateParams;
|
||||
use Espo\Core\Select\SearchParams;
|
||||
use Espo\Entities\Note;
|
||||
use Espo\Modules\Crm\Entities\KnowledgeBaseArticle;
|
||||
use Espo\Tools\Stream\RecordService;
|
||||
use tests\integration\Core\BaseTestCase;
|
||||
|
||||
class AuditTest extends BaseTestCase
|
||||
{
|
||||
public function testAudit1(): void
|
||||
{
|
||||
$this->createUser('test1', [
|
||||
'auditPermission' => Table::LEVEL_YES,
|
||||
'data' => [
|
||||
KnowledgeBaseArticle::ENTITY_TYPE => [
|
||||
'create' => Table::LEVEL_NO,
|
||||
'read' => Table::LEVEL_ALL,
|
||||
],
|
||||
]
|
||||
]);
|
||||
|
||||
$this->createUser('test2', [
|
||||
'auditPermission' => Table::LEVEL_NO,
|
||||
'data' => [
|
||||
KnowledgeBaseArticle::ENTITY_TYPE => [
|
||||
'create' => Table::LEVEL_NO,
|
||||
'read' => Table::LEVEL_ALL,
|
||||
],
|
||||
]
|
||||
]);
|
||||
|
||||
$this->createUser('test3', [
|
||||
'auditPermission' => Table::LEVEL_NO,
|
||||
'data' => [
|
||||
KnowledgeBaseArticle::ENTITY_TYPE => [
|
||||
'create' => Table::LEVEL_NO,
|
||||
'read' => Table::LEVEL_NO,
|
||||
],
|
||||
]
|
||||
]);
|
||||
|
||||
$this->getMetadata()->set('entityDefs', KnowledgeBaseArticle::ENTITY_TYPE, [
|
||||
'fields' => [
|
||||
'publishDate' => [
|
||||
'audited' => true,
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->getMetadata()->save();
|
||||
|
||||
$this->reCreateApplication();
|
||||
|
||||
$service = $this->getContainer()
|
||||
->getByClass(ServiceContainer::class)
|
||||
->getByClass(KnowledgeBaseArticle::class);
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$article = $service->create((object) [
|
||||
'name' => 'Test 1',
|
||||
'publishDate' => '2025-01-01',
|
||||
], CreateParams::create());
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$service->update($article->getId(), (object) [
|
||||
'publishDate' => '2025-01-02',
|
||||
], UpdateParams::create());
|
||||
|
||||
$em = $this->getEntityManager();
|
||||
|
||||
$note = $em->getRDBRepositoryByClass(Note::class)
|
||||
->where([
|
||||
'parentId' => $article->getId(),
|
||||
'parentType' => $article->getEntityType(),
|
||||
'type' => Note::TYPE_UPDATE,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
$this->assertNotNull($note);
|
||||
$this->assertEquals(['publishDate'], $note->getData()?->fields);
|
||||
|
||||
$searchParams = SearchParams::create();
|
||||
|
||||
$this->auth('test1');
|
||||
$this->reCreateApplication();
|
||||
|
||||
$service = $this->getInjectableFactory()->create(RecordService::class);
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$recordCollection = $service->findUpdates(KnowledgeBaseArticle::ENTITY_TYPE, $article->getId(), $searchParams);
|
||||
|
||||
$this->assertCount(1, $recordCollection->getCollection()->getValueMapList());
|
||||
|
||||
$this->auth('test2');
|
||||
$this->reCreateApplication();
|
||||
|
||||
$isThrown = false;
|
||||
|
||||
try {
|
||||
$service = $this->getInjectableFactory()->create(RecordService::class);
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$service->findUpdates(KnowledgeBaseArticle::ENTITY_TYPE, $article->getId(), $searchParams);
|
||||
}
|
||||
catch (Forbidden) {
|
||||
$isThrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($isThrown);
|
||||
|
||||
$this->auth('test3');
|
||||
$this->reCreateApplication();
|
||||
|
||||
$isThrown = false;
|
||||
|
||||
try {
|
||||
$service = $this->getInjectableFactory()->create(RecordService::class);
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$service->findUpdates(KnowledgeBaseArticle::ENTITY_TYPE, $article->getId(), $searchParams);
|
||||
}
|
||||
catch (Forbidden) {
|
||||
$isThrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($isThrown);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user