]> source.dussan.org Git - nextcloud-server.git/commitdiff
Validate the scope when validating operations
authorJoas Schilling <coding@schilljs.com>
Wed, 15 Feb 2023 12:20:20 +0000 (13:20 +0100)
committerJoas Schilling <coding@schilljs.com>
Thu, 23 Feb 2023 05:15:36 +0000 (06:15 +0100)
Signed-off-by: Joas Schilling <coding@schilljs.com>
apps/workflowengine/lib/Manager.php
apps/workflowengine/tests/ManagerTest.php

index 659fd2421c1863d340d74a4bfd211880725e5c16..023112a21f609cf804621ce7ff1c05b8e6d841ab 100644 (file)
@@ -310,7 +310,7 @@ class Manager implements IManager {
                string $entity,
                array $events
        ) {
-               $this->validateOperation($class, $name, $checks, $operation, $entity, $events);
+               $this->validateOperation($class, $name, $checks, $operation, $scope, $entity, $events);
 
                $this->connection->beginTransaction();
 
@@ -383,7 +383,7 @@ class Manager implements IManager {
                        throw new \DomainException('Target operation not within scope');
                };
                $row = $this->getOperation($id);
-               $this->validateOperation($row['class'], $name, $checks, $operation, $entity, $events);
+               $this->validateOperation($row['class'], $name, $checks, $operation, $scopeContext, $entity, $events);
 
                $checkIds = [];
                try {
@@ -483,9 +483,12 @@ class Manager implements IManager {
         * @param string $name
         * @param array[] $checks
         * @param string $operation
+        * @param ScopeContext $scope
+        * @param string $entity
+        * @param array $events
         * @throws \UnexpectedValueException
         */
-       public function validateOperation($class, $name, array $checks, $operation, string $entity, array $events) {
+       public function validateOperation($class, $name, array $checks, $operation, ScopeContext $scope, string $entity, array $events) {
                try {
                        /** @var IOperation $instance */
                        $instance = $this->container->query($class);
@@ -497,6 +500,10 @@ class Manager implements IManager {
                        throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]));
                }
 
+               if (!$instance->isAvailableForScope($scope->getScope())) {
+                       throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]));
+               }
+
                $this->validateEvents($entity, $events, $instance);
 
                if (count($checks) === 0) {
index 612495a5b6d96d605b9898ecdb39488c50c42301..6fb65f433736a91b478b662015b770052ab10a8d 100644 (file)
@@ -288,11 +288,20 @@ class ManagerTest extends TestCase {
                $userScope = $this->buildScope('jackie');
                $entity = File::class;
 
+               $operationMock = $this->createMock(IOperation::class);
+               $operationMock->expects($this->any())
+                       ->method('isAvailableForScope')
+                       ->withConsecutive(
+                               [IManager::SCOPE_ADMIN],
+                               [IManager::SCOPE_USER]
+                       )
+                       ->willReturn(true);
+
                $this->container->expects($this->any())
                        ->method('query')
-                       ->willReturnCallback(function ($class) {
+                       ->willReturnCallback(function ($class) use ($operationMock) {
                                if (substr($class, -2) === 'Op') {
-                                       return $this->createMock(IOperation::class);
+                                       return $operationMock;
                                } elseif ($class === File::class) {
                                        return $this->getMockBuilder(File::class)
                                                ->setConstructorArgs([
@@ -453,6 +462,16 @@ class ManagerTest extends TestCase {
                $entityMock = $this->createMock(IEntity::class);
                $eventEntityMock = $this->createMock(IEntityEvent::class);
                $checkMock = $this->createMock(ICheck::class);
+               $scopeMock = $this->createMock(ScopeContext::class);
+
+               $scopeMock->expects($this->any())
+                       ->method('getScope')
+                       ->willReturn(IManager::SCOPE_ADMIN);
+
+               $operationMock->expects($this->once())
+                       ->method('isAvailableForScope')
+                       ->with(IManager::SCOPE_ADMIN)
+                       ->willReturn(true);
 
                $operationMock->expects($this->once())
                        ->method('validateOperation')
@@ -489,7 +508,7 @@ class ManagerTest extends TestCase {
                                }
                        });
 
-               $this->manager->validateOperation(IOperation::class, 'test', [$check], 'operationData', IEntity::class, ['MyEvent']);
+               $this->manager->validateOperation(IOperation::class, 'test', [$check], 'operationData', $scopeMock, IEntity::class, ['MyEvent']);
        }
 
        public function testValidateOperationCheckInputLengthError() {
@@ -503,6 +522,16 @@ class ManagerTest extends TestCase {
                $entityMock = $this->createMock(IEntity::class);
                $eventEntityMock = $this->createMock(IEntityEvent::class);
                $checkMock = $this->createMock(ICheck::class);
+               $scopeMock = $this->createMock(ScopeContext::class);
+
+               $scopeMock->expects($this->any())
+                       ->method('getScope')
+                       ->willReturn(IManager::SCOPE_ADMIN);
+
+               $operationMock->expects($this->once())
+                       ->method('isAvailableForScope')
+                       ->with(IManager::SCOPE_ADMIN)
+                       ->willReturn(true);
 
                $operationMock->expects($this->once())
                        ->method('validateOperation')
@@ -540,7 +569,7 @@ class ManagerTest extends TestCase {
                        });
 
                try {
-                       $this->manager->validateOperation(IOperation::class, 'test', [$check], 'operationData', IEntity::class, ['MyEvent']);
+                       $this->manager->validateOperation(IOperation::class, 'test', [$check], 'operationData', $scopeMock, IEntity::class, ['MyEvent']);
                } catch (\UnexpectedValueException $e) {
                        $this->assertSame('The provided check value is too long', $e->getMessage());
                }
@@ -558,6 +587,16 @@ class ManagerTest extends TestCase {
                $entityMock = $this->createMock(IEntity::class);
                $eventEntityMock = $this->createMock(IEntityEvent::class);
                $checkMock = $this->createMock(ICheck::class);
+               $scopeMock = $this->createMock(ScopeContext::class);
+
+               $scopeMock->expects($this->any())
+                       ->method('getScope')
+                       ->willReturn(IManager::SCOPE_ADMIN);
+
+               $operationMock->expects($this->once())
+                       ->method('isAvailableForScope')
+                       ->with(IManager::SCOPE_ADMIN)
+                       ->willReturn(true);
 
                $operationMock->expects($this->never())
                        ->method('validateOperation');
@@ -594,9 +633,73 @@ class ManagerTest extends TestCase {
                        });
 
                try {
-                       $this->manager->validateOperation(IOperation::class, 'test', [$check], $operationData, IEntity::class, ['MyEvent']);
+                       $this->manager->validateOperation(IOperation::class, 'test', [$check], $operationData, $scopeMock, IEntity::class, ['MyEvent']);
                } catch (\UnexpectedValueException $e) {
                        $this->assertSame('The provided operation data is too long', $e->getMessage());
                }
        }
+
+       public function testValidateOperationScopeNotAvailable() {
+               $check = [
+                       'class' => ICheck::class,
+                       'operator' => 'is',
+                       'value' => 'barfoo',
+               ];
+               $operationData = str_pad('', IManager::MAX_OPERATION_VALUE_BYTES + 1, 'FooBar');
+
+               $operationMock = $this->createMock(IOperation::class);
+               $entityMock = $this->createMock(IEntity::class);
+               $eventEntityMock = $this->createMock(IEntityEvent::class);
+               $checkMock = $this->createMock(ICheck::class);
+               $scopeMock = $this->createMock(ScopeContext::class);
+
+               $scopeMock->expects($this->any())
+                       ->method('getScope')
+                       ->willReturn(IManager::SCOPE_ADMIN);
+
+               $operationMock->expects($this->once())
+                       ->method('isAvailableForScope')
+                       ->with(IManager::SCOPE_ADMIN)
+                       ->willReturn(false);
+
+               $operationMock->expects($this->never())
+                       ->method('validateOperation');
+
+               $entityMock->expects($this->any())
+                       ->method('getEvents')
+                       ->willReturn([$eventEntityMock]);
+
+               $eventEntityMock->expects($this->any())
+                       ->method('getEventName')
+                       ->willReturn('MyEvent');
+
+               $checkMock->expects($this->any())
+                       ->method('supportedEntities')
+                       ->willReturn([IEntity::class]);
+               $checkMock->expects($this->never())
+                       ->method('validateCheck');
+
+               $this->container->expects($this->any())
+                       ->method('query')
+                       ->willReturnCallback(function ($className) use ($operationMock, $entityMock, $eventEntityMock, $checkMock) {
+                               switch ($className) {
+                                       case IOperation::class:
+                                               return $operationMock;
+                                       case IEntity::class:
+                                               return $entityMock;
+                                       case IEntityEvent::class:
+                                               return $eventEntityMock;
+                                       case ICheck::class:
+                                               return $checkMock;
+                                       default:
+                                               return $this->createMock($className);
+                               }
+                       });
+
+               try {
+                       $this->manager->validateOperation(IOperation::class, 'test', [$check], $operationData, $scopeMock, IEntity::class, ['MyEvent']);
+               } catch (\UnexpectedValueException $e) {
+                       $this->assertSame('Operation OCP\WorkflowEngine\IOperation is invalid', $e->getMessage());
+               }
+       }
 }