migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,125 @@
<?php
/**
* @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Julius Härtl <jus@bitgrid.net>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\AppInfo;
use Closure;
use OCA\WorkflowEngine\Helper\LogContext;
use OCA\WorkflowEngine\Listener\LoadAdditionalSettingsScriptsListener;
use OCA\WorkflowEngine\Manager;
use OCA\WorkflowEngine\Service\Logger;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\WorkflowEngine\Events\LoadSettingsScriptsEvent;
use OCP\WorkflowEngine\IEntity;
use OCP\WorkflowEngine\IOperation;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
class Application extends App implements IBootstrap {
public const APP_ID = 'workflowengine';
public function __construct() {
parent::__construct(self::APP_ID);
}
public function register(IRegistrationContext $context): void {
$context->registerEventListener(
LoadSettingsScriptsEvent::class,
LoadAdditionalSettingsScriptsListener::class,
-100
);
}
public function boot(IBootContext $context): void {
$context->injectFn(Closure::fromCallable([$this, 'registerRuleListeners']));
}
private function registerRuleListeners(IEventDispatcher $dispatcher,
ContainerInterface $container,
LoggerInterface $logger): void {
/** @var Manager $manager */
$manager = $container->get(Manager::class);
$configuredEvents = $manager->getAllConfiguredEvents();
foreach ($configuredEvents as $operationClass => $events) {
foreach ($events as $entityClass => $eventNames) {
array_map(function (string $eventName) use ($manager, $container, $dispatcher, $logger, $operationClass, $entityClass) {
$dispatcher->addListener(
$eventName,
function ($event) use ($manager, $container, $eventName, $logger, $operationClass, $entityClass) {
$ruleMatcher = $manager->getRuleMatcher();
try {
/** @var IEntity $entity */
$entity = $container->get($entityClass);
/** @var IOperation $operation */
$operation = $container->get($operationClass);
$ruleMatcher->setEventName($eventName);
$ruleMatcher->setEntity($entity);
$ruleMatcher->setOperation($operation);
$ctx = new LogContext();
$ctx
->setOperation($operation)
->setEntity($entity)
->setEventName($eventName);
/** @var Logger $flowLogger */
$flowLogger = $container->get(Logger::class);
$flowLogger->logEventInit($ctx);
if ($event instanceof Event) {
$entity->prepareRuleMatcher($ruleMatcher, $eventName, $event);
$operation->onEvent($eventName, $event, $ruleMatcher);
} else {
$logger->debug(
'Cannot handle event {name} of {event} against entity {entity} and operation {operation}',
[
'app' => self::APP_ID,
'name' => $eventName,
'event' => get_class($event),
'entity' => $entityClass,
'operation' => $operationClass,
]
);
}
$flowLogger->logEventDone($ctx);
} catch (ContainerExceptionInterface $e) {
// Ignore query exceptions since they might occur when an entity/operation were set up before by an app that is disabled now
}
}
);
}, $eventNames ?? []);
}
}
}
}
@@ -0,0 +1,55 @@
<?php
/**
* @copyright Copyright (c) 2018 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\BackgroundJobs;
use OCA\WorkflowEngine\AppInfo\Application;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use OCP\Log\RotationTrait;
class Rotate extends TimedJob {
use RotationTrait;
public function __construct(ITimeFactory $time) {
parent::__construct($time);
$this->setInterval(60 * 60 * 3);
}
protected function run($argument) {
$config = \OC::$server->getConfig();
$default = $config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/flow.log';
$this->filePath = trim((string)$config->getAppValue(Application::APP_ID, 'logfile', $default));
if ($this->filePath === '') {
// disabled, nothing to do
return;
}
$this->maxSize = $config->getSystemValue('log_rotate_size', 100 * 1024 * 1024);
if ($this->shouldRotateBySize()) {
$this->rotate();
}
}
}
@@ -0,0 +1,125 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Check;
use OCP\IL10N;
use OCP\WorkflowEngine\ICheck;
use OCP\WorkflowEngine\IManager;
abstract class AbstractStringCheck implements ICheck {
/** @var array[] Nested array: [Pattern => [ActualValue => Regex Result]] */
protected $matches;
/** @var IL10N */
protected $l;
/**
* @param IL10N $l
*/
public function __construct(IL10N $l) {
$this->l = $l;
}
/**
* @return string
*/
abstract protected function getActualValue();
/**
* @param string $operator
* @param string $value
* @return bool
*/
public function executeCheck($operator, $value) {
$actualValue = $this->getActualValue();
return $this->executeStringCheck($operator, $value, $actualValue);
}
/**
* @param string $operator
* @param string $checkValue
* @param string $actualValue
* @return bool
*/
protected function executeStringCheck($operator, $checkValue, $actualValue) {
if ($operator === 'is') {
return $checkValue === $actualValue;
} elseif ($operator === '!is') {
return $checkValue !== $actualValue;
} else {
$match = $this->match($checkValue, $actualValue);
if ($operator === 'matches') {
return $match === 1;
} else {
return $match === 0;
}
}
}
/**
* @param string $operator
* @param string $value
* @throws \UnexpectedValueException
*/
public function validateCheck($operator, $value) {
if (!in_array($operator, ['is', '!is', 'matches', '!matches'])) {
throw new \UnexpectedValueException($this->l->t('The given operator is invalid'), 1);
}
if (in_array($operator, ['matches', '!matches']) &&
@preg_match($value, null) === false) {
throw new \UnexpectedValueException($this->l->t('The given regular expression is invalid'), 2);
}
}
public function supportedEntities(): array {
// universal by default
return [];
}
public function isAvailableForScope(int $scope): bool {
// admin only by default
return $scope === IManager::SCOPE_ADMIN;
}
/**
* @param string $pattern
* @param string $subject
* @return int|bool
*/
protected function match($pattern, $subject) {
$patternHash = md5($pattern);
$subjectHash = md5($subject);
if (isset($this->matches[$patternHash][$subjectHash])) {
return $this->matches[$patternHash][$subjectHash];
}
if (!isset($this->matches[$patternHash])) {
$this->matches[$patternHash] = [];
}
$this->matches[$patternHash][$subjectHash] = preg_match($pattern, $subject);
return $this->matches[$patternHash][$subjectHash];
}
}
@@ -0,0 +1,179 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Check;
use OC\Files\Storage\Local;
use OCA\WorkflowEngine\Entity\File;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\Storage\IStorage;
use OCP\IL10N;
use OCP\IRequest;
use OCP\WorkflowEngine\IFileCheck;
class FileMimeType extends AbstractStringCheck implements IFileCheck {
use TFileCheck {
setFileInfo as _setFileInfo;
}
/** @var array */
protected $mimeType;
/** @var IRequest */
protected $request;
/** @var IMimeTypeDetector */
protected $mimeTypeDetector;
/**
* @param IL10N $l
* @param IRequest $request
* @param IMimeTypeDetector $mimeTypeDetector
*/
public function __construct(IL10N $l, IRequest $request, IMimeTypeDetector $mimeTypeDetector) {
parent::__construct($l);
$this->request = $request;
$this->mimeTypeDetector = $mimeTypeDetector;
}
/**
* @param IStorage $storage
* @param string $path
* @param bool $isDir
*/
public function setFileInfo(IStorage $storage, string $path, bool $isDir = false): void {
$this->_setFileInfo($storage, $path, $isDir);
if (!isset($this->mimeType[$this->storage->getId()][$this->path])
|| $this->mimeType[$this->storage->getId()][$this->path] === '') {
if ($isDir) {
$this->mimeType[$this->storage->getId()][$this->path] = 'httpd/unix-directory';
} else {
$this->mimeType[$this->storage->getId()][$this->path] = null;
}
}
}
/**
* The mimetype is only cached if the file has a valid mimetype. Otherwise files access
* control will cache "application/octet-stream" for all the target node on:
* rename, move, copy and all other methods which create a new item
*
* To check this:
* 1. Add an automated tagging rule which tags on mimetype NOT "httpd/unix-directory"
* 2. Add an access control rule which checks for any mimetype
* 3. Create a folder and rename it, the folder should not be tagged, but it is
*
* @param string $storageId
* @param string|null $path
* @param string $mimeType
* @return string
*/
protected function cacheAndReturnMimeType(string $storageId, ?string $path, string $mimeType): string {
if ($path !== null && $mimeType !== 'application/octet-stream') {
$this->mimeType[$storageId][$path] = $mimeType;
}
return $mimeType;
}
/**
* Make sure that even though the content based check returns an application/octet-stream can still be checked based on mimetypemappings of their extension
*
* @param string $operator
* @param string $value
* @return bool
*/
public function executeCheck($operator, $value) {
return $this->executeStringCheck($operator, $value, $this->getActualValue());
}
/**
* @return string
*/
protected function getActualValue() {
if ($this->mimeType[$this->storage->getId()][$this->path] !== null) {
return $this->mimeType[$this->storage->getId()][$this->path];
}
$cacheEntry = $this->storage->getCache()->get($this->path);
if ($cacheEntry && $cacheEntry->getMimeType() !== 'application/octet-stream') {
return $this->cacheAndReturnMimeType($this->storage->getId(), $this->path, $cacheEntry->getMimeType());
}
if ($this->storage->file_exists($this->path) &&
$this->storage->filesize($this->path) &&
$this->storage->instanceOfStorage(Local::class)
) {
$path = $this->storage->getLocalFile($this->path);
$mimeType = $this->mimeTypeDetector->detectContent($path);
return $this->cacheAndReturnMimeType($this->storage->getId(), $this->path, $mimeType);
}
if ($this->isWebDAVRequest() || $this->isPublicWebDAVRequest()) {
// Creating a folder
if ($this->request->getMethod() === 'MKCOL') {
return 'httpd/unix-directory';
}
}
// We do not cache this, as the file did not exist yet.
// In case it does in the future, we will check with detectContent()
// again to get the real mimetype of the content, rather than
// guessing it from the path.
return $this->mimeTypeDetector->detectPath($this->path);
}
/**
* @return bool
*/
protected function isWebDAVRequest() {
return substr($this->request->getScriptName(), 0 - strlen('/remote.php')) === '/remote.php' && (
$this->request->getPathInfo() === '/webdav' ||
str_starts_with($this->request->getPathInfo() ?? '', '/webdav/') ||
$this->request->getPathInfo() === '/dav/files' ||
str_starts_with($this->request->getPathInfo() ?? '', '/dav/files/') ||
$this->request->getPathInfo() === '/dav/uploads' ||
str_starts_with($this->request->getPathInfo() ?? '', '/dav/uploads/')
);
}
/**
* @return bool
*/
protected function isPublicWebDAVRequest() {
return substr($this->request->getScriptName(), 0 - strlen('/public.php')) === '/public.php' && (
$this->request->getPathInfo() === '/webdav' ||
str_starts_with($this->request->getPathInfo() ?? '', '/webdav/')
);
}
public function supportedEntities(): array {
return [ File::class ];
}
public function isAvailableForScope(int $scope): bool {
return true;
}
}
@@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Daniel Kesselberg <mail@danielkesselberg.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author szaimen <szaimen@e.mail.de>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Check;
use OC\Files\Storage\Local;
use OCA\WorkflowEngine\Entity\File;
use OCP\Files\Mount\IMountManager;
use OCP\IL10N;
use OCP\IRequest;
use OCP\WorkflowEngine\IFileCheck;
class FileName extends AbstractStringCheck implements IFileCheck {
use TFileCheck;
/** @var IRequest */
protected $request;
/** @var IMountManager */
private $mountManager;
/**
* @param IL10N $l
* @param IRequest $request
*/
public function __construct(IL10N $l, IRequest $request, IMountManager $mountManager) {
parent::__construct($l);
$this->request = $request;
$this->mountManager = $mountManager;
}
/**
* @return string
*/
protected function getActualValue(): string {
$fileName = $this->path === null ? '' : basename($this->path);
if ($fileName === '' && (!$this->storage->isLocal() || $this->storage->instanceOfStorage(Local::class))) {
// Return the mountpoint name of external storage that are not mounted as user home
$mountPoints = $this->mountManager->findByStorageId($this->storage->getId());
if (empty($mountPoints) || $mountPoints[0]->getMountType() !== 'external') {
return $fileName;
}
$mountPointPath = rtrim($mountPoints[0]->getMountPoint(), '/');
$mountPointPieces = explode('/', $mountPointPath);
$mountPointName = array_pop($mountPointPieces);
if (!empty($mountPointName) && $mountPointName !== 'files' && count($mountPointPieces) !== 2) {
return $mountPointName;
}
}
return $fileName;
}
/**
* @param string $operator
* @param string $checkValue
* @param string $actualValue
* @return bool
*/
protected function executeStringCheck($operator, $checkValue, $actualValue): bool {
if ($operator === 'is' || $operator === '!is') {
$checkValue = mb_strtolower($checkValue);
$actualValue = mb_strtolower($actualValue);
}
return parent::executeStringCheck($operator, $checkValue, $actualValue);
}
public function supportedEntities(): array {
return [ File::class ];
}
public function isAvailableForScope(int $scope): bool {
return true;
}
}
@@ -0,0 +1,122 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Check;
use OCA\WorkflowEngine\Entity\File;
use OCP\IL10N;
use OCP\IRequest;
use OCP\Util;
use OCP\WorkflowEngine\ICheck;
class FileSize implements ICheck {
/** @var int */
protected $size;
/** @var IL10N */
protected $l;
/** @var IRequest */
protected $request;
/**
* @param IL10N $l
* @param IRequest $request
*/
public function __construct(IL10N $l, IRequest $request) {
$this->l = $l;
$this->request = $request;
}
/**
* @param string $operator
* @param string $value
* @return bool
*/
public function executeCheck($operator, $value) {
$size = $this->getFileSizeFromHeader();
$value = Util::computerFileSize($value);
if ($size !== false) {
switch ($operator) {
case 'less':
return $size < $value;
case '!less':
return $size >= $value;
case 'greater':
return $size > $value;
case '!greater':
return $size <= $value;
}
}
return false;
}
/**
* @param string $operator
* @param string $value
* @throws \UnexpectedValueException
*/
public function validateCheck($operator, $value) {
if (!in_array($operator, ['less', '!less', 'greater', '!greater'])) {
throw new \UnexpectedValueException($this->l->t('The given operator is invalid'), 1);
}
if (!preg_match('/^[0-9]+[ ]?[kmgt]?b$/i', $value)) {
throw new \UnexpectedValueException($this->l->t('The given file size is invalid'), 2);
}
}
/**
* @return string
*/
protected function getFileSizeFromHeader() {
if ($this->size !== null) {
return $this->size;
}
$size = $this->request->getHeader('OC-Total-Length');
if ($size === '') {
if (in_array($this->request->getMethod(), ['POST', 'PUT'])) {
$size = $this->request->getHeader('Content-Length');
}
}
if ($size === '') {
$size = false;
}
$this->size = $size;
return $this->size;
}
public function supportedEntities(): array {
return [ File::class ];
}
public function isAvailableForScope(int $scope): bool {
return true;
}
}
@@ -0,0 +1,209 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Check;
use OC\Files\Storage\Wrapper\Wrapper;
use OCA\Files_Sharing\SharedStorage;
use OCA\WorkflowEngine\Entity\File;
use OCP\Files\Cache\ICache;
use OCP\Files\IHomeStorage;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IUser;
use OCP\IUserSession;
use OCP\SystemTag\ISystemTagManager;
use OCP\SystemTag\ISystemTagObjectMapper;
use OCP\SystemTag\TagNotFoundException;
use OCP\WorkflowEngine\ICheck;
use OCP\WorkflowEngine\IFileCheck;
class FileSystemTags implements ICheck, IFileCheck {
use TFileCheck;
/** @var array */
protected $fileIds;
/** @var array */
protected $fileSystemTags;
/** @var IL10N */
protected $l;
/** @var ISystemTagManager */
protected $systemTagManager;
/** @var ISystemTagObjectMapper */
protected $systemTagObjectMapper;
/** @var IUserSession */
protected $userSession;
/** @var IGroupManager */
protected $groupManager;
public function __construct(
IL10N $l,
ISystemTagManager $systemTagManager,
ISystemTagObjectMapper $systemTagObjectMapper,
IUserSession $userSession,
IGroupManager $groupManager
) {
$this->l = $l;
$this->systemTagManager = $systemTagManager;
$this->systemTagObjectMapper = $systemTagObjectMapper;
$this->userSession = $userSession;
$this->groupManager = $groupManager;
}
/**
* @param string $operator
* @param string $value
* @return bool
*/
public function executeCheck($operator, $value) {
$systemTags = $this->getSystemTags();
return ($operator === 'is') === in_array($value, $systemTags);
}
/**
* @param string $operator
* @param string $value
* @throws \UnexpectedValueException
*/
public function validateCheck($operator, $value) {
if (!in_array($operator, ['is', '!is'])) {
throw new \UnexpectedValueException($this->l->t('The given operator is invalid'), 1);
}
try {
$tags = $this->systemTagManager->getTagsByIds($value);
$user = $this->userSession->getUser();
$isAdmin = $user instanceof IUser && $this->groupManager->isAdmin($user->getUID());
if (!$isAdmin) {
foreach ($tags as $tag) {
if (!$tag->isUserVisible()) {
throw new \UnexpectedValueException($this->l->t('The given tag id is invalid'), 4);
}
}
}
} catch (TagNotFoundException $e) {
throw new \UnexpectedValueException($this->l->t('The given tag id is invalid'), 2);
} catch (\InvalidArgumentException $e) {
throw new \UnexpectedValueException($this->l->t('The given tag id is invalid'), 3);
}
}
/**
* Get the ids of the assigned system tags
* @return string[]
*/
protected function getSystemTags() {
$cache = $this->storage->getCache();
$fileIds = $this->getFileIds($cache, $this->path, !$this->storage->instanceOfStorage(IHomeStorage::class) || $this->storage->instanceOfStorage(SharedStorage::class));
$systemTags = [];
foreach ($fileIds as $i => $fileId) {
if (isset($this->fileSystemTags[$fileId])) {
$systemTags[] = $this->fileSystemTags[$fileId];
unset($fileIds[$i]);
}
}
if (!empty($fileIds)) {
$mappedSystemTags = $this->systemTagObjectMapper->getTagIdsForObjects($fileIds, 'files');
foreach ($mappedSystemTags as $fileId => $fileSystemTags) {
$this->fileSystemTags[$fileId] = $fileSystemTags;
$systemTags[] = $fileSystemTags;
}
}
$systemTags = call_user_func_array('array_merge', $systemTags);
$systemTags = array_unique($systemTags);
return $systemTags;
}
/**
* Get the file ids of the given path and its parents
* @param ICache $cache
* @param string $path
* @param bool $isExternalStorage
* @return int[]
*/
protected function getFileIds(ICache $cache, $path, $isExternalStorage) {
/** @psalm-suppress InvalidArgument */
if ($this->storage->instanceOfStorage(\OCA\GroupFolders\Mount\GroupFolderStorage::class)) {
// Special implementation for groupfolder since all groupfolders share the same storage
// id so add the group folder id in the cache key too.
$groupFolderStorage = $this->storage;
if ($this->storage instanceof Wrapper) {
$groupFolderStorage = $this->storage->getInstanceOfStorage(\OCA\GroupFolders\Mount\GroupFolderStorage::class);
}
if ($groupFolderStorage === null) {
throw new \LogicException('Should not happen: Storage is instance of GroupFolderStorage but no group folder storage found while unwrapping.');
}
/**
* @psalm-suppress UndefinedDocblockClass
* @psalm-suppress UndefinedInterfaceMethod
*/
$cacheId = $cache->getNumericStorageId() . '/' . $groupFolderStorage->getFolderId();
} else {
$cacheId = $cache->getNumericStorageId();
}
if (isset($this->fileIds[$cacheId][$path])) {
return $this->fileIds[$cacheId][$path];
}
$parentIds = [];
if ($path !== $this->dirname($path)) {
$parentIds = $this->getFileIds($cache, $this->dirname($path), $isExternalStorage);
} elseif (!$isExternalStorage) {
return [];
}
$fileId = $cache->getId($path);
if ($fileId !== -1) {
$parentIds[] = $fileId;
}
$this->fileIds[$cacheId][$path] = $parentIds;
return $parentIds;
}
protected function dirname($path) {
$dir = dirname($path);
return $dir === '.' ? '' : $dir;
}
public function supportedEntities(): array {
return [ File::class ];
}
public function isAvailableForScope(int $scope): bool {
return true;
}
}
@@ -0,0 +1,177 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author J0WI <J0WI@users.noreply.github.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Morris Jobke <hey@morrisjobke.de>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Check;
use OCP\IL10N;
use OCP\IRequest;
use OCP\WorkflowEngine\ICheck;
class RequestRemoteAddress implements ICheck {
/** @var IL10N */
protected $l;
/** @var IRequest */
protected $request;
/**
* @param IL10N $l
* @param IRequest $request
*/
public function __construct(IL10N $l, IRequest $request) {
$this->l = $l;
$this->request = $request;
}
/**
* @param string $operator
* @param string $value
* @return bool
*/
public function executeCheck($operator, $value) {
$actualValue = $this->request->getRemoteAddress();
$decodedValue = explode('/', $value);
if ($operator === 'matchesIPv4') {
return $this->matchIPv4($actualValue, $decodedValue[0], $decodedValue[1]);
} elseif ($operator === '!matchesIPv4') {
return !$this->matchIPv4($actualValue, $decodedValue[0], $decodedValue[1]);
} elseif ($operator === 'matchesIPv6') {
return $this->matchIPv6($actualValue, $decodedValue[0], $decodedValue[1]);
} else {
return !$this->matchIPv6($actualValue, $decodedValue[0], $decodedValue[1]);
}
}
/**
* @param string $operator
* @param string $value
* @throws \UnexpectedValueException
*/
public function validateCheck($operator, $value) {
if (!in_array($operator, ['matchesIPv4', '!matchesIPv4', 'matchesIPv6', '!matchesIPv6'])) {
throw new \UnexpectedValueException($this->l->t('The given operator is invalid'), 1);
}
$decodedValue = explode('/', $value);
if (count($decodedValue) !== 2) {
throw new \UnexpectedValueException($this->l->t('The given IP range is invalid'), 2);
}
if (in_array($operator, ['matchesIPv4', '!matchesIPv4'])) {
if (!filter_var($decodedValue[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
throw new \UnexpectedValueException($this->l->t('The given IP range is not valid for IPv4'), 3);
}
if ($decodedValue[1] > 32 || $decodedValue[1] <= 0) {
throw new \UnexpectedValueException($this->l->t('The given IP range is not valid for IPv4'), 4);
}
} else {
if (!filter_var($decodedValue[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
throw new \UnexpectedValueException($this->l->t('The given IP range is not valid for IPv6'), 3);
}
if ($decodedValue[1] > 128 || $decodedValue[1] <= 0) {
throw new \UnexpectedValueException($this->l->t('The given IP range is not valid for IPv6'), 4);
}
}
}
/**
* Based on https://stackoverflow.com/a/594134
* @param string $ip
* @param string $rangeIp
* @param int $bits
* @return bool
*/
protected function matchIPv4($ip, $rangeIp, $bits) {
$rangeDecimal = ip2long($rangeIp);
$ipDecimal = ip2long($ip);
$mask = -1 << (32 - $bits);
return ($ipDecimal & $mask) === ($rangeDecimal & $mask);
}
/**
* Based on https://stackoverflow.com/a/7951507
* @param string $ip
* @param string $rangeIp
* @param int $bits
* @return bool
*/
protected function matchIPv6($ip, $rangeIp, $bits) {
$ipNet = inet_pton($ip);
$binaryIp = $this->ipv6ToBits($ipNet);
$ipNetBits = substr($binaryIp, 0, $bits);
$rangeNet = inet_pton($rangeIp);
$binaryRange = $this->ipv6ToBits($rangeNet);
$rangeNetBits = substr($binaryRange, 0, $bits);
return $ipNetBits === $rangeNetBits;
}
/**
* Based on https://stackoverflow.com/a/7951507
* @param string $packedIp
* @return string
*/
protected function ipv6ToBits($packedIp) {
$unpackedIp = unpack('A16', $packedIp);
$unpackedIp = str_split($unpackedIp[1]);
$binaryIp = '';
foreach ($unpackedIp as $char) {
$binaryIp .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
}
return str_pad($binaryIp, 128, '0', STR_PAD_RIGHT);
}
/**
* returns a list of Entities the checker supports. The values must match
* the class name of the entity.
*
* An empty result means the check is universally available.
*
* @since 18.0.0
*/
public function supportedEntities(): array {
return [];
}
/**
* returns whether the operation can be used in the requested scope.
*
* Scope IDs are defined as constants in OCP\WorkflowEngine\IManager. At
* time of writing these are SCOPE_ADMIN and SCOPE_USER.
*
* For possibly unknown future scopes the recommended behaviour is: if
* user scope is permitted, the default behaviour should return `true`,
* otherwise `false`.
*
* @since 18.0.0
*/
public function isAvailableForScope(int $scope): bool {
return true;
}
}
@@ -0,0 +1,138 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Check;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IL10N;
use OCP\WorkflowEngine\ICheck;
class RequestTime implements ICheck {
public const REGEX_TIME = '([0-1][0-9]|2[0-3]):([0-5][0-9])';
public const REGEX_TIMEZONE = '([a-zA-Z]+(?:\\/[a-zA-Z\-\_]+)+)';
/** @var bool[] */
protected $cachedResults;
/** @var IL10N */
protected $l;
/** @var ITimeFactory */
protected $timeFactory;
/**
* @param ITimeFactory $timeFactory
*/
public function __construct(IL10N $l, ITimeFactory $timeFactory) {
$this->l = $l;
$this->timeFactory = $timeFactory;
}
/**
* @param string $operator
* @param string $value
* @return bool
*/
public function executeCheck($operator, $value) {
$valueHash = md5($value);
if (isset($this->cachedResults[$valueHash])) {
return $this->cachedResults[$valueHash];
}
$timestamp = $this->timeFactory->getTime();
$values = json_decode($value, true);
$timestamp1 = $this->getTimestamp($timestamp, $values[0]);
$timestamp2 = $this->getTimestamp($timestamp, $values[1]);
if ($timestamp1 < $timestamp2) {
$in = $timestamp1 <= $timestamp && $timestamp <= $timestamp2;
} else {
$in = $timestamp1 <= $timestamp || $timestamp <= $timestamp2;
}
return ($operator === 'in') ? $in : !$in;
}
/**
* @param int $currentTimestamp
* @param string $value Format: "H:i e"
* @return int
*/
protected function getTimestamp($currentTimestamp, $value) {
[$time1, $timezone1] = explode(' ', $value);
[$hour1, $minute1] = explode(':', $time1);
$date1 = new \DateTime('now', new \DateTimeZone($timezone1));
$date1->setTimestamp($currentTimestamp);
$date1->setTime($hour1, $minute1);
return $date1->getTimestamp();
}
/**
* @param string $operator
* @param string $value
* @throws \UnexpectedValueException
*/
public function validateCheck($operator, $value) {
if (!in_array($operator, ['in', '!in'])) {
throw new \UnexpectedValueException($this->l->t('The given operator is invalid'), 1);
}
$regexValue = '\"' . self::REGEX_TIME . ' ' . self::REGEX_TIMEZONE . '\"';
$result = preg_match('/^\[' . $regexValue . ',' . $regexValue . '\]$/', $value, $matches);
if (!$result) {
throw new \UnexpectedValueException($this->l->t('The given time span is invalid'), 2);
}
$values = json_decode($value, true);
$time1 = \DateTime::createFromFormat('H:i e', (string)$values[0]);
if ($time1 === false) {
throw new \UnexpectedValueException($this->l->t('The given start time is invalid'), 3);
}
$time2 = \DateTime::createFromFormat('H:i e', (string)$values[1]);
if ($time2 === false) {
throw new \UnexpectedValueException($this->l->t('The given end time is invalid'), 4);
}
}
public function isAvailableForScope(int $scope): bool {
return true;
}
/**
* returns a list of Entities the checker supports. The values must match
* the class name of the entity.
*
* An empty result means the check is universally available.
*
* @since 18.0.0
*/
public function supportedEntities(): array {
return [];
}
}
@@ -0,0 +1,98 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Check;
use OCP\IL10N;
use OCP\IRequest;
class RequestURL extends AbstractStringCheck {
public const CLI = 'cli';
/** @var ?string */
protected $url;
/** @var IRequest */
protected $request;
/**
* @param IL10N $l
* @param IRequest $request
*/
public function __construct(IL10N $l, IRequest $request) {
parent::__construct($l);
$this->request = $request;
}
/**
* @param string $operator
* @param string $value
* @return bool
*/
public function executeCheck($operator, $value) {
if (\OC::$CLI) {
$actualValue = $this->url = RequestURL::CLI;
} else {
$actualValue = $this->getActualValue();
}
if (in_array($operator, ['is', '!is'])) {
switch ($value) {
case 'webdav':
if ($operator === 'is') {
return $this->isWebDAVRequest();
} else {
return !$this->isWebDAVRequest();
}
}
}
return $this->executeStringCheck($operator, $value, $actualValue);
}
/**
* @return string
*/
protected function getActualValue() {
if ($this->url !== null) {
return $this->url;
}
$this->url = $this->request->getServerProtocol() . '://';// E.g. http(s) + ://
$this->url .= $this->request->getServerHost();// E.g. localhost
$this->url .= $this->request->getScriptName();// E.g. /nextcloud/index.php
$this->url .= $this->request->getPathInfo();// E.g. /apps/files_texteditor/ajax/loadfile
return $this->url; // E.g. https://localhost/nextcloud/index.php/apps/files_texteditor/ajax/loadfile
}
protected function isWebDAVRequest(): bool {
if ($this->url === RequestURL::CLI) {
return false;
}
return substr($this->request->getScriptName(), 0 - strlen('/remote.php')) === '/remote.php' && (
$this->request->getPathInfo() === '/webdav' ||
str_starts_with($this->request->getPathInfo() ?? '', '/webdav/') ||
$this->request->getPathInfo() === '/dav/files' ||
str_starts_with($this->request->getPathInfo() ?? '', '/dav/files/')
);
}
}
@@ -0,0 +1,88 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Check;
use OCP\IL10N;
use OCP\IRequest;
class RequestUserAgent extends AbstractStringCheck {
/** @var IRequest */
protected $request;
/**
* @param IL10N $l
* @param IRequest $request
*/
public function __construct(IL10N $l, IRequest $request) {
parent::__construct($l);
$this->request = $request;
}
/**
* @param string $operator
* @param string $value
* @return bool
*/
public function executeCheck($operator, $value) {
$actualValue = $this->getActualValue();
if (in_array($operator, ['is', '!is'], true)) {
switch ($value) {
case 'android':
$operator = $operator === 'is' ? 'matches' : '!matches';
$value = IRequest::USER_AGENT_CLIENT_ANDROID;
break;
case 'ios':
$operator = $operator === 'is' ? 'matches' : '!matches';
$value = IRequest::USER_AGENT_CLIENT_IOS;
break;
case 'desktop':
$operator = $operator === 'is' ? 'matches' : '!matches';
$value = IRequest::USER_AGENT_CLIENT_DESKTOP;
break;
case 'mail':
if ($operator === 'is') {
return $this->executeStringCheck('matches', IRequest::USER_AGENT_OUTLOOK_ADDON, $actualValue)
|| $this->executeStringCheck('matches', IRequest::USER_AGENT_THUNDERBIRD_ADDON, $actualValue);
}
return $this->executeStringCheck('!matches', IRequest::USER_AGENT_OUTLOOK_ADDON, $actualValue)
&& $this->executeStringCheck('!matches', IRequest::USER_AGENT_THUNDERBIRD_ADDON, $actualValue);
}
}
return $this->executeStringCheck($operator, $value, $actualValue);
}
/**
* @return string
*/
protected function getActualValue() {
return $this->request->getHeader('User-Agent');
}
public function isAvailableForScope(int $scope): bool {
return true;
}
}
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Check;
use OCA\WorkflowEngine\AppInfo\Application;
use OCA\WorkflowEngine\Entity\File;
use OCP\Files\Node;
use OCP\Files\Storage\IStorage;
use OCP\WorkflowEngine\IEntity;
trait TFileCheck {
/** @var IStorage */
protected $storage;
/** @var string */
protected $path;
/** @var bool */
protected $isDir;
/**
* @param IStorage $storage
* @param string $path
* @param bool $isDir
* @since 18.0.0
*/
public function setFileInfo(IStorage $storage, string $path, bool $isDir = false): void {
$this->storage = $storage;
$this->path = $path;
$this->isDir = $isDir;
}
/**
* @throws \OCP\Files\NotFoundException
*/
public function setEntitySubject(IEntity $entity, $subject): void {
if ($entity instanceof File) {
if (!$subject instanceof Node) {
throw new \UnexpectedValueException(
'Expected Node subject for File entity, got {class}',
['app' => Application::APP_ID, 'class' => get_class($subject)]
);
}
$this->storage = $subject->getStorage();
$this->path = $subject->getPath();
}
}
}
@@ -0,0 +1,118 @@
<?php
/**
* @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
*
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Morris Jobke <hey@morrisjobke.de>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Check;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IUser;
use OCP\IUserSession;
use OCP\WorkflowEngine\ICheck;
use OCP\WorkflowEngine\IManager;
class UserGroupMembership implements ICheck {
/** @var string */
protected $cachedUser;
/** @var string[] */
protected $cachedGroupMemberships;
/** @var IUserSession */
protected $userSession;
/** @var IGroupManager */
protected $groupManager;
/** @var IL10N */
protected $l;
/**
* @param IUserSession $userSession
* @param IGroupManager $groupManager
* @param IL10N $l
*/
public function __construct(IUserSession $userSession, IGroupManager $groupManager, IL10N $l) {
$this->userSession = $userSession;
$this->groupManager = $groupManager;
$this->l = $l;
}
/**
* @param string $operator
* @param string $value
* @return bool
*/
public function executeCheck($operator, $value) {
$user = $this->userSession->getUser();
if ($user instanceof IUser) {
$groupIds = $this->getUserGroups($user);
return ($operator === 'is') === in_array($value, $groupIds);
} else {
return $operator !== 'is';
}
}
/**
* @param string $operator
* @param string $value
* @throws \UnexpectedValueException
*/
public function validateCheck($operator, $value) {
if (!in_array($operator, ['is', '!is'])) {
throw new \UnexpectedValueException($this->l->t('The given operator is invalid'), 1);
}
if (!$this->groupManager->groupExists($value)) {
throw new \UnexpectedValueException($this->l->t('The given group does not exist'), 2);
}
}
/**
* @param IUser $user
* @return string[]
*/
protected function getUserGroups(IUser $user) {
$uid = $user->getUID();
if ($this->cachedUser !== $uid) {
$this->cachedUser = $uid;
$this->cachedGroupMemberships = $this->groupManager->getUserGroupIds($user);
}
return $this->cachedGroupMemberships;
}
public function supportedEntities(): array {
// universal by default
return [];
}
public function isAvailableForScope(int $scope): bool {
// admin only by default
return $scope === IManager::SCOPE_ADMIN;
}
}
@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Command;
use OCA\WorkflowEngine\Helper\ScopeContext;
use OCA\WorkflowEngine\Manager;
use OCP\WorkflowEngine\IManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Index extends Command {
/** @var Manager */
private $manager;
public function __construct(Manager $manager) {
$this->manager = $manager;
parent::__construct();
}
protected function configure() {
$this
->setName('workflows:list')
->setDescription('Lists configured workflows')
->addArgument(
'scope',
InputArgument::OPTIONAL,
'Lists workflows for "admin", "user"',
'admin'
)
->addArgument(
'scopeId',
InputArgument::OPTIONAL,
'User IDs when the scope is "user"',
null
);
}
protected function mappedScope(string $scope): int {
static $scopes = [
'admin' => IManager::SCOPE_ADMIN,
'user' => IManager::SCOPE_USER,
];
return $scopes[$scope] ?? -1;
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$ops = $this->manager->getAllOperations(
new ScopeContext(
$this->mappedScope($input->getArgument('scope')),
$input->getArgument('scopeId')
)
);
$output->writeln(\json_encode($ops));
return 0;
}
}
@@ -0,0 +1,179 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author blizzz <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Controller;
use Doctrine\DBAL\Exception;
use OCA\WorkflowEngine\Helper\ScopeContext;
use OCA\WorkflowEngine\Manager;
use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use Psr\Log\LoggerInterface;
abstract class AWorkflowController extends OCSController {
/** @var Manager */
protected $manager;
/** @var LoggerInterface */
private $logger;
public function __construct(
$appName,
IRequest $request,
Manager $manager,
LoggerInterface $logger
) {
parent::__construct($appName, $request);
$this->manager = $manager;
$this->logger = $logger;
}
/**
* @throws OCSForbiddenException
*/
abstract protected function getScopeContext(): ScopeContext;
/**
* Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/global?format=json"
*
* @throws OCSForbiddenException
*/
public function index(): DataResponse {
$operationsByClass = $this->manager->getAllOperations($this->getScopeContext());
foreach ($operationsByClass as &$operations) {
foreach ($operations as &$operation) {
$operation = $this->manager->formatOperation($operation);
}
}
return new DataResponse($operationsByClass);
}
/**
* Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/global/OCA\\Workflow_DocToPdf\\Operation?format=json"
*
* @throws OCSForbiddenException
*/
public function show(string $id): DataResponse {
$context = $this->getScopeContext();
// The ID corresponds to a class name
$operations = $this->manager->getOperations($id, $context);
foreach ($operations as &$operation) {
$operation = $this->manager->formatOperation($operation);
}
return new DataResponse($operations);
}
/**
* @throws OCSBadRequestException
* @throws OCSForbiddenException
* @throws OCSException
*/
#[PasswordConfirmationRequired]
public function create(
string $class,
string $name,
array $checks,
string $operation,
string $entity,
array $events
): DataResponse {
$context = $this->getScopeContext();
try {
$operation = $this->manager->addOperation($class, $name, $checks, $operation, $context, $entity, $events);
$operation = $this->manager->formatOperation($operation);
return new DataResponse($operation);
} catch (\UnexpectedValueException $e) {
throw new OCSBadRequestException($e->getMessage(), $e);
} catch (\DomainException $e) {
throw new OCSForbiddenException($e->getMessage(), $e);
} catch (Exception $e) {
$this->logger->error('Error when inserting flow', ['exception' => $e]);
throw new OCSException('An internal error occurred', $e->getCode(), $e);
}
}
/**
* @throws OCSBadRequestException
* @throws OCSForbiddenException
* @throws OCSException
*/
#[PasswordConfirmationRequired]
public function update(
int $id,
string $name,
array $checks,
string $operation,
string $entity,
array $events
): DataResponse {
try {
$context = $this->getScopeContext();
$operation = $this->manager->updateOperation($id, $name, $checks, $operation, $context, $entity, $events);
$operation = $this->manager->formatOperation($operation);
return new DataResponse($operation);
} catch (\UnexpectedValueException $e) {
throw new OCSBadRequestException($e->getMessage(), $e);
} catch (\DomainException $e) {
throw new OCSForbiddenException($e->getMessage(), $e);
} catch (Exception $e) {
$this->logger->error('Error when updating flow with id ' . $id, ['exception' => $e]);
throw new OCSException('An internal error occurred', $e->getCode(), $e);
}
}
/**
* @throws OCSBadRequestException
* @throws OCSForbiddenException
* @throws OCSException
*/
#[PasswordConfirmationRequired]
public function destroy(int $id): DataResponse {
try {
$deleted = $this->manager->deleteOperation($id, $this->getScopeContext());
return new DataResponse($deleted);
} catch (\UnexpectedValueException $e) {
throw new OCSBadRequestException($e->getMessage(), $e);
} catch (\DomainException $e) {
throw new OCSForbiddenException($e->getMessage(), $e);
} catch (Exception $e) {
$this->logger->error('Error when deleting flow with id ' . $id, ['exception' => $e]);
throw new OCSException('An internal error occurred', $e->getCode(), $e);
}
}
}
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Controller;
use OCA\WorkflowEngine\Helper\ScopeContext;
use OCP\WorkflowEngine\IManager;
class GlobalWorkflowsController extends AWorkflowController {
/** @var ScopeContext */
private $scopeContext;
protected function getScopeContext(): ScopeContext {
if ($this->scopeContext === null) {
$this->scopeContext = new ScopeContext(IManager::SCOPE_ADMIN);
}
return $this->scopeContext;
}
}
@@ -0,0 +1,54 @@
<?php
/**
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
class RequestTimeController extends Controller {
/**
* @NoAdminRequired
*
* @param string $search
* @return JSONResponse
*/
public function getTimezones($search = '') {
$timezones = \DateTimeZone::listIdentifiers();
if ($search !== '') {
$timezones = array_filter($timezones, function ($timezone) use ($search) {
return stripos($timezone, $search) !== false;
});
}
$timezones = array_slice($timezones, 0, 10);
$response = [];
foreach ($timezones as $timezone) {
$response[$timezone] = $timezone;
}
return new JSONResponse($response);
}
}
@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Controller;
use OCA\WorkflowEngine\Helper\ScopeContext;
use OCA\WorkflowEngine\Manager;
use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\IRequest;
use OCP\IUserSession;
use OCP\WorkflowEngine\IManager;
use Psr\Log\LoggerInterface;
class UserWorkflowsController extends AWorkflowController {
/** @var IUserSession */
private $session;
/** @var ScopeContext */
private $scopeContext;
public function __construct(
$appName,
IRequest $request,
Manager $manager,
IUserSession $session,
LoggerInterface $logger
) {
parent::__construct($appName, $request, $manager, $logger);
$this->session = $session;
}
/**
* Retrieve all configured workflow rules
*
* Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/user?format=json"
*
* @NoAdminRequired
* @throws OCSForbiddenException
*/
public function index(): DataResponse {
return parent::index();
}
/**
* @NoAdminRequired
*
* Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/user/OCA\\Workflow_DocToPdf\\Operation?format=json"
* @throws OCSForbiddenException
*/
public function show(string $id): DataResponse {
return parent::show($id);
}
/**
* @NoAdminRequired
* @throws OCSBadRequestException
* @throws OCSForbiddenException
*/
#[PasswordConfirmationRequired]
public function create(string $class, string $name, array $checks, string $operation, string $entity, array $events): DataResponse {
return parent::create($class, $name, $checks, $operation, $entity, $events);
}
/**
* @NoAdminRequired
* @throws OCSBadRequestException
* @throws OCSForbiddenException
*/
#[PasswordConfirmationRequired]
public function update(int $id, string $name, array $checks, string $operation, string $entity, array $events): DataResponse {
return parent::update($id, $name, $checks, $operation, $entity, $events);
}
/**
* @NoAdminRequired
* @throws OCSForbiddenException
*/
#[PasswordConfirmationRequired]
public function destroy(int $id): DataResponse {
return parent::destroy($id);
}
/**
* @throws OCSForbiddenException
*/
protected function getScopeContext(): ScopeContext {
if ($this->scopeContext === null) {
$user = $this->session->getUser();
if (!$user || !$this->manager->isUserScopeEnabled()) {
throw new OCSForbiddenException('User not logged in');
}
$this->scopeContext = new ScopeContext(IManager::SCOPE_USER, $user->getUID());
}
return $this->scopeContext;
}
}
@@ -0,0 +1,297 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Jonas Meurer <jonas@freesources.org>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Entity;
use OC\Files\Config\UserMountCache;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\GenericEvent;
use OCP\Files\InvalidPathException;
use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountManager;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\SystemTag\ISystemTag;
use OCP\SystemTag\ISystemTagManager;
use OCP\SystemTag\MapperEvent;
use OCP\WorkflowEngine\EntityContext\IContextPortation;
use OCP\WorkflowEngine\EntityContext\IDisplayText;
use OCP\WorkflowEngine\EntityContext\IIcon;
use OCP\WorkflowEngine\EntityContext\IUrl;
use OCP\WorkflowEngine\GenericEntityEvent;
use OCP\WorkflowEngine\IEntity;
use OCP\WorkflowEngine\IRuleMatcher;
class File implements IEntity, IDisplayText, IUrl, IIcon, IContextPortation {
private const EVENT_NAMESPACE = '\OCP\Files::';
/** @var IL10N */
protected $l10n;
/** @var IURLGenerator */
protected $urlGenerator;
/** @var IRootFolder */
protected $root;
/** @var string */
protected $eventName;
/** @var Event */
protected $event;
/** @var IUserSession */
private $userSession;
/** @var ISystemTagManager */
private $tagManager;
/** @var ?Node */
private $node;
/** @var ?IUser */
private $actingUser = null;
/** @var IUserManager */
private $userManager;
/** @var UserMountCache */
private $userMountCache;
/** @var IMountManager */
private $mountManager;
public function __construct(
IL10N $l10n,
IURLGenerator $urlGenerator,
IRootFolder $root,
IUserSession $userSession,
ISystemTagManager $tagManager,
IUserManager $userManager,
UserMountCache $userMountCache,
IMountManager $mountManager
) {
$this->l10n = $l10n;
$this->urlGenerator = $urlGenerator;
$this->root = $root;
$this->userSession = $userSession;
$this->tagManager = $tagManager;
$this->userManager = $userManager;
$this->userMountCache = $userMountCache;
$this->mountManager = $mountManager;
}
public function getName(): string {
return $this->l10n->t('File');
}
public function getIcon(): string {
return $this->urlGenerator->imagePath('core', 'categories/files.svg');
}
public function getEvents(): array {
return [
new GenericEntityEvent($this->l10n->t('File created'), self::EVENT_NAMESPACE . 'postCreate'),
new GenericEntityEvent($this->l10n->t('File updated'), self::EVENT_NAMESPACE . 'postWrite'),
new GenericEntityEvent($this->l10n->t('File renamed'), self::EVENT_NAMESPACE . 'postRename'),
new GenericEntityEvent($this->l10n->t('File deleted'), self::EVENT_NAMESPACE . 'postDelete'),
new GenericEntityEvent($this->l10n->t('File accessed'), self::EVENT_NAMESPACE . 'postTouch'),
new GenericEntityEvent($this->l10n->t('File copied'), self::EVENT_NAMESPACE . 'postCopy'),
new GenericEntityEvent($this->l10n->t('Tag assigned'), MapperEvent::EVENT_ASSIGN),
];
}
public function prepareRuleMatcher(IRuleMatcher $ruleMatcher, string $eventName, Event $event): void {
if (!$event instanceof GenericEvent && !$event instanceof MapperEvent) {
return;
}
$this->eventName = $eventName;
$this->event = $event;
$this->actingUser = $this->actingUser ?? $this->userSession->getUser();
try {
$node = $this->getNode();
$ruleMatcher->setEntitySubject($this, $node);
$ruleMatcher->setFileInfo($node->getStorage(), $node->getInternalPath());
} catch (NotFoundException $e) {
// pass
}
}
public function isLegitimatedForUserId(string $userId): bool {
try {
$node = $this->getNode();
if ($node->getOwner()?->getUID() === $userId) {
return true;
}
if ($this->eventName === self::EVENT_NAMESPACE . 'postDelete') {
// At postDelete, the file no longer exists. Check for parent folder instead.
$fileId = $node->getParentId();
} else {
$fileId = $node->getId();
}
$mountInfos = $this->userMountCache->getMountsForFileId($fileId, $userId);
foreach ($mountInfos as $mountInfo) {
$mount = $this->mountManager->getMountFromMountInfo($mountInfo);
if ($mount && $mount->getStorage() && !empty($mount->getStorage()->getCache()->get($fileId))) {
return true;
}
}
return false;
} catch (NotFoundException $e) {
return false;
}
}
/**
* @throws NotFoundException
*/
protected function getNode(): Node {
if ($this->node) {
return $this->node;
}
if (!$this->event instanceof GenericEvent && !$this->event instanceof MapperEvent) {
throw new NotFoundException();
}
switch ($this->eventName) {
case self::EVENT_NAMESPACE . 'postCreate':
case self::EVENT_NAMESPACE . 'postWrite':
case self::EVENT_NAMESPACE . 'postDelete':
case self::EVENT_NAMESPACE . 'postTouch':
return $this->event->getSubject();
case self::EVENT_NAMESPACE . 'postRename':
case self::EVENT_NAMESPACE . 'postCopy':
return $this->event->getSubject()[1];
case MapperEvent::EVENT_ASSIGN:
if (!$this->event instanceof MapperEvent || $this->event->getObjectType() !== 'files') {
throw new NotFoundException();
}
$nodes = $this->root->getById((int)$this->event->getObjectId());
if (is_array($nodes) && isset($nodes[0])) {
$this->node = $nodes[0];
return $this->node;
}
break;
}
throw new NotFoundException();
}
public function getDisplayText(int $verbosity = 0): string {
try {
$node = $this->getNode();
} catch (NotFoundException $e) {
return '';
}
$options = [
$this->actingUser ? $this->actingUser->getDisplayName() : $this->l10n->t('Someone'),
$node->getName()
];
switch ($this->eventName) {
case self::EVENT_NAMESPACE . 'postCreate':
return $this->l10n->t('%s created %s', $options);
case self::EVENT_NAMESPACE . 'postWrite':
return $this->l10n->t('%s modified %s', $options);
case self::EVENT_NAMESPACE . 'postDelete':
return $this->l10n->t('%s deleted %s', $options);
case self::EVENT_NAMESPACE . 'postTouch':
return $this->l10n->t('%s accessed %s', $options);
case self::EVENT_NAMESPACE . 'postRename':
return $this->l10n->t('%s renamed %s', $options);
case self::EVENT_NAMESPACE . 'postCopy':
return $this->l10n->t('%s copied %s', $options);
case MapperEvent::EVENT_ASSIGN:
$tagNames = [];
if ($this->event instanceof MapperEvent) {
$tagIDs = $this->event->getTags();
$tagObjects = $this->tagManager->getTagsByIds($tagIDs);
foreach ($tagObjects as $systemTag) {
/** @var ISystemTag $systemTag */
if ($systemTag->isUserVisible()) {
$tagNames[] = $systemTag->getName();
}
}
}
$filename = array_pop($options);
$tagString = implode(', ', $tagNames);
if ($tagString === '') {
return '';
}
array_push($options, $tagString, $filename);
return $this->l10n->t('%s assigned %s to %s', $options);
}
}
public function getUrl(): string {
try {
return $this->urlGenerator->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $this->getNode()->getId()]);
} catch (InvalidPathException $e) {
return '';
} catch (NotFoundException $e) {
return '';
}
}
/**
* @inheritDoc
*/
public function exportContextIDs(): array {
$nodeOwner = $this->getNode()->getOwner();
$actingUserId = null;
if ($this->actingUser instanceof IUser) {
$actingUserId = $this->actingUser->getUID();
} elseif ($this->userSession->getUser() instanceof IUser) {
$actingUserId = $this->userSession->getUser()->getUID();
}
return [
'eventName' => $this->eventName,
'nodeId' => $this->getNode()->getId(),
'nodeOwnerId' => $nodeOwner ? $nodeOwner->getUID() : null,
'actingUserId' => $actingUserId,
];
}
/**
* @inheritDoc
*/
public function importContextIDs(array $contextIDs): void {
$this->eventName = $contextIDs['eventName'];
if ($contextIDs['nodeOwnerId'] !== null) {
$userFolder = $this->root->getUserFolder($contextIDs['nodeOwnerId']);
$nodes = $userFolder->getById($contextIDs['nodeId']);
} else {
$nodes = $this->root->getById($contextIDs['nodeId']);
}
$this->node = $nodes[0] ?? null;
if ($contextIDs['actingUserId']) {
$this->actingUser = $this->userManager->get($contextIDs['actingUserId']);
}
}
/**
* @inheritDoc
*/
public function getIconUrl(): string {
return $this->getIcon();
}
}
@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Helper;
use OCP\WorkflowEngine\IEntity;
use OCP\WorkflowEngine\IManager;
use OCP\WorkflowEngine\IOperation;
class LogContext {
/** @var array */
protected $details;
public function setDescription(string $description): LogContext {
$this->details['message'] = $description;
return $this;
}
public function setScopes(array $scopes): LogContext {
$this->details['scopes'] = [];
foreach ($scopes as $scope) {
if ($scope instanceof ScopeContext) {
switch ($scope->getScope()) {
case IManager::SCOPE_ADMIN:
$this->details['scopes'][] = ['scope' => 'admin'];
break;
case IManager::SCOPE_USER:
$this->details['scopes'][] = [
'scope' => 'user',
'uid' => $scope->getScopeId(),
];
break;
default:
continue 2;
}
}
}
return $this;
}
public function setOperation(?IOperation $operation): LogContext {
if ($operation instanceof IOperation) {
$this->details['operation'] = [
'class' => get_class($operation),
'name' => $operation->getDisplayName(),
];
}
return $this;
}
public function setEntity(?IEntity $entity): LogContext {
if ($entity instanceof IEntity) {
$this->details['entity'] = [
'class' => get_class($entity),
'name' => $entity->getName(),
];
}
return $this;
}
public function setConfiguration(array $configuration): LogContext {
$this->details['configuration'] = $configuration;
return $this;
}
public function setEventName(string $eventName): LogContext {
$this->details['eventName'] = $eventName;
return $this;
}
public function getDetails(): array {
return $this->details;
}
}
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Helper;
use OCP\WorkflowEngine\IManager;
class ScopeContext {
/** @var int */
private $scope;
/** @var string */
private $scopeId;
/** @var string */
private $hash;
public function __construct(int $scope, string $scopeId = null) {
$this->scope = $this->evaluateScope($scope);
$this->scopeId = $this->evaluateScopeId($scopeId);
}
private function evaluateScope(int $scope): int {
if (in_array($scope, [IManager::SCOPE_ADMIN, IManager::SCOPE_USER], true)) {
return $scope;
}
throw new \InvalidArgumentException('Invalid scope');
}
private function evaluateScopeId(string $scopeId = null): string {
if ($this->scope === IManager::SCOPE_USER
&& trim((string)$scopeId) === '') {
throw new \InvalidArgumentException('user scope requires a user id');
}
return trim((string)$scopeId);
}
/**
* @return int
*/
public function getScope(): int {
return $this->scope;
}
/**
* @return string
*/
public function getScopeId(): string {
return $this->scopeId;
}
public function getHash(): string {
if ($this->hash === null) {
$this->hash = \hash('sha256', $this->getScope() . '::' . $this->getScopeId());
}
return $this->hash;
}
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/**
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author François Freitag <mail@franek.fr>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Listener;
use OCA\WorkflowEngine\AppInfo\Application;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Template;
use OCP\Util;
use function class_exists;
use function function_exists;
class LoadAdditionalSettingsScriptsListener implements IEventListener {
public function handle(Event $event): void {
if (!function_exists('style')) {
// This is hacky, but we need to load the template class
class_exists(Template::class, true);
}
Util::addScript('core', 'files_fileinfo');
Util::addScript('core', 'files_client');
Util::addScript('core', 'systemtags');
Util::addScript(Application::APP_ID, 'workflowengine');
}
}
@@ -0,0 +1,737 @@
<?php
/**
* @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author blizzz <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine;
use Doctrine\DBAL\Exception;
use OCA\WorkflowEngine\AppInfo\Application;
use OCA\WorkflowEngine\Check\FileMimeType;
use OCA\WorkflowEngine\Check\FileName;
use OCA\WorkflowEngine\Check\FileSize;
use OCA\WorkflowEngine\Check\FileSystemTags;
use OCA\WorkflowEngine\Check\RequestRemoteAddress;
use OCA\WorkflowEngine\Check\RequestTime;
use OCA\WorkflowEngine\Check\RequestURL;
use OCA\WorkflowEngine\Check\RequestUserAgent;
use OCA\WorkflowEngine\Check\UserGroupMembership;
use OCA\WorkflowEngine\Entity\File;
use OCA\WorkflowEngine\Helper\ScopeContext;
use OCA\WorkflowEngine\Service\Logger;
use OCA\WorkflowEngine\Service\RuleMatcher;
use OCP\AppFramework\QueryException;
use OCP\Cache\CappedMemoryCache;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IL10N;
use OCP\IServerContainer;
use OCP\IUserSession;
use OCP\WorkflowEngine\Events\RegisterChecksEvent;
use OCP\WorkflowEngine\Events\RegisterEntitiesEvent;
use OCP\WorkflowEngine\Events\RegisterOperationsEvent;
use OCP\WorkflowEngine\ICheck;
use OCP\WorkflowEngine\IComplexOperation;
use OCP\WorkflowEngine\IEntity;
use OCP\WorkflowEngine\IEntityEvent;
use OCP\WorkflowEngine\IManager;
use OCP\WorkflowEngine\IOperation;
use OCP\WorkflowEngine\IRuleMatcher;
use Psr\Log\LoggerInterface;
class Manager implements IManager {
/** @var array[] */
protected $operations = [];
/** @var array[] */
protected $checks = [];
/** @var IEntity[] */
protected $registeredEntities = [];
/** @var IOperation[] */
protected $registeredOperators = [];
/** @var ICheck[] */
protected $registeredChecks = [];
/** @var CappedMemoryCache<int[]> */
protected CappedMemoryCache $operationsByScope;
public function __construct(
protected IDBConnection $connection,
protected IServerContainer $container,
protected IL10N $l,
protected LoggerInterface $logger,
protected IUserSession $session,
private IEventDispatcher $dispatcher,
private IConfig $config,
private ICacheFactory $cacheFactory,
) {
$this->operationsByScope = new CappedMemoryCache(64);
}
public function getRuleMatcher(): IRuleMatcher {
return new RuleMatcher(
$this->session,
$this->container,
$this->l,
$this,
$this->container->query(Logger::class)
);
}
public function getAllConfiguredEvents() {
$cache = $this->cacheFactory->createDistributed('flow');
$cached = $cache->get('events');
if ($cached !== null) {
return $cached;
}
$query = $this->connection->getQueryBuilder();
$query->select('class', 'entity')
->selectAlias($query->expr()->castColumn('events', IQueryBuilder::PARAM_STR), 'events')
->from('flow_operations')
->where($query->expr()->neq('events', $query->createNamedParameter('[]'), IQueryBuilder::PARAM_STR))
->groupBy('class', 'entity', $query->expr()->castColumn('events', IQueryBuilder::PARAM_STR));
$result = $query->execute();
$operations = [];
while ($row = $result->fetch()) {
$eventNames = \json_decode($row['events']);
$operation = $row['class'];
$entity = $row['entity'];
$operations[$operation] = $operations[$row['class']] ?? [];
$operations[$operation][$entity] = $operations[$operation][$entity] ?? [];
$operations[$operation][$entity] = array_unique(array_merge($operations[$operation][$entity], $eventNames ?? []));
}
$result->closeCursor();
$cache->set('events', $operations, 3600);
return $operations;
}
/**
* @param string $operationClass
* @return ScopeContext[]
*/
public function getAllConfiguredScopesForOperation(string $operationClass): array {
static $scopesByOperation = [];
if (isset($scopesByOperation[$operationClass])) {
return $scopesByOperation[$operationClass];
}
try {
/** @var IOperation $operation */
$operation = $this->container->query($operationClass);
} catch (QueryException $e) {
return [];
}
$query = $this->connection->getQueryBuilder();
$query->selectDistinct('s.type')
->addSelect('s.value')
->from('flow_operations', 'o')
->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id'))
->where($query->expr()->eq('o.class', $query->createParameter('operationClass')));
$query->setParameters(['operationClass' => $operationClass]);
$result = $query->execute();
$scopesByOperation[$operationClass] = [];
while ($row = $result->fetch()) {
$scope = new ScopeContext($row['type'], $row['value']);
if (!$operation->isAvailableForScope((int) $row['type'])) {
continue;
}
$scopesByOperation[$operationClass][$scope->getHash()] = $scope;
}
return $scopesByOperation[$operationClass];
}
public function getAllOperations(ScopeContext $scopeContext): array {
if (isset($this->operations[$scopeContext->getHash()])) {
return $this->operations[$scopeContext->getHash()];
}
$query = $this->connection->getQueryBuilder();
$query->select('o.*')
->selectAlias('s.type', 'scope_type')
->selectAlias('s.value', 'scope_actor_id')
->from('flow_operations', 'o')
->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id'))
->where($query->expr()->eq('s.type', $query->createParameter('scope')));
if ($scopeContext->getScope() === IManager::SCOPE_USER) {
$query->andWhere($query->expr()->eq('s.value', $query->createParameter('scopeId')));
}
$query->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
$result = $query->execute();
$this->operations[$scopeContext->getHash()] = [];
while ($row = $result->fetch()) {
try {
/** @var IOperation $operation */
$operation = $this->container->query($row['class']);
} catch (QueryException $e) {
continue;
}
if (!$operation->isAvailableForScope((int) $row['scope_type'])) {
continue;
}
if (!isset($this->operations[$scopeContext->getHash()][$row['class']])) {
$this->operations[$scopeContext->getHash()][$row['class']] = [];
}
$this->operations[$scopeContext->getHash()][$row['class']][] = $row;
}
return $this->operations[$scopeContext->getHash()];
}
public function getOperations(string $class, ScopeContext $scopeContext): array {
if (!isset($this->operations[$scopeContext->getHash()])) {
$this->getAllOperations($scopeContext);
}
return $this->operations[$scopeContext->getHash()][$class] ?? [];
}
/**
* @param int $id
* @return array
* @throws \UnexpectedValueException
*/
protected function getOperation($id) {
$query = $this->connection->getQueryBuilder();
$query->select('*')
->from('flow_operations')
->where($query->expr()->eq('id', $query->createNamedParameter($id)));
$result = $query->execute();
$row = $result->fetch();
$result->closeCursor();
if ($row) {
return $row;
}
throw new \UnexpectedValueException($this->l->t('Operation #%s does not exist', [$id]));
}
protected function insertOperation(
string $class,
string $name,
array $checkIds,
string $operation,
string $entity,
array $events
): int {
$query = $this->connection->getQueryBuilder();
$query->insert('flow_operations')
->values([
'class' => $query->createNamedParameter($class),
'name' => $query->createNamedParameter($name),
'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))),
'operation' => $query->createNamedParameter($operation),
'entity' => $query->createNamedParameter($entity),
'events' => $query->createNamedParameter(json_encode($events))
]);
$query->execute();
$this->cacheFactory->createDistributed('flow')->remove('events');
return $query->getLastInsertId();
}
/**
* @param string $class
* @param string $name
* @param array[] $checks
* @param string $operation
* @return array The added operation
* @throws \UnexpectedValueException
* @throw Exception
*/
public function addOperation(
string $class,
string $name,
array $checks,
string $operation,
ScopeContext $scope,
string $entity,
array $events
) {
$this->validateOperation($class, $name, $checks, $operation, $scope, $entity, $events);
$this->connection->beginTransaction();
try {
$checkIds = [];
foreach ($checks as $check) {
$checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
}
$id = $this->insertOperation($class, $name, $checkIds, $operation, $entity, $events);
$this->addScope($id, $scope);
$this->connection->commit();
} catch (Exception $e) {
$this->connection->rollBack();
throw $e;
}
return $this->getOperation($id);
}
protected function canModify(int $id, ScopeContext $scopeContext):bool {
if (isset($this->operationsByScope[$scopeContext->getHash()])) {
return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
}
$qb = $this->connection->getQueryBuilder();
$qb = $qb->select('o.id')
->from('flow_operations', 'o')
->leftJoin('o', 'flow_operations_scope', 's', $qb->expr()->eq('o.id', 's.operation_id'))
->where($qb->expr()->eq('s.type', $qb->createParameter('scope')));
if ($scopeContext->getScope() !== IManager::SCOPE_ADMIN) {
$qb->andWhere($qb->expr()->eq('s.value', $qb->createParameter('scopeId')));
}
$qb->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
$result = $qb->execute();
$operations = [];
while (($opId = $result->fetchOne()) !== false) {
$operations[] = (int)$opId;
}
$this->operationsByScope[$scopeContext->getHash()] = $operations;
$result->closeCursor();
return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
}
/**
* @param int $id
* @param string $name
* @param array[] $checks
* @param string $operation
* @return array The updated operation
* @throws \UnexpectedValueException
* @throws \DomainException
* @throws Exception
*/
public function updateOperation(
int $id,
string $name,
array $checks,
string $operation,
ScopeContext $scopeContext,
string $entity,
array $events
): array {
if (!$this->canModify($id, $scopeContext)) {
throw new \DomainException('Target operation not within scope');
};
$row = $this->getOperation($id);
$this->validateOperation($row['class'], $name, $checks, $operation, $scopeContext, $entity, $events);
$checkIds = [];
try {
$this->connection->beginTransaction();
foreach ($checks as $check) {
$checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
}
$query = $this->connection->getQueryBuilder();
$query->update('flow_operations')
->set('name', $query->createNamedParameter($name))
->set('checks', $query->createNamedParameter(json_encode(array_unique($checkIds))))
->set('operation', $query->createNamedParameter($operation))
->set('entity', $query->createNamedParameter($entity))
->set('events', $query->createNamedParameter(json_encode($events)))
->where($query->expr()->eq('id', $query->createNamedParameter($id)));
$query->execute();
$this->connection->commit();
} catch (Exception $e) {
$this->connection->rollBack();
throw $e;
}
unset($this->operations[$scopeContext->getHash()]);
$this->cacheFactory->createDistributed('flow')->remove('events');
return $this->getOperation($id);
}
/**
* @param int $id
* @return bool
* @throws \UnexpectedValueException
* @throws Exception
* @throws \DomainException
*/
public function deleteOperation($id, ScopeContext $scopeContext) {
if (!$this->canModify($id, $scopeContext)) {
throw new \DomainException('Target operation not within scope');
};
$query = $this->connection->getQueryBuilder();
try {
$this->connection->beginTransaction();
$result = (bool)$query->delete('flow_operations')
->where($query->expr()->eq('id', $query->createNamedParameter($id)))
->execute();
if ($result) {
$qb = $this->connection->getQueryBuilder();
$result &= (bool)$qb->delete('flow_operations_scope')
->where($qb->expr()->eq('operation_id', $qb->createNamedParameter($id)))
->execute();
}
$this->connection->commit();
} catch (Exception $e) {
$this->connection->rollBack();
throw $e;
}
if (isset($this->operations[$scopeContext->getHash()])) {
unset($this->operations[$scopeContext->getHash()]);
}
$this->cacheFactory->createDistributed('flow')->remove('events');
return $result;
}
protected function validateEvents(string $entity, array $events, IOperation $operation) {
try {
/** @var IEntity $instance */
$instance = $this->container->query($entity);
} catch (QueryException $e) {
throw new \UnexpectedValueException($this->l->t('Entity %s does not exist', [$entity]));
}
if (!$instance instanceof IEntity) {
throw new \UnexpectedValueException($this->l->t('Entity %s is invalid', [$entity]));
}
if (empty($events)) {
if (!$operation instanceof IComplexOperation) {
throw new \UnexpectedValueException($this->l->t('No events are chosen.'));
}
return;
}
$availableEvents = [];
foreach ($instance->getEvents() as $event) {
/** @var IEntityEvent $event */
$availableEvents[] = $event->getEventName();
}
$diff = array_diff($events, $availableEvents);
if (!empty($diff)) {
throw new \UnexpectedValueException($this->l->t('Entity %s has no event %s', [$entity, array_shift($diff)]));
}
}
/**
* @param string $class
* @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, ScopeContext $scope, string $entity, array $events) {
try {
/** @var IOperation $instance */
$instance = $this->container->query($class);
} catch (QueryException $e) {
throw new \UnexpectedValueException($this->l->t('Operation %s does not exist', [$class]));
}
if (!($instance instanceof IOperation)) {
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) {
throw new \UnexpectedValueException($this->l->t('At least one check needs to be provided'));
}
if (strlen((string)$operation) > IManager::MAX_OPERATION_VALUE_BYTES) {
throw new \UnexpectedValueException($this->l->t('The provided operation data is too long'));
}
$instance->validateOperation($name, $checks, $operation);
foreach ($checks as $check) {
if (!is_string($check['class'])) {
throw new \UnexpectedValueException($this->l->t('Invalid check provided'));
}
try {
/** @var ICheck $instance */
$instance = $this->container->query($check['class']);
} catch (QueryException $e) {
throw new \UnexpectedValueException($this->l->t('Check %s does not exist', [$class]));
}
if (!($instance instanceof ICheck)) {
throw new \UnexpectedValueException($this->l->t('Check %s is invalid', [$class]));
}
if (!empty($instance->supportedEntities())
&& !in_array($entity, $instance->supportedEntities())
) {
throw new \UnexpectedValueException($this->l->t('Check %s is not allowed with this entity', [$class]));
}
if (strlen((string)$check['value']) > IManager::MAX_CHECK_VALUE_BYTES) {
throw new \UnexpectedValueException($this->l->t('The provided check value is too long'));
}
$instance->validateCheck($check['operator'], $check['value']);
}
}
/**
* @param int[] $checkIds
* @return array[]
*/
public function getChecks(array $checkIds) {
$checkIds = array_map('intval', $checkIds);
$checks = [];
foreach ($checkIds as $i => $checkId) {
if (isset($this->checks[$checkId])) {
$checks[$checkId] = $this->checks[$checkId];
unset($checkIds[$i]);
}
}
if (empty($checkIds)) {
return $checks;
}
$query = $this->connection->getQueryBuilder();
$query->select('*')
->from('flow_checks')
->where($query->expr()->in('id', $query->createNamedParameter($checkIds, IQueryBuilder::PARAM_INT_ARRAY)));
$result = $query->execute();
while ($row = $result->fetch()) {
$this->checks[(int) $row['id']] = $row;
$checks[(int) $row['id']] = $row;
}
$result->closeCursor();
$checkIds = array_diff($checkIds, array_keys($checks));
if (!empty($checkIds)) {
$missingCheck = array_pop($checkIds);
throw new \UnexpectedValueException($this->l->t('Check #%s does not exist', $missingCheck));
}
return $checks;
}
/**
* @param string $class
* @param string $operator
* @param string $value
* @return int Check unique ID
*/
protected function addCheck($class, $operator, $value) {
$hash = md5($class . '::' . $operator . '::' . $value);
$query = $this->connection->getQueryBuilder();
$query->select('id')
->from('flow_checks')
->where($query->expr()->eq('hash', $query->createNamedParameter($hash)));
$result = $query->execute();
if ($row = $result->fetch()) {
$result->closeCursor();
return (int) $row['id'];
}
$query = $this->connection->getQueryBuilder();
$query->insert('flow_checks')
->values([
'class' => $query->createNamedParameter($class),
'operator' => $query->createNamedParameter($operator),
'value' => $query->createNamedParameter($value),
'hash' => $query->createNamedParameter($hash),
]);
$query->execute();
return $query->getLastInsertId();
}
protected function addScope(int $operationId, ScopeContext $scope): void {
$query = $this->connection->getQueryBuilder();
$insertQuery = $query->insert('flow_operations_scope');
$insertQuery->values([
'operation_id' => $query->createNamedParameter($operationId),
'type' => $query->createNamedParameter($scope->getScope()),
'value' => $query->createNamedParameter($scope->getScopeId()),
]);
$insertQuery->execute();
}
public function formatOperation(array $operation): array {
$checkIds = json_decode($operation['checks'], true);
$checks = $this->getChecks($checkIds);
$operation['checks'] = [];
foreach ($checks as $check) {
// Remove internal values
unset($check['id']);
unset($check['hash']);
$operation['checks'][] = $check;
}
$operation['events'] = json_decode($operation['events'], true) ?? [];
return $operation;
}
/**
* @return IEntity[]
*/
public function getEntitiesList(): array {
$this->dispatcher->dispatchTyped(new RegisterEntitiesEvent($this));
return array_values(array_merge($this->getBuildInEntities(), $this->registeredEntities));
}
/**
* @return IOperation[]
*/
public function getOperatorList(): array {
$this->dispatcher->dispatchTyped(new RegisterOperationsEvent($this));
return array_merge($this->getBuildInOperators(), $this->registeredOperators);
}
/**
* @return ICheck[]
*/
public function getCheckList(): array {
$this->dispatcher->dispatchTyped(new RegisterChecksEvent($this));
return array_merge($this->getBuildInChecks(), $this->registeredChecks);
}
public function registerEntity(IEntity $entity): void {
$this->registeredEntities[get_class($entity)] = $entity;
}
public function registerOperation(IOperation $operator): void {
$this->registeredOperators[get_class($operator)] = $operator;
}
public function registerCheck(ICheck $check): void {
$this->registeredChecks[get_class($check)] = $check;
}
/**
* @return IEntity[]
*/
protected function getBuildInEntities(): array {
try {
return [
File::class => $this->container->query(File::class),
];
} catch (QueryException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
return [];
}
}
/**
* @return IOperation[]
*/
protected function getBuildInOperators(): array {
try {
return [
// None yet
];
} catch (QueryException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
return [];
}
}
/**
* @return ICheck[]
*/
protected function getBuildInChecks(): array {
try {
return [
$this->container->query(FileMimeType::class),
$this->container->query(FileName::class),
$this->container->query(FileSize::class),
$this->container->query(FileSystemTags::class),
$this->container->query(RequestRemoteAddress::class),
$this->container->query(RequestTime::class),
$this->container->query(RequestURL::class),
$this->container->query(RequestUserAgent::class),
$this->container->query(UserGroupMembership::class),
];
} catch (QueryException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
return [];
}
}
public function isUserScopeEnabled(): bool {
return $this->config->getAppValue(Application::APP_ID, 'user_scope_disabled', 'no') === 'no';
}
}
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Migration;
use OCP\DB\IResult;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
use OCP\WorkflowEngine\IManager;
class PopulateNewlyIntroducedDatabaseFields implements IRepairStep {
/** @var IDBConnection */
private $dbc;
public function __construct(IDBConnection $dbc) {
$this->dbc = $dbc;
}
public function getName() {
return 'Populating added database structures for workflows';
}
public function run(IOutput $output) {
$result = $this->getIdsWithoutScope();
$this->populateScopeTable($result);
$result->closeCursor();
}
protected function populateScopeTable(IResult $ids): void {
$qb = $this->dbc->getQueryBuilder();
$insertQuery = $qb->insert('flow_operations_scope');
while (($id = $ids->fetchOne()) !== false) {
$insertQuery->values(['operation_id' => $qb->createNamedParameter($id), 'type' => IManager::SCOPE_ADMIN]);
$insertQuery->execute();
}
}
protected function getIdsWithoutScope(): IResult {
$qb = $this->dbc->getQueryBuilder();
$selectQuery = $qb->select('o.id')
->from('flow_operations', 'o')
->leftJoin('o', 'flow_operations_scope', 's', $qb->expr()->eq('o.id', 's.operation_id'))
->where($qb->expr()->isNull('s.operation_id'));
// The left join operation is not necessary, usually, but it's a safe-guard
// in case the repair step is executed multiple times for whatever reason.
/** @var IResult $result */
$result = $selectQuery->execute();
return $result;
}
}
@@ -0,0 +1,155 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Migration;
use Closure;
use Doctrine\DBAL\Schema\Table;
use OCA\WorkflowEngine\Entity\File;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version2000Date20190808074233 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if (!$schema->hasTable('flow_checks')) {
$table = $schema->createTable('flow_checks');
$table->addColumn('id', Types::INTEGER, [
'autoincrement' => true,
'notnull' => true,
'length' => 4,
]);
$table->addColumn('class', Types::STRING, [
'notnull' => true,
'length' => 256,
'default' => '',
]);
$table->addColumn('operator', Types::STRING, [
'notnull' => true,
'length' => 16,
'default' => '',
]);
$table->addColumn('value', Types::TEXT, [
'notnull' => false,
]);
$table->addColumn('hash', Types::STRING, [
'notnull' => true,
'length' => 32,
'default' => '',
]);
$table->setPrimaryKey(['id']);
$table->addUniqueIndex(['hash'], 'flow_unique_hash');
}
if (!$schema->hasTable('flow_operations')) {
$table = $schema->createTable('flow_operations');
$table->addColumn('id', Types::INTEGER, [
'autoincrement' => true,
'notnull' => true,
'length' => 4,
]);
$table->addColumn('class', Types::STRING, [
'notnull' => true,
'length' => 256,
'default' => '',
]);
$table->addColumn('name', Types::STRING, [
'notnull' => false,
'length' => 256,
'default' => '',
]);
$table->addColumn('checks', Types::TEXT, [
'notnull' => false,
]);
$table->addColumn('operation', Types::TEXT, [
'notnull' => false,
]);
$this->ensureEntityColumns($table);
$table->setPrimaryKey(['id']);
} else {
$table = $schema->getTable('flow_operations');
$this->ensureEntityColumns($table);
}
if (!$schema->hasTable('flow_operations_scope')) {
$table = $schema->createTable('flow_operations_scope');
$table->addColumn('id', Types::BIGINT, [
'autoincrement' => true,
'notnull' => true,
'length' => 4,
]);
$table->addColumn('operation_id', Types::INTEGER, [
'notnull' => true,
'length' => 4,
'default' => 0,
]);
$table->addColumn('type', Types::INTEGER, [
'notnull' => true,
'length' => 4,
'default' => 0,
]);
$table->addColumn('value', Types::STRING, [
'notnull' => false,
'length' => 64,
'default' => '',
]);
$table->setPrimaryKey(['id']);
$table->addUniqueIndex(['operation_id', 'type', 'value'], 'flow_unique_scope');
}
return $schema;
}
protected function ensureEntityColumns(Table $table) {
if (!$table->hasColumn('entity')) {
$table->addColumn('entity', Types::STRING, [
'notnull' => true,
'length' => 256,
'default' => File::class,
]);
}
if (!$table->hasColumn('events')) {
$table->addColumn('events', Types::TEXT, [
'notnull' => true,
'default' => '[]',
]);
}
}
}
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2021 Vincent Petry <vincent@nextcloud.com>
*
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version2200Date20210805101925 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if ($schema->hasTable('flow_operations')) {
$table = $schema->getTable('flow_operations');
$table->changeColumn('name', [
'notnull' => false,
]);
}
return $schema;
}
}
@@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Service;
use OCA\WorkflowEngine\AppInfo\Application;
use OCA\WorkflowEngine\Helper\LogContext;
use OCP\IConfig;
use OCP\ILogger;
use OCP\Log\IDataLogger;
use OCP\Log\ILogFactory;
use Psr\Log\LoggerInterface;
class Logger {
protected ?LoggerInterface $flowLogger = null;
public function __construct(
protected LoggerInterface $generalLogger,
private IConfig $config,
private ILogFactory $logFactory,
) {
$this->initLogger();
}
protected function initLogger(): void {
$default = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/flow.log';
$logFile = trim((string)$this->config->getAppValue(Application::APP_ID, 'logfile', $default));
if ($logFile !== '') {
$this->flowLogger = $this->logFactory->getCustomPsrLogger($logFile);
}
}
public function logFlowRequests(LogContext $logContext) {
$message = 'Flow activation: rules were requested for operation {op}';
$context = ['op' => $logContext->getDetails()['operation']['name'], 'level' => ILogger::DEBUG];
$logContext->setDescription('Flow activation: rules were requested');
$this->log($message, $context, $logContext);
}
public function logScopeExpansion(LogContext $logContext) {
$message = 'Flow rule of a different user is legit for operation {op}';
$context = ['op' => $logContext->getDetails()['operation']['name']];
$logContext->setDescription('Flow rule of a different user is legit');
$this->log($message, $context, $logContext);
}
public function logPassedCheck(LogContext $logContext) {
$message = 'Flow rule qualified to run {op}, config: {config}';
$context = [
'op' => $logContext->getDetails()['operation']['name'],
'config' => $logContext->getDetails()['configuration'],
'level' => ILogger::DEBUG,
];
$logContext->setDescription('Flow rule qualified to run');
$this->log($message, $context, $logContext);
}
public function logRunSingle(LogContext $logContext) {
$message = 'Last qualified flow configuration is going to run {op}';
$context = [
'op' => $logContext->getDetails()['operation']['name'],
];
$logContext->setDescription('Last qualified flow configuration is going to run');
$this->log($message, $context, $logContext);
}
public function logRunAll(LogContext $logContext) {
$message = 'All qualified flow configurations are going to run {op}';
$context = [
'op' => $logContext->getDetails()['operation']['name'],
];
$logContext->setDescription('All qualified flow configurations are going to run');
$this->log($message, $context, $logContext);
}
public function logRunNone(LogContext $logContext) {
$message = 'No flow configurations is going to run {op}';
$context = [
'op' => $logContext->getDetails()['operation']['name'],
'level' => ILogger::DEBUG,
];
$logContext->setDescription('No flow configurations is going to run');
$this->log($message, $context, $logContext);
}
public function logEventInit(LogContext $logContext) {
$message = 'Flow activated by event {ev}';
$context = [
'ev' => $logContext->getDetails()['eventName'],
'level' => ILogger::DEBUG,
];
$logContext->setDescription('Flow activated by event');
$this->log($message, $context, $logContext);
}
public function logEventDone(LogContext $logContext) {
$message = 'Flow handling done for event {ev}';
$context = [
'ev' => $logContext->getDetails()['eventName'],
];
$logContext->setDescription('Flow handling for event done');
$this->log($message, $context, $logContext);
}
protected function log(
string $message,
array $context,
LogContext $logContext
): void {
if (!isset($context['app'])) {
$context['app'] = Application::APP_ID;
}
if (!isset($context['level'])) {
$context['level'] = ILogger::INFO;
}
$this->generalLogger->log($context['level'], $message, $context);
if (!$this->flowLogger instanceof IDataLogger) {
return;
}
$details = $logContext->getDetails();
$this->flowLogger->logData(
$details['message'],
$details,
['app' => Application::APP_ID, 'level' => $context['level']]
);
}
}
@@ -0,0 +1,247 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Service;
use OCA\WorkflowEngine\Helper\LogContext;
use OCA\WorkflowEngine\Helper\ScopeContext;
use OCA\WorkflowEngine\Manager;
use OCP\AppFramework\QueryException;
use OCP\Files\Storage\IStorage;
use OCP\IL10N;
use OCP\IServerContainer;
use OCP\IUserSession;
use OCP\WorkflowEngine\ICheck;
use OCP\WorkflowEngine\IEntity;
use OCP\WorkflowEngine\IEntityCheck;
use OCP\WorkflowEngine\IFileCheck;
use OCP\WorkflowEngine\IManager;
use OCP\WorkflowEngine\IOperation;
use OCP\WorkflowEngine\IRuleMatcher;
use RuntimeException;
class RuleMatcher implements IRuleMatcher {
/** @var IUserSession */
protected $session;
/** @var IManager */
protected $manager;
/** @var array */
protected $contexts;
/** @var IServerContainer */
protected $container;
/** @var array */
protected $fileInfo = [];
/** @var IL10N */
protected $l;
/** @var IOperation */
protected $operation;
/** @var IEntity */
protected $entity;
/** @var Logger */
protected $logger;
/** @var string */
protected $eventName;
public function __construct(
IUserSession $session,
IServerContainer $container,
IL10N $l,
Manager $manager,
Logger $logger
) {
$this->session = $session;
$this->manager = $manager;
$this->container = $container;
$this->l = $l;
$this->logger = $logger;
}
public function setFileInfo(IStorage $storage, string $path, bool $isDir = false): void {
$this->fileInfo['storage'] = $storage;
$this->fileInfo['path'] = $path;
$this->fileInfo['isDir'] = $isDir;
}
public function setEntitySubject(IEntity $entity, $subject): void {
$this->contexts[get_class($entity)] = [$entity, $subject];
}
public function setOperation(IOperation $operation): void {
if ($this->operation !== null) {
throw new RuntimeException('This method must not be called more than once');
}
$this->operation = $operation;
}
public function setEntity(IEntity $entity): void {
if ($this->entity !== null) {
throw new RuntimeException('This method must not be called more than once');
}
$this->entity = $entity;
}
public function setEventName(string $eventName): void {
if ($this->eventName !== null) {
throw new RuntimeException('This method must not be called more than once');
}
$this->eventName = $eventName;
}
public function getEntity(): IEntity {
if ($this->entity === null) {
throw new \LogicException('Entity was not set yet');
}
return $this->entity;
}
public function getFlows(bool $returnFirstMatchingOperationOnly = true): array {
if (!$this->operation) {
throw new RuntimeException('Operation is not set');
}
return $this->getMatchingOperations(get_class($this->operation), $returnFirstMatchingOperationOnly);
}
public function getMatchingOperations(string $class, bool $returnFirstMatchingOperationOnly = true): array {
$scopes[] = new ScopeContext(IManager::SCOPE_ADMIN);
$user = $this->session->getUser();
if ($user !== null && $this->manager->isUserScopeEnabled()) {
$scopes[] = new ScopeContext(IManager::SCOPE_USER, $user->getUID());
}
$ctx = new LogContext();
$ctx
->setScopes($scopes)
->setEntity($this->entity)
->setOperation($this->operation);
$this->logger->logFlowRequests($ctx);
$operations = [];
foreach ($scopes as $scope) {
$operations = array_merge($operations, $this->manager->getOperations($class, $scope));
}
if ($this->entity instanceof IEntity) {
/** @var ScopeContext[] $additionalScopes */
$additionalScopes = $this->manager->getAllConfiguredScopesForOperation($class);
foreach ($additionalScopes as $hash => $scopeCandidate) {
if ($scopeCandidate->getScope() !== IManager::SCOPE_USER || in_array($scopeCandidate, $scopes)) {
continue;
}
if ($this->entity->isLegitimatedForUserId($scopeCandidate->getScopeId())) {
$ctx = new LogContext();
$ctx
->setScopes([$scopeCandidate])
->setEntity($this->entity)
->setOperation($this->operation);
$this->logger->logScopeExpansion($ctx);
$operations = array_merge($operations, $this->manager->getOperations($class, $scopeCandidate));
}
}
}
$matches = [];
foreach ($operations as $operation) {
$configuredEvents = json_decode($operation['events'], true);
if ($this->eventName !== null && !in_array($this->eventName, $configuredEvents)) {
continue;
}
$checkIds = json_decode($operation['checks'], true);
$checks = $this->manager->getChecks($checkIds);
foreach ($checks as $check) {
if (!$this->check($check)) {
// Check did not match, continue with the next operation
continue 2;
}
}
$ctx = new LogContext();
$ctx
->setEntity($this->entity)
->setOperation($this->operation)
->setConfiguration($operation);
$this->logger->logPassedCheck($ctx);
if ($returnFirstMatchingOperationOnly) {
$ctx = new LogContext();
$ctx
->setEntity($this->entity)
->setOperation($this->operation)
->setConfiguration($operation);
$this->logger->logRunSingle($ctx);
return $operation;
}
$matches[] = $operation;
}
$ctx = new LogContext();
$ctx
->setEntity($this->entity)
->setOperation($this->operation);
if (!empty($matches)) {
$ctx->setConfiguration($matches);
$this->logger->logRunAll($ctx);
} else {
$this->logger->logRunNone($ctx);
}
return $matches;
}
/**
* @param array $check
* @return bool
*/
public function check(array $check) {
try {
$checkInstance = $this->container->query($check['class']);
} catch (QueryException $e) {
// Check does not exist, assume it matches.
return true;
}
if ($checkInstance instanceof IFileCheck) {
if (empty($this->fileInfo)) {
throw new RuntimeException('Must set file info before running the check');
}
$checkInstance->setFileInfo($this->fileInfo['storage'], $this->fileInfo['path'], $this->fileInfo['isDir']);
} elseif ($checkInstance instanceof IEntityCheck) {
foreach ($this->contexts as $entityInfo) {
[$entity, $subject] = $entityInfo;
$checkInstance->setEntitySubject($entity, $subject);
}
} elseif (!$checkInstance instanceof ICheck) {
// Check is invalid
throw new \UnexpectedValueException($this->l->t('Check %s is invalid or does not exist', $check['class']));
}
return $checkInstance->executeCheck($check['operator'], $check['value']);
}
}
@@ -0,0 +1,192 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Julius Härtl <jus@bitgrid.net>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Settings;
use OCA\WorkflowEngine\AppInfo\Application;
use OCA\WorkflowEngine\Manager;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Settings\ISettings;
use OCP\WorkflowEngine\Events\LoadSettingsScriptsEvent;
use OCP\WorkflowEngine\ICheck;
use OCP\WorkflowEngine\IComplexOperation;
use OCP\WorkflowEngine\IEntity;
use OCP\WorkflowEngine\IEntityEvent;
use OCP\WorkflowEngine\IOperation;
use OCP\WorkflowEngine\ISpecificOperation;
abstract class ASettings implements ISettings {
private IL10N $l10n;
private string $appName;
private IEventDispatcher $eventDispatcher;
protected Manager $manager;
private IInitialState $initialStateService;
private IConfig $config;
private IURLGenerator $urlGenerator;
public function __construct(
string $appName,
IL10N $l,
IEventDispatcher $eventDispatcher,
Manager $manager,
IInitialState $initialStateService,
IConfig $config,
IURLGenerator $urlGenerator
) {
$this->appName = $appName;
$this->l10n = $l;
$this->eventDispatcher = $eventDispatcher;
$this->manager = $manager;
$this->initialStateService = $initialStateService;
$this->config = $config;
$this->urlGenerator = $urlGenerator;
}
abstract public function getScope(): int;
/**
* @return TemplateResponse
*/
public function getForm(): TemplateResponse {
// @deprecated in 20.0.0: retire this one in favor of the typed event
$this->eventDispatcher->dispatch(
'OCP\WorkflowEngine::loadAdditionalSettingScripts',
new LoadSettingsScriptsEvent()
);
$this->eventDispatcher->dispatchTyped(new LoadSettingsScriptsEvent());
$entities = $this->manager->getEntitiesList();
$this->initialStateService->provideInitialState(
'entities',
$this->entitiesToArray($entities)
);
$operators = $this->manager->getOperatorList();
$this->initialStateService->provideInitialState(
'operators',
$this->operatorsToArray($operators)
);
$checks = $this->manager->getCheckList();
$this->initialStateService->provideInitialState(
'checks',
$this->checksToArray($checks)
);
$this->initialStateService->provideInitialState(
'scope',
$this->getScope()
);
$this->initialStateService->provideInitialState(
'appstoreenabled',
$this->config->getSystemValueBool('appstoreenabled', true)
);
$this->initialStateService->provideInitialState(
'doc-url',
$this->urlGenerator->linkToDocs('admin-workflowengine')
);
return new TemplateResponse(Application::APP_ID, 'settings', [], 'blank');
}
/**
* @return string the section ID, e.g. 'sharing'
*/
public function getSection(): ?string {
return 'workflow';
}
/**
* @return int whether the form should be rather on the top or bottom of
* the admin section. The forms are arranged in ascending order of the
* priority values. It is required to return a value between 0 and 100.
*
* E.g.: 70
*/
public function getPriority(): int {
return 0;
}
private function entitiesToArray(array $entities) {
return array_map(function (IEntity $entity) {
$events = array_map(function (IEntityEvent $entityEvent) {
return [
'eventName' => $entityEvent->getEventName(),
'displayName' => $entityEvent->getDisplayName()
];
}, $entity->getEvents());
return [
'id' => get_class($entity),
'icon' => $entity->getIcon(),
'name' => $entity->getName(),
'events' => $events,
];
}, $entities);
}
private function operatorsToArray(array $operators) {
$operators = array_filter($operators, function (IOperation $operator) {
return $operator->isAvailableForScope($this->getScope());
});
return array_map(function (IOperation $operator) {
return [
'id' => get_class($operator),
'icon' => $operator->getIcon(),
'name' => $operator->getDisplayName(),
'description' => $operator->getDescription(),
'fixedEntity' => $operator instanceof ISpecificOperation ? $operator->getEntityId() : '',
'isComplex' => $operator instanceof IComplexOperation,
'triggerHint' => $operator instanceof IComplexOperation ? $operator->getTriggerHint() : '',
];
}, $operators);
}
private function checksToArray(array $checks) {
$checks = array_filter($checks, function (ICheck $check) {
return $check->isAvailableForScope($this->getScope());
});
return array_map(function (ICheck $check) {
return [
'id' => get_class($check),
'supportedEntities' => $check->supportedEntities(),
];
}, $checks);
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Settings;
use OCP\WorkflowEngine\IManager;
class Admin extends ASettings {
public function getScope(): int {
return IManager::SCOPE_ADMIN;
}
}
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Julius Härtl <jus@bitgrid.net>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Settings;
use OCP\WorkflowEngine\IManager;
class Personal extends ASettings {
public function getScope(): int {
return IManager::SCOPE_USER;
}
public function getSection(): ?string {
return $this->manager->isUserScopeEnabled() ? 'workflow' : null;
}
}
@@ -0,0 +1,75 @@
<?php
/**
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Jan-Christoph Borchardt <hey@jancborchardt.net>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\WorkflowEngine\Settings;
use OCA\WorkflowEngine\AppInfo\Application;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Settings\IIconSection;
class Section implements IIconSection {
/** @var IL10N */
private $l;
/** @var IURLGenerator */
private $url;
/**
* @param IURLGenerator $url
* @param IL10N $l
*/
public function __construct(IURLGenerator $url, IL10N $l) {
$this->url = $url;
$this->l = $l;
}
/**
* {@inheritdoc}
*/
public function getID() {
return 'workflow';
}
/**
* {@inheritdoc}
*/
public function getName() {
return $this->l->t('Flow');
}
/**
* {@inheritdoc}
*/
public function getPriority() {
return 55;
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return $this->url->imagePath(Application::APP_ID, 'app-dark.svg');
}
}