Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Validate the scope when validating operations
Signed-off-by: Joas Schilling <[email protected]>
  • Loading branch information
nickvergessen committed Feb 23, 2023
commit e361d508fe7b2ee1772b1cdcfa81fdbf247c4cae
13 changes: 10 additions & 3 deletions apps/workflowengine/lib/Manager.php
Original file line number Diff line number Diff line change
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 @@ -382,7 +382,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 @@ -482,9 +482,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 @@ -496,6 +499,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
Original file line number Diff line number Diff line change
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());
}
}
}