summaryrefslogtreecommitdiffstats
path: root/apps/workflowengine/lib
diff options
context:
space:
mode:
authorMorris Jobke <hey@morrisjobke.de>2016-07-26 11:16:34 +0200
committerMorris Jobke <hey@morrisjobke.de>2016-07-26 11:16:34 +0200
commit2f42a3fc319f4cffcae6db6b0874786f26fa4817 (patch)
tree080256d466720ae21b0ca1a0804ff15c9b6f3fea /apps/workflowengine/lib
parentcc5ddcf537d03c3f2f4cdc6817e02e098f8e8edb (diff)
downloadnextcloud-server-2f42a3fc319f4cffcae6db6b0874786f26fa4817.tar.gz
nextcloud-server-2f42a3fc319f4cffcae6db6b0874786f26fa4817.zip
Add workflowengine
Diffstat (limited to 'apps/workflowengine/lib')
-rw-r--r--apps/workflowengine/lib/AppInfo/Application.php62
-rw-r--r--apps/workflowengine/lib/Check/UserGroupMembership.php108
-rw-r--r--apps/workflowengine/lib/Controller/FlowOperations.php141
-rw-r--r--apps/workflowengine/lib/Manager.php306
4 files changed, 617 insertions, 0 deletions
diff --git a/apps/workflowengine/lib/AppInfo/Application.php b/apps/workflowengine/lib/AppInfo/Application.php
new file mode 100644
index 00000000000..c196ecd955c
--- /dev/null
+++ b/apps/workflowengine/lib/AppInfo/Application.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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\AppInfo;
+
+use OCP\Util;
+use OCP\WorkflowEngine\RegisterCheckEvent;
+
+class Application extends \OCP\AppFramework\App {
+
+ public function __construct() {
+ parent::__construct('workflowengine');
+
+ $this->getContainer()->registerAlias('FlowOperationsController', 'OCA\WorkflowEngine\Controller\FlowOperations');
+ }
+
+ /**
+ * Register all hooks and listeners
+ */
+ public function registerHooksAndListeners() {
+ $dispatcher = $this->getContainer()->getServer()->getEventDispatcher();
+ $dispatcher->addListener(
+ 'OCP\WorkflowEngine\RegisterCheckEvent',
+ function(RegisterCheckEvent $event) {
+ $event->addCheck(
+ 'OCA\WorkflowEngine\Check\UserGroupMembership',
+ 'User group membership',
+ ['is', '!is']
+ );
+ },
+ -100
+ );
+
+ $dispatcher->addListener(
+ 'OCP\WorkflowEngine::loadAdditionalSettingScripts',
+ function() {
+ Util::addStyle('workflowengine', 'admin');
+ Util::addScript('workflowengine', 'admin');
+ Util::addScript('workflowengine', 'usergroupmembershipplugin');
+ },
+ -100
+ );
+ }
+}
diff --git a/apps/workflowengine/lib/Check/UserGroupMembership.php b/apps/workflowengine/lib/Check/UserGroupMembership.php
new file mode 100644
index 00000000000..f437dbfc2d1
--- /dev/null
+++ b/apps/workflowengine/lib/Check/UserGroupMembership.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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\Files\Storage\IStorage;
+use OCP\IGroupManager;
+use OCP\IUser;
+use OCP\IUserSession;
+use OCP\WorkflowEngine\ICheck;
+
+class UserGroupMembership implements ICheck {
+
+ /** @var string */
+ protected $cachedUser;
+
+ /** @var string[] */
+ protected $cachedGroupMemberships;
+
+ /** @var IUserSession */
+ protected $userSession;
+
+ /** @var IGroupManager */
+ protected $groupManager;
+
+ /**
+ * @param IUserSession $userSession
+ * @param IGroupManager $groupManager
+ */
+ public function __construct(IUserSession $userSession, IGroupManager $groupManager) {
+ $this->userSession = $userSession;
+ $this->groupManager = $groupManager;
+ }
+
+ /**
+ * @param IStorage $storage
+ * @param string $path
+ */
+ public function setFileInfo(IStorage $storage, $path) {
+ // A different path doesn't change group memberships, so nothing to do here.
+ }
+
+ /**
+ * @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('Invalid operator', 1);
+ }
+
+ if (!$this->groupManager->groupExists($value)) {
+ throw new \UnexpectedValueException('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;
+ }
+}
diff --git a/apps/workflowengine/lib/Controller/FlowOperations.php b/apps/workflowengine/lib/Controller/FlowOperations.php
new file mode 100644
index 00000000000..e0836c727a2
--- /dev/null
+++ b/apps/workflowengine/lib/Controller/FlowOperations.php
@@ -0,0 +1,141 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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 OCA\WorkflowEngine\Manager;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\IRequest;
+use OCP\WorkflowEngine\RegisterCheckEvent;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+class FlowOperations extends Controller {
+
+ /** @var Manager */
+ protected $manager;
+
+ /** @var EventDispatcherInterface */
+ protected $dispatcher;
+
+ /**
+ * @param IRequest $request
+ * @param Manager $manager
+ * @param EventDispatcherInterface $dispatcher
+ */
+ public function __construct(IRequest $request, Manager $manager, EventDispatcherInterface $dispatcher) {
+ parent::__construct('workflowengine', $request);
+ $this->manager = $manager;
+ $this->dispatcher = $dispatcher;
+ }
+
+ /**
+ * @NoCSRFRequired
+ *
+ * @return JSONResponse
+ */
+ public function getChecks() {
+ $event = new RegisterCheckEvent();
+ $this->dispatcher->dispatch('OCP\WorkflowEngine\RegisterCheckEvent', $event);
+
+ return new JSONResponse($event->getChecks());
+ }
+
+ /**
+ * @NoCSRFRequired
+ *
+ * @param string $class
+ * @return JSONResponse
+ */
+ public function getOperations($class) {
+ $operations = $this->manager->getOperations($class);
+
+ foreach ($operations as &$operation) {
+ $operation = $this->prepareOperation($operation);
+ }
+
+ return new JSONResponse($operations);
+ }
+
+ /**
+ * @param string $class
+ * @param string $name
+ * @param array[] $checks
+ * @param string $operation
+ * @return JSONResponse The added element
+ */
+ public function addOperation($class, $name, $checks, $operation) {
+ try {
+ $operation = $this->manager->addOperation($class, $name, $checks, $operation);
+ $operation = $this->prepareOperation($operation);
+ return new JSONResponse($operation);
+ } catch (\UnexpectedValueException $e) {
+ return new JSONResponse($e->getMessage(), Http::STATUS_BAD_REQUEST);
+ }
+ }
+
+ /**
+ * @param int $id
+ * @param string $name
+ * @param array[] $checks
+ * @param string $operation
+ * @return JSONResponse The updated element
+ */
+ public function updateOperation($id, $name, $checks, $operation) {
+ try {
+ $operation = $this->manager->updateOperation($id, $name, $checks, $operation);
+ $operation = $this->prepareOperation($operation);
+ return new JSONResponse($operation);
+ } catch (\UnexpectedValueException $e) {
+ return new JSONResponse($e->getMessage(), Http::STATUS_BAD_REQUEST);
+ }
+ }
+
+ /**
+ * @param int $id
+ * @return JSONResponse
+ */
+ public function deleteOperation($id) {
+ $deleted = $this->manager->deleteOperation((int) $id);
+ return new JSONResponse($deleted);
+ }
+
+ /**
+ * @param array $operation
+ * @return array
+ */
+ protected function prepareOperation(array $operation) {
+ $checkIds = json_decode($operation['checks']);
+ $checks = $this->manager->getChecks($checkIds);
+
+ $operation['checks'] = [];
+ foreach ($checks as $check) {
+ // Remove internal values
+ unset($check['id']);
+ unset($check['hash']);
+
+ $operation['checks'][] = $check;
+ }
+
+ return $operation;
+ }
+}
diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php
new file mode 100644
index 00000000000..98c34e894cc
--- /dev/null
+++ b/apps/workflowengine/lib/Manager.php
@@ -0,0 +1,306 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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;
+
+
+use OCP\AppFramework\QueryException;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\Files\Storage\IStorage;
+use OCP\IDBConnection;
+use OCP\IServerContainer;
+use OCP\WorkflowEngine\ICheck;
+use OCP\WorkflowEngine\IManager;
+
+class Manager implements IManager {
+
+ /** @var IStorage */
+ protected $storage;
+
+ /** @var string */
+ protected $path;
+
+ /** @var array[] */
+ protected $operations = [];
+
+ /** @var array[] */
+ protected $checks = [];
+
+ /** @var IDBConnection */
+ protected $connection;
+
+ /** @var IServerContainer|\OC\Server */
+ protected $container;
+
+ /**
+ * @param IDBConnection $connection
+ * @param IServerContainer $container
+ */
+ public function __construct(IDBConnection $connection, IServerContainer $container) {
+ $this->connection = $connection;
+ $this->container = $container;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setFileInfo(IStorage $storage, $path) {
+ $this->storage = $storage;
+ $this->path = $path;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getMatchingOperations($class, $returnFirstMatchingOperationOnly = true) {
+ $operations = $this->getOperations($class);
+
+ $matches = [];
+ foreach ($operations as $operation) {
+ $checkIds = json_decode($operation['checks'], true);
+ $checks = $this->getChecks($checkIds);
+
+ foreach ($checks as $check) {
+ if (!$this->check($check)) {
+ // Check did not match, continue with the next operation
+ continue 2;
+ }
+ }
+
+ if ($returnFirstMatchingOperationOnly) {
+ return $operation;
+ }
+ $matches[] = $operation;
+ }
+
+ return $matches;
+ }
+
+ /**
+ * @param array $check
+ * @return bool
+ */
+ protected 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 ICheck) {
+ $checkInstance->setFileInfo($this->storage, $this->path);
+ return $checkInstance->executeCheck($check['operator'], $check['value']);
+ } else {
+ // Check is invalid, assume it matches.
+ return true;
+ }
+ }
+
+ /**
+ * @param string $class
+ * @return array[]
+ */
+ public function getOperations($class) {
+ if (isset($this->operations[$class])) {
+ return $this->operations[$class];
+ }
+
+ $query = $this->connection->getQueryBuilder();
+
+ $query->select('*')
+ ->from('flow_operations')
+ ->where($query->expr()->eq('class', $query->createNamedParameter($class)));
+ $result = $query->execute();
+
+ $this->operations[$class] = [];
+ while ($row = $result->fetch()) {
+ $this->operations[$class][] = $row;
+ }
+ $result->closeCursor();
+
+ return $this->operations[$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('Operation does not exist');
+ }
+
+ /**
+ * @param string $class
+ * @param string $name
+ * @param array[] $checks
+ * @param string $operation
+ * @return array The added operation
+ * @throws \UnexpectedValueException
+ */
+ public function addOperation($class, $name, array $checks, $operation) {
+ $checkIds = [];
+ foreach ($checks as $check) {
+ $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
+ }
+
+ $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),
+ ]);
+ $query->execute();
+
+ $id = $query->getLastInsertId();
+ return $this->getOperation($id);
+ }
+
+ /**
+ * @param int $id
+ * @param string $name
+ * @param array[] $checks
+ * @param string $operation
+ * @return array The updated operation
+ * @throws \UnexpectedValueException
+ */
+ public function updateOperation($id, $name, array $checks, $operation) {
+ $checkIds = [];
+ 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))
+ ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
+ $query->execute();
+
+ return $this->getOperation($id);
+ }
+
+ /**
+ * @param int $id
+ * @return bool
+ * @throws \UnexpectedValueException
+ */
+ public function deleteOperation($id) {
+ $query = $this->connection->getQueryBuilder();
+ $query->delete('flow_operations')
+ ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
+ return (bool) $query->execute();
+ }
+
+ /**
+ * @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();
+
+ $checks = [];
+ while ($row = $result->fetch()) {
+ $this->checks[(int) $row['id']] = $row;
+ $checks[(int) $row['id']] = $row;
+ }
+ $result->closeCursor();
+
+ // TODO What if a check is missing? Should we throw?
+ // As long as we only allow AND-concatenation of checks, a missing check
+ // is like a matching check, so it evaluates to true and therefor blocks
+ // access. So better save than sorry.
+
+ return $checks;
+ }
+
+ /**
+ * @param string $class
+ * @param string $operator
+ * @param string $value
+ * @return int Check unique ID
+ * @throws \UnexpectedValueException
+ */
+ protected function addCheck($class, $operator, $value) {
+ /** @var ICheck $check */
+ $check = $this->container->query($class);
+ $check->validateCheck($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();
+ }
+}