Skip to content

Commit c19d37f

Browse files
authored
Merge pull request #56839 from nextcloud/backport/56630/stable31
[stable31] refactor(workflowengine): Check if class is correct
2 parents 847ae2b + 66b54b1 commit c19d37f

File tree

2 files changed

+57
-26
lines changed

2 files changed

+57
-26
lines changed

apps/workflowengine/lib/Manager.php

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
/**
34
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
45
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -423,10 +424,6 @@ protected function validateEvents(string $entity, array $events, IOperation $ope
423424
throw new \UnexpectedValueException($this->l->t('Entity %s does not exist', [$entity]));
424425
}
425426

426-
if (!$instance instanceof IEntity) {
427-
throw new \UnexpectedValueException($this->l->t('Entity %s is invalid', [$entity]));
428-
}
429-
430427
if (empty($events)) {
431428
if (!$operation instanceof IComplexOperation) {
432429
throw new \UnexpectedValueException($this->l->t('No events are chosen.'));
@@ -457,17 +454,23 @@ protected function validateEvents(string $entity, array $events, IOperation $ope
457454
* @throws \UnexpectedValueException
458455
*/
459456
public function validateOperation($class, $name, array $checks, $operation, ScopeContext $scope, string $entity, array $events) {
457+
if (strlen($operation) > IManager::MAX_OPERATION_VALUE_BYTES) {
458+
throw new \UnexpectedValueException($this->l->t('The provided operation data is too long'));
459+
}
460+
461+
/** @psalm-suppress TaintedCallable newInstance is not called */
462+
$reflection = new \ReflectionClass($class);
463+
if ($class !== IOperation::class && !in_array(IOperation::class, $reflection->getInterfaceNames())) {
464+
throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]) . join(', ', $reflection->getInterfaceNames()));
465+
}
466+
460467
try {
461468
/** @var IOperation $instance */
462469
$instance = $this->container->query($class);
463470
} catch (QueryException $e) {
464471
throw new \UnexpectedValueException($this->l->t('Operation %s does not exist', [$class]));
465472
}
466473

467-
if (!($instance instanceof IOperation)) {
468-
throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]));
469-
}
470-
471474
if (!$instance->isAvailableForScope($scope->getScope())) {
472475
throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]));
473476
}
@@ -489,27 +492,28 @@ public function validateOperation($class, $name, array $checks, $operation, Scop
489492
throw new \UnexpectedValueException($this->l->t('Invalid check provided'));
490493
}
491494

495+
if (strlen((string)$check['value']) > IManager::MAX_CHECK_VALUE_BYTES) {
496+
throw new \UnexpectedValueException($this->l->t('The provided check value is too long'));
497+
}
498+
499+
$reflection = new \ReflectionClass($check['class']);
500+
if ($check['class'] !== ICheck::class && !in_array(ICheck::class, $reflection->getInterfaceNames())) {
501+
throw new \UnexpectedValueException($this->l->t('Check %s is invalid', [$class]));
502+
}
503+
492504
try {
493505
/** @var ICheck $instance */
494506
$instance = $this->container->query($check['class']);
495507
} catch (QueryException $e) {
496508
throw new \UnexpectedValueException($this->l->t('Check %s does not exist', [$class]));
497509
}
498510

499-
if (!($instance instanceof ICheck)) {
500-
throw new \UnexpectedValueException($this->l->t('Check %s is invalid', [$class]));
501-
}
502-
503511
if (!empty($instance->supportedEntities())
504512
&& !in_array($entity, $instance->supportedEntities())
505513
) {
506514
throw new \UnexpectedValueException($this->l->t('Check %s is not allowed with this entity', [$class]));
507515
}
508516

509-
if (strlen((string)$check['value']) > IManager::MAX_CHECK_VALUE_BYTES) {
510-
throw new \UnexpectedValueException($this->l->t('The provided check value is too long'));
511-
}
512-
513517
$instance->validateCheck($check['operator'], $check['value']);
514518
}
515519
}

apps/workflowengine/tests/ManagerTest.php

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use OCA\WorkflowEngine\Helper\ScopeContext;
1212
use OCA\WorkflowEngine\Manager;
1313
use OCP\AppFramework\QueryException;
14+
use OCP\EventDispatcher\Event;
1415
use OCP\EventDispatcher\IEventDispatcher;
1516
use OCP\Files\Events\Node\NodeCreatedEvent;
1617
use OCP\Files\IRootFolder;
@@ -31,10 +32,41 @@
3132
use OCP\WorkflowEngine\IEntityEvent;
3233
use OCP\WorkflowEngine\IManager;
3334
use OCP\WorkflowEngine\IOperation;
35+
use OCP\WorkflowEngine\IRuleMatcher;
3436
use PHPUnit\Framework\MockObject\MockObject;
3537
use Psr\Log\LoggerInterface;
3638
use Test\TestCase;
3739

40+
class TestAdminOp implements IOperation {
41+
public function getDisplayName(): string {
42+
return 'Admin';
43+
}
44+
45+
public function getDescription(): string {
46+
return '';
47+
}
48+
49+
public function getIcon(): string {
50+
return '';
51+
}
52+
53+
public function isAvailableForScope(int $scope): bool {
54+
return true;
55+
}
56+
57+
public function validateOperation(string $name, array $checks, string $operation): void {
58+
}
59+
60+
public function onEvent(string $eventName, Event $event, IRuleMatcher $ruleMatcher): void {
61+
}
62+
}
63+
64+
class TestUserOp extends TestAdminOp {
65+
public function getDisplayName(): string {
66+
return 'User';
67+
}
68+
}
69+
3870
/**
3971
* Class ManagerTest
4072
*
@@ -405,19 +437,19 @@ public function testUpdateOperation(): void {
405437
$opId1 = $this->invokePrivate(
406438
$this->manager,
407439
'insertOperation',
408-
['OCA\WFE\TestAdminOp', 'Test01', [11, 22], 'foo', $entity, []]
440+
[TestAdminOp::class, 'Test01', [11, 22], 'foo', $entity, []]
409441
);
410442
$this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]);
411443

412444
$opId2 = $this->invokePrivate(
413445
$this->manager,
414446
'insertOperation',
415-
['OCA\WFE\TestUserOp', 'Test02', [33, 22], 'bar', $entity, []]
447+
[TestUserOp::class, 'Test02', [33, 22], 'bar', $entity, []]
416448
);
417449
$this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]);
418450

419-
$check1 = ['class' => 'OCA\WFE\C22', 'operator' => 'eq', 'value' => 'asdf'];
420-
$check2 = ['class' => 'OCA\WFE\C33', 'operator' => 'eq', 'value' => 23456];
451+
$check1 = ['class' => ICheck::class, 'operator' => 'eq', 'value' => 'asdf'];
452+
$check2 = ['class' => ICheck::class, 'operator' => 'eq', 'value' => 23456];
421453

422454
/** @noinspection PhpUnhandledExceptionInspection */
423455
$op = $this->manager->updateOperation($opId1, 'Test01a', [$check1, $check2], 'foohur', $adminScope, $entity, ['\OCP\Files::postDelete']);
@@ -680,11 +712,6 @@ public function testValidateOperationDataLengthError(): void {
680712
->method('getScope')
681713
->willReturn(IManager::SCOPE_ADMIN);
682714

683-
$operationMock->expects($this->once())
684-
->method('isAvailableForScope')
685-
->with(IManager::SCOPE_ADMIN)
686-
->willReturn(true);
687-
688715
$operationMock->expects($this->never())
689716
->method('validateOperation');
690717

@@ -732,7 +759,7 @@ public function testValidateOperationScopeNotAvailable(): void {
732759
'operator' => 'is',
733760
'value' => 'barfoo',
734761
];
735-
$operationData = str_pad('', IManager::MAX_OPERATION_VALUE_BYTES + 1, 'FooBar');
762+
$operationData = str_pad('', IManager::MAX_OPERATION_VALUE_BYTES - 1, 'FooBar');
736763

737764
$operationMock = $this->createMock(IOperation::class);
738765
$entityMock = $this->createMock(IEntity::class);

0 commit comments

Comments
 (0)