Skip to content
Permalink
Browse files Browse the repository at this point in the history
Validate the scope when validating operations
Signed-off-by: Joas Schilling <coding@schilljs.com>
  • Loading branch information
nickvergessen committed Feb 23, 2023
1 parent 0da274b commit 5a06b50
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 8 deletions.
13 changes: 10 additions & 3 deletions apps/workflowengine/lib/Manager.php
Expand Up @@ -310,7 +310,7 @@ public function addOperation(
string $entity,
array $events
) {
$this->validateOperation($class, $name, $checks, $operation, $entity, $events);
$this->validateOperation($class, $name, $checks, $operation, $scope, $entity, $events);

$this->connection->beginTransaction();

Expand Down Expand Up @@ -383,7 +383,7 @@ public function updateOperation(
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 {
Expand Down Expand Up @@ -483,9 +483,12 @@ protected function validateEvents(string $entity, array $events, IOperation $ope
* @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);
Expand All @@ -497,6 +500,10 @@ public function validateOperation($class, $name, array $checks, $operation, stri
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) {
Expand Down
113 changes: 108 additions & 5 deletions apps/workflowengine/tests/ManagerTest.php
Expand Up @@ -288,11 +288,20 @@ public function testUpdateOperation() {
$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([
Expand Down Expand Up @@ -453,6 +462,16 @@ public function testValidateOperationOK() {
$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')
Expand Down Expand Up @@ -489,7 +508,7 @@ public function testValidateOperationOK() {
}
});

$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() {
Expand All @@ -503,6 +522,16 @@ public function testValidateOperationCheckInputLengthError() {
$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')
Expand Down Expand Up @@ -540,7 +569,7 @@ public function testValidateOperationCheckInputLengthError() {
});

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());
}
Expand All @@ -558,6 +587,16 @@ public function testValidateOperationDataLengthError() {
$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');
Expand Down Expand Up @@ -594,9 +633,73 @@ public function testValidateOperationDataLengthError() {
});

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());
}
}
}

0 comments on commit 5a06b50

Please sign in to comment.