diff --git a/apps/workflowengine/appinfo/routes.php b/apps/workflowengine/appinfo/routes.php
index b8c9ae1c236a29a3cc8f95776726fb4f97fe4d2d..5ae74bcafc34c787a6dcf45aad0c1faa6434878c 100644
--- a/apps/workflowengine/appinfo/routes.php
+++ b/apps/workflowengine/appinfo/routes.php
@@ -25,5 +25,6 @@ return [
 		['name' => 'flowOperations#addOperation', 'url' => '/operations', 'verb' => 'POST'],
 		['name' => 'flowOperations#updateOperation', 'url' => '/operations/{id}', 'verb' => 'PUT'],
 		['name' => 'flowOperations#deleteOperation', 'url' => '/operations/{id}', 'verb' => 'DELETE'],
+		['name' => 'requestTime#getTimezones', 'url' => '/timezones', 'verb' => 'GET'],
 	]
 ];
diff --git a/apps/workflowengine/css/admin.css b/apps/workflowengine/css/admin.css
index 1d94fced003d00f4aaaa7bb51379fce3da60588d..70185615ad642d152c14243e8ada5cb263976605 100644
--- a/apps/workflowengine/css/admin.css
+++ b/apps/workflowengine/css/admin.css
@@ -46,3 +46,7 @@
 	margin-right: 5px;
 }
 
+.workflowengine .invalid-input {
+	border-color: #aa0000;
+}
+
diff --git a/apps/workflowengine/js/admin.js b/apps/workflowengine/js/admin.js
index 48d1592b4571a0b2a4c77da46a87c0ff65bab365..2c6fa54d87dbaff27339b5d52ca65c78d93386d5 100644
Binary files a/apps/workflowengine/js/admin.js and b/apps/workflowengine/js/admin.js differ
diff --git a/apps/workflowengine/js/filemimetypeplugin.js b/apps/workflowengine/js/filemimetypeplugin.js
new file mode 100644
index 0000000000000000000000000000000000000000..33cbbd7fd7e27428022f02f7cdf5329576bd3437
Binary files /dev/null and b/apps/workflowengine/js/filemimetypeplugin.js differ
diff --git a/apps/workflowengine/js/filesizeplugin.js b/apps/workflowengine/js/filesizeplugin.js
new file mode 100644
index 0000000000000000000000000000000000000000..0efa9d00edf06798be76e8b96d0e057c07475d93
Binary files /dev/null and b/apps/workflowengine/js/filesizeplugin.js differ
diff --git a/apps/workflowengine/js/filesystemtagsplugin.js b/apps/workflowengine/js/filesystemtagsplugin.js
new file mode 100644
index 0000000000000000000000000000000000000000..dc6f608d85a3ec087f16918750cebce6701b1971
Binary files /dev/null and b/apps/workflowengine/js/filesystemtagsplugin.js differ
diff --git a/apps/workflowengine/js/requestremoteaddressplugin.js b/apps/workflowengine/js/requestremoteaddressplugin.js
new file mode 100644
index 0000000000000000000000000000000000000000..a66d6f51f0f8f1151938fbbaf5845fd50b418a93
Binary files /dev/null and b/apps/workflowengine/js/requestremoteaddressplugin.js differ
diff --git a/apps/workflowengine/js/requesttimeplugin.js b/apps/workflowengine/js/requesttimeplugin.js
new file mode 100644
index 0000000000000000000000000000000000000000..111b2bb7437b3e52e1b9587df3b2a299f9af4430
Binary files /dev/null and b/apps/workflowengine/js/requesttimeplugin.js differ
diff --git a/apps/workflowengine/js/requesturlplugin.js b/apps/workflowengine/js/requesturlplugin.js
new file mode 100644
index 0000000000000000000000000000000000000000..5f21d2a59fc410cfbe19986853fa9de6e86e136e
Binary files /dev/null and b/apps/workflowengine/js/requesturlplugin.js differ
diff --git a/apps/workflowengine/js/requestuseragentplugin.js b/apps/workflowengine/js/requestuseragentplugin.js
new file mode 100644
index 0000000000000000000000000000000000000000..8413d52ac43a56d448c799e9868a677e0506450f
Binary files /dev/null and b/apps/workflowengine/js/requestuseragentplugin.js differ
diff --git a/apps/workflowengine/js/usergroupmembershipplugin.js b/apps/workflowengine/js/usergroupmembershipplugin.js
index 528a7bd3e3d059bb25b9b75990ca7604d2d95ae9..1c09e7d5ccd18821e5d1cf1e1bf22a5be75bd865 100644
Binary files a/apps/workflowengine/js/usergroupmembershipplugin.js and b/apps/workflowengine/js/usergroupmembershipplugin.js differ
diff --git a/apps/workflowengine/lib/AppInfo/Application.php b/apps/workflowengine/lib/AppInfo/Application.php
index 843395030472654d6d5832a0254fd47d6ffa4437..b5e769d01d72c5e922e2d349e6dda92ed3cf0c32 100644
--- a/apps/workflowengine/lib/AppInfo/Application.php
+++ b/apps/workflowengine/lib/AppInfo/Application.php
@@ -30,6 +30,7 @@ class Application extends \OCP\AppFramework\App {
 		parent::__construct('workflowengine');
 
 		$this->getContainer()->registerAlias('FlowOperationsController', 'OCA\WorkflowEngine\Controller\FlowOperations');
+		$this->getContainer()->registerAlias('RequestTimeController', 'OCA\WorkflowEngine\Controller\RequestTime');
 	}
 
 	/**
@@ -40,9 +41,32 @@ class Application extends \OCP\AppFramework\App {
 		$dispatcher->addListener(
 			'OCP\WorkflowEngine::loadAdditionalSettingScripts',
 			function() {
-				Util::addStyle('workflowengine', 'admin');
-				Util::addScript('workflowengine', 'admin');
-				Util::addScript('workflowengine', 'usergroupmembershipplugin');
+				style('workflowengine', [
+					'admin',
+				]);
+
+				script('core', [
+					'oc-backbone-webdav',
+					'systemtags/systemtags',
+					'systemtags/systemtagmodel',
+					'systemtags/systemtagscollection',
+				]);
+
+				vendor_script('jsTimezoneDetect/jstz');
+
+				script('workflowengine', [
+					'admin',
+
+					// Check plugins
+					'filemimetypeplugin',
+					'filesizeplugin',
+					'filesystemtagsplugin',
+					'requestremoteaddressplugin',
+					'requesttimeplugin',
+					'requesturlplugin',
+					'requestuseragentplugin',
+					'usergroupmembershipplugin',
+				]);
 			},
 			-100
 		);
diff --git a/apps/workflowengine/lib/Check/AbstractStringCheck.php b/apps/workflowengine/lib/Check/AbstractStringCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..0fd728e349664d5e67c8da762f8c43ada8528269
--- /dev/null
+++ b/apps/workflowengine/lib/Check/AbstractStringCheck.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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\Files\Storage\IStorage;
+use OCP\IL10N;
+use OCP\WorkflowEngine\ICheck;
+
+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;
+	}
+
+	/**
+	 * @param IStorage $storage
+	 * @param string $path
+	 */
+	public function setFileInfo(IStorage $storage, $path) {
+		// Nothing changes here with a different path
+	}
+
+	/**
+	 * @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;
+		} else if ($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);
+		}
+	}
+
+	/**
+	 * @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];
+	}
+}
diff --git a/apps/workflowengine/lib/Check/FileMimeType.php b/apps/workflowengine/lib/Check/FileMimeType.php
new file mode 100644
index 0000000000000000000000000000000000000000..1de9a70a17d54db371aa5b1e5dd4758a75f8673b
--- /dev/null
+++ b/apps/workflowengine/lib/Check/FileMimeType.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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\Files\IMimeTypeDetector;
+use OCP\IL10N;
+use OCP\IRequest;
+
+class FileMimeType extends AbstractStringCheck {
+
+	/** @var string */
+	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;
+	}
+
+	/**
+	 * @return string
+	 */
+	protected function getActualValue() {
+		if ($this->mimeType !== null) {
+			return $this->mimeType;
+		}
+
+		$this->mimeType = '';
+		if ($this->isWebDAVRequest()) {
+			if ($this->request->getMethod() === 'PUT') {
+				$path = $this->request->getPathInfo();
+				$this->mimeType = $this->mimeTypeDetector->detectPath($path);
+			}
+		} else if (in_array($this->request->getMethod(), ['POST', 'PUT'])) {
+			$files = $this->request->getUploadedFile('files');
+			if (isset($files['type'][0])) {
+				$this->mimeType = $files['type'][0];
+			}
+		}
+		return $this->mimeType;
+	}
+
+	/**
+	 * @return bool
+	 */
+	protected function isWebDAVRequest() {
+		return substr($this->request->getScriptName(), 0 - strlen('/remote.php')) === '/remote.php' && (
+			$this->request->getPathInfo() === '/webdav' ||
+			strpos($this->request->getPathInfo(), '/webdav/') === 0 ||
+			$this->request->getPathInfo() === '/dav/files' ||
+			strpos($this->request->getPathInfo(), '/dav/files/') === 0
+		);
+	}
+}
diff --git a/apps/workflowengine/lib/Check/FileSize.php b/apps/workflowengine/lib/Check/FileSize.php
new file mode 100644
index 0000000000000000000000000000000000000000..1744793dec72e3c68a168354aa0573fda7c804e5
--- /dev/null
+++ b/apps/workflowengine/lib/Check/FileSize.php
@@ -0,0 +1,119 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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\Files\Storage\IStorage;
+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 IStorage $storage
+	 * @param string $path
+	 */
+	public function setFileInfo(IStorage $storage, $path) {
+	}
+
+	/**
+	 * @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 === null) {
+			if (in_array($this->request->getMethod(), ['POST', 'PUT'])) {
+				$size = $this->request->getHeader('Content-Length');
+			}
+		}
+
+		if ($size === null) {
+			$size = false;
+		}
+
+		$this->size = $size;
+		return $this->size;
+	}
+}
diff --git a/apps/workflowengine/lib/Check/FileSystemTags.php b/apps/workflowengine/lib/Check/FileSystemTags.php
new file mode 100644
index 0000000000000000000000000000000000000000..e9b5a945967a1c33a4060daa4e2a5f63d8246ce4
--- /dev/null
+++ b/apps/workflowengine/lib/Check/FileSystemTags.php
@@ -0,0 +1,161 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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\Files\Cache\ICache;
+use OCP\Files\Storage\IStorage;
+use OCP\IL10N;
+use OCP\SystemTag\ISystemTagManager;
+use OCP\SystemTag\ISystemTagObjectMapper;
+use OCP\SystemTag\TagNotFoundException;
+use OCP\WorkflowEngine\ICheck;
+
+class FileSystemTags implements ICheck {
+
+	/** @var array */
+	protected $fileIds;
+
+	/** @var array */
+	protected $fileSystemTags;
+
+	/** @var IL10N */
+	protected $l;
+
+	/** @var ISystemTagManager */
+	protected $systemTagManager;
+
+	/** @var ISystemTagObjectMapper */
+	protected $systemTagObjectMapper;
+
+	/** @var IStorage */
+	protected $storage;
+
+	/** @var string */
+	protected $path;
+
+	/**
+	 * @param IL10N $l
+	 * @param ISystemTagManager $systemTagManager
+	 * @param ISystemTagObjectMapper $systemTagObjectMapper
+	 */
+	public function __construct(IL10N $l, ISystemTagManager $systemTagManager, ISystemTagObjectMapper $systemTagObjectMapper) {
+		$this->l = $l;
+		$this->systemTagManager = $systemTagManager;
+		$this->systemTagObjectMapper = $systemTagObjectMapper;
+	}
+
+	/**
+	 * @param IStorage $storage
+	 * @param string $path
+	 */
+	public function setFileInfo(IStorage $storage, $path) {
+		$this->storage = $storage;
+		$this->path = $path;
+	}
+
+	/**
+	 * @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 {
+			$this->systemTagManager->getTagsByIds($value);
+		} 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);
+
+		$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
+	 * @return int[]
+	 */
+	protected function getFileIds(ICache $cache, $path) {
+		$cacheId = $cache->getNumericStorageId();
+		if (isset($this->fileIds[$cacheId][$path])) {
+			return $this->fileIds[$cacheId][$path];
+		}
+
+		if ($path !== dirname($path)) {
+			$parentIds = $this->getFileIds($cache, dirname($path));
+		} else {
+			return [];
+		}
+
+		$fileId = $cache->getId($path);
+		if ($fileId !== -1) {
+			$parentIds[] = $cache->getId($path);
+		}
+
+		$this->fileIds[$cacheId][$path] = $parentIds;
+
+		return $parentIds;
+	}
+}
diff --git a/apps/workflowengine/lib/Check/RequestRemoteAddress.php b/apps/workflowengine/lib/Check/RequestRemoteAddress.php
new file mode 100644
index 0000000000000000000000000000000000000000..de9738fb631276cfd3bad0557b6ba04d6740e8bb
--- /dev/null
+++ b/apps/workflowengine/lib/Check/RequestRemoteAddress.php
@@ -0,0 +1,154 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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\Files\Storage\IStorage;
+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 IStorage $storage
+	 * @param string $path
+	 */
+	public function setFileInfo(IStorage $storage, $path) {
+		// A different path doesn't change time, so nothing to do here.
+	}
+
+	/**
+	 * @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]);
+		} else if ($operator === '!matchesIPv4') {
+			return !$this->matchIPv4($actualValue, $decodedValue[0], $decodedValue[1]);
+		} else if ($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 (sizeof($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 http://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 http://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 http://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);
+	}
+}
diff --git a/apps/workflowengine/lib/Check/RequestTime.php b/apps/workflowengine/lib/Check/RequestTime.php
new file mode 100644
index 0000000000000000000000000000000000000000..2aa79e77673cb993cf181352c56ea7ecc0ea79e4
--- /dev/null
+++ b/apps/workflowengine/lib/Check/RequestTime.php
@@ -0,0 +1,129 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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\AppFramework\Utility\ITimeFactory;
+use OCP\Files\Storage\IStorage;
+use OCP\IL10N;
+use OCP\WorkflowEngine\ICheck;
+
+class RequestTime implements ICheck {
+
+	const REGEX_TIME = '([0-1][0-9]|2[0-3]):([0-5][0-9])';
+	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 IStorage $storage
+	 * @param string $path
+	 */
+	public function setFileInfo(IStorage $storage, $path) {
+		// A different path doesn't change time, so nothing to do here.
+	}
+
+	/**
+	 * @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) {
+		list($time1, $timezone1) = explode(' ', $value);
+		list($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', $values[0]);
+		if ($time1 === false) {
+			throw new \UnexpectedValueException($this->l->t('The given start time is invalid'), 3);
+		}
+
+		$time2 = \DateTime::createFromFormat('H:i e', $values[1]);
+		if ($time2 === false) {
+			throw new \UnexpectedValueException($this->l->t('The given end time is invalid'), 4);
+		}
+	}
+}
diff --git a/apps/workflowengine/lib/Check/RequestURL.php b/apps/workflowengine/lib/Check/RequestURL.php
new file mode 100644
index 0000000000000000000000000000000000000000..36d41c101f207e945b517100a08153fe8ae53eb1
--- /dev/null
+++ b/apps/workflowengine/lib/Check/RequestURL.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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 {
+
+	/** @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)  {
+		$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
+	}
+
+	/**
+	 * @return bool
+	 */
+	protected function isWebDAVRequest() {
+		return substr($this->request->getScriptName(), 0 - strlen('/remote.php')) === '/remote.php' && (
+			$this->request->getPathInfo() === '/webdav' ||
+			strpos($this->request->getPathInfo(), '/webdav/') === 0 ||
+			$this->request->getPathInfo() === '/dav/files' ||
+			strpos($this->request->getPathInfo(), '/dav/files/') === 0
+		);
+	}
+}
diff --git a/apps/workflowengine/lib/Check/RequestUserAgent.php b/apps/workflowengine/lib/Check/RequestUserAgent.php
new file mode 100644
index 0000000000000000000000000000000000000000..7a8d4a71acfee4171c8882b6b9238746416b1188
--- /dev/null
+++ b/apps/workflowengine/lib/Check/RequestUserAgent.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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 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'])) {
+			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;
+			}
+		}
+		return $this->executeStringCheck($operator, $value, $actualValue);
+	}
+
+	/**
+	 * @return string
+	 */
+	protected function getActualValue() {
+		return (string) $this->request->getHeader('User-Agent');
+	}
+}
diff --git a/apps/workflowengine/lib/Check/UserGroupMembership.php b/apps/workflowengine/lib/Check/UserGroupMembership.php
index 6390c57fbea2b54043ff36a3dff785e77af59dbe..fd6ba00d09245af5a27110fecefcc1fcd8f38842 100644
--- a/apps/workflowengine/lib/Check/UserGroupMembership.php
+++ b/apps/workflowengine/lib/Check/UserGroupMembership.php
@@ -89,11 +89,11 @@ class UserGroupMembership implements ICheck {
 	 */
 	public function validateCheck($operator, $value) {
 		if (!in_array($operator, ['is', '!is'])) {
-			throw new \UnexpectedValueException($this->l->t('Operator %s is invalid', $operator), 1);
+			throw new \UnexpectedValueException($this->l->t('The given operator is invalid'), 1);
 		}
 
 		if (!$this->groupManager->groupExists($value)) {
-			throw new \UnexpectedValueException($this->l->t('Group %s does not exist', $value), 2);
+			throw new \UnexpectedValueException($this->l->t('The given group does not exist'), 2);
 		}
 	}
 
diff --git a/apps/workflowengine/lib/Controller/RequestTime.php b/apps/workflowengine/lib/Controller/RequestTime.php
new file mode 100644
index 0000000000000000000000000000000000000000..dd0efa89b91588d5f543a4a7f12d278e24566b53
--- /dev/null
+++ b/apps/workflowengine/lib/Controller/RequestTime.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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\Controller;
+
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\JSONResponse;
+
+class RequestTime 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 strpos(strtolower($timezone), strtolower($search)) !== false;
+			});
+		}
+
+		$timezones = array_slice($timezones, 0, 10);
+
+		$response = [];
+		foreach ($timezones as $timezone) {
+			$response[$timezone] = $timezone;
+		}
+		return new JSONResponse($response);
+	}
+}
diff --git a/apps/workflowengine/templates/admin.php b/apps/workflowengine/templates/admin.php
index 4eabf783bbae02e52c813db325f150a1939b2e52..935e8c70f17ca0a23ba5fdd840ef3d0c2d7c4d34 100644
--- a/apps/workflowengine/templates/admin.php
+++ b/apps/workflowengine/templates/admin.php
@@ -36,7 +36,7 @@
 			{{#if operation.id}}
 			<span class="button-delete pull-right icon-delete"></span>
 			{{/if}}
-			<span class="pull-right info">{{operation.class}} - ID: {{operation.id}} - operation: {{operation.operation}}</span>
+			<input type="text" class="pull-right operation-operation" value="{{operation.operation}}">
 
 			<div class="checks">
 				{{#each operation.checks}}
diff --git a/apps/workflowengine/tests/Check/AbstractStringCheckTest.php b/apps/workflowengine/tests/Check/AbstractStringCheckTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..91da8931604c5728368e81f68e9ba3189e49c70c
--- /dev/null
+++ b/apps/workflowengine/tests/Check/AbstractStringCheckTest.php
@@ -0,0 +1,149 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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\Tests\Check;
+
+
+class AbstractStringCheckTest extends \Test\TestCase {
+
+	protected function getCheckMock() {
+		$l = $this->getMockBuilder('OCP\IL10N')
+			->disableOriginalConstructor()
+			->getMock();
+		$l->expects($this->any())
+			->method('t')
+			->willReturnCallback(function($string, $args) {
+					return sprintf($string, $args);
+				});
+
+		$check = $this->getMockBuilder('OCA\WorkflowEngine\Check\AbstractStringCheck')
+			->setConstructorArgs([
+				$l,
+			])
+			->setMethods([
+				'setPath',
+				'executeCheck',
+				'getActualValue',
+			])
+			->getMock();
+
+		return $check;
+	}
+
+	public function dataExecuteStringCheck() {
+		return [
+			['is', 'same', 'same', true],
+			['is', 'different', 'not the same', false],
+			['!is', 'same', 'same', false],
+			['!is', 'different', 'not the same', true],
+
+			['matches', '/match/', 'match', true],
+			['matches', '/different/', 'not the same', false],
+			['!matches', '/match/', 'match', false],
+			['!matches', '/different/', 'not the same', true],
+		];
+	}
+
+	/**
+	 * @dataProvider dataExecuteStringCheck
+	 * @param string $operation
+	 * @param string $checkValue
+	 * @param string $actualValue
+	 * @param bool $expected
+	 */
+	public function testExecuteStringCheck($operation, $checkValue, $actualValue, $expected) {
+		$check = $this->getCheckMock();
+
+		/** @var \OCA\WorkflowEngine\Check\AbstractStringCheck $check */
+		$this->assertEquals($expected, $this->invokePrivate($check, 'executeStringCheck', [$operation, $checkValue, $actualValue]));
+	}
+
+	public function dataValidateCheck() {
+		return [
+			['is', '/Invalid(Regex/'],
+			['!is', '/Invalid(Regex/'],
+			['matches', '/Valid(Regex)/'],
+			['!matches', '/Valid(Regex)/'],
+		];
+	}
+
+	/**
+	 * @dataProvider dataValidateCheck
+	 * @param string $operator
+	 * @param string $value
+	 */
+	public function testValidateCheck($operator, $value) {
+		$check = $this->getCheckMock();
+
+		/** @var \OCA\WorkflowEngine\Check\AbstractStringCheck $check */
+		$check->validateCheck($operator, $value);
+	}
+
+	public function dataValidateCheckInvalid() {
+		return [
+			['!!is', '', 1, 'The given operator is invalid'],
+			['less', '', 1, 'The given operator is invalid'],
+			['matches', '/Invalid(Regex/', 2, 'The given regular expression is invalid'],
+			['!matches', '/Invalid(Regex/', 2, 'The given regular expression is invalid'],
+		];
+	}
+
+	/**
+	 * @dataProvider dataValidateCheckInvalid
+	 * @param $operator
+	 * @param $value
+	 * @param $exceptionCode
+	 * @param $exceptionMessage
+	 */
+	public function testValidateCheckInvalid($operator, $value, $exceptionCode, $exceptionMessage) {
+		$check = $this->getCheckMock();
+
+		try {
+			/** @var \OCA\WorkflowEngine\Check\AbstractStringCheck $check */
+			$check->validateCheck($operator, $value);
+		} catch (\UnexpectedValueException $e) {
+			$this->assertEquals($exceptionCode, $e->getCode());
+			$this->assertEquals($exceptionMessage, $e->getMessage());
+		}
+	}
+
+	public function dataMatch() {
+		return [
+			['/valid/', 'valid', [], true],
+			['/valid/', 'valid', [md5('/valid/') => [md5('valid') => false]], false], // Cache hit
+		];
+	}
+
+	/**
+	 * @dataProvider dataMatch
+	 * @param string $pattern
+	 * @param string $subject
+	 * @param array[] $matches
+	 * @param bool $expected
+	 */
+	public function testMatch($pattern, $subject, $matches, $expected) {
+		$check = $this->getCheckMock();
+
+		$this->invokePrivate($check, 'matches', [$matches]);
+
+		$this->assertEquals($expected, $this->invokePrivate($check, 'match', [$pattern, $subject]));
+	}
+}
diff --git a/apps/workflowengine/tests/Check/RequestRemoteAddressTest.php b/apps/workflowengine/tests/Check/RequestRemoteAddressTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..efe8f6372ddbbb7f7515b3bdad1f588338864206
--- /dev/null
+++ b/apps/workflowengine/tests/Check/RequestRemoteAddressTest.php
@@ -0,0 +1,138 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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\Tests\Check;
+
+
+class RequestRemoteAddressTest extends \Test\TestCase {
+
+	/** @var \OCP\IRequest|\PHPUnit_Framework_MockObject_MockObject */
+	protected $request;
+
+	/**
+	 * @return \OCP\IL10N|\PHPUnit_Framework_MockObject_MockObject
+	 */
+	protected function getL10NMock() {
+		$l = $this->getMockBuilder('OCP\IL10N')
+			->disableOriginalConstructor()
+			->getMock();
+		$l->expects($this->any())
+			->method('t')
+			->willReturnCallback(function ($string, $args) {
+				return sprintf($string, $args);
+			});
+		return $l;
+	}
+
+	protected function setUp() {
+		parent::setUp();
+
+		$this->request = $this->getMockBuilder('OCP\IRequest')
+			->getMock();
+	}
+
+	public function dataExecuteCheckIPv4() {
+		return [
+			['127.0.0.1/32', '127.0.0.1', true],
+			['127.0.0.1/32', '127.0.0.0', false],
+			['127.0.0.1/31', '127.0.0.0', true],
+			['127.0.0.1/32', '127.0.0.2', false],
+			['127.0.0.1/31', '127.0.0.2', false],
+			['127.0.0.1/30', '127.0.0.2', true],
+		];
+	}
+
+	/**
+	 * @dataProvider dataExecuteCheckIPv4
+	 * @param string $value
+	 * @param string $ip
+	 * @param bool $expected
+	 */
+	public function testExecuteCheckMatchesIPv4($value, $ip, $expected) {
+		$check = new \OCA\WorkflowEngine\Check\RequestRemoteAddress($this->getL10NMock(), $this->request);
+
+		$this->request->expects($this->once())
+			->method('getRemoteAddress')
+			->willReturn($ip);
+
+		$this->assertEquals($expected, $check->executeCheck('matchesIPv4', $value));
+	}
+
+	/**
+	 * @dataProvider dataExecuteCheckIPv4
+	 * @param string $value
+	 * @param string $ip
+	 * @param bool $expected
+	 */
+	public function testExecuteCheckNotMatchesIPv4($value, $ip, $expected) {
+		$check = new \OCA\WorkflowEngine\Check\RequestRemoteAddress($this->getL10NMock(), $this->request);
+
+		$this->request->expects($this->once())
+			->method('getRemoteAddress')
+			->willReturn($ip);
+
+		$this->assertEquals(!$expected, $check->executeCheck('!matchesIPv4', $value));
+	}
+
+	public function dataExecuteCheckIPv6() {
+		return [
+			['::1/128', '::1', true],
+			['::2/128', '::3', false],
+			['::2/127', '::3', true],
+			['::1/128', '::2', false],
+			['::1/127', '::2', false],
+			['::1/126', '::2', true],
+			['1234::1/127', '1234::', true],
+		];
+	}
+
+	/**
+	 * @dataProvider dataExecuteCheckIPv6
+	 * @param string $value
+	 * @param string $ip
+	 * @param bool $expected
+	 */
+	public function testExecuteCheckMatchesIPv6($value, $ip, $expected) {
+		$check = new \OCA\WorkflowEngine\Check\RequestRemoteAddress($this->getL10NMock(), $this->request);
+
+		$this->request->expects($this->once())
+			->method('getRemoteAddress')
+			->willReturn($ip);
+
+		$this->assertEquals($expected, $check->executeCheck('matchesIPv6', $value));
+	}
+
+	/**
+	 * @dataProvider dataExecuteCheckIPv6
+	 * @param string $value
+	 * @param string $ip
+	 * @param bool $expected
+	 */
+	public function testExecuteCheckNotMatchesIPv6($value, $ip, $expected) {
+		$check = new \OCA\WorkflowEngine\Check\RequestRemoteAddress($this->getL10NMock(), $this->request);
+
+		$this->request->expects($this->once())
+			->method('getRemoteAddress')
+			->willReturn($ip);
+
+		$this->assertEquals(!$expected, $check->executeCheck('!matchesIPv6', $value));
+	}
+}
diff --git a/apps/workflowengine/tests/Check/RequestTimeTest.php b/apps/workflowengine/tests/Check/RequestTimeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c07b4bf87754e90ade910842c3ebc03eff0fc289
--- /dev/null
+++ b/apps/workflowengine/tests/Check/RequestTimeTest.php
@@ -0,0 +1,162 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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\Tests\Check;
+
+
+class RequestTimeTest extends \Test\TestCase {
+
+	/** @var \OCP\AppFramework\Utility\ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */
+	protected $timeFactory;
+
+	/**
+	 * @return \OCP\IL10N|\PHPUnit_Framework_MockObject_MockObject
+	 */
+	protected function getL10NMock() {
+		$l = $this->getMockBuilder('OCP\IL10N')
+			->disableOriginalConstructor()
+			->getMock();
+		$l->expects($this->any())
+			->method('t')
+			->willReturnCallback(function ($string, $args) {
+				return sprintf($string, $args);
+			});
+		return $l;
+	}
+
+	protected function setUp() {
+		parent::setUp();
+
+		$this->timeFactory = $this->getMockBuilder('OCP\AppFramework\Utility\ITimeFactory')
+			->getMock();
+	}
+
+	public function dataExecuteCheck() {
+		return [
+			[json_encode(['08:00 Europe/Berlin', '17:00 Europe/Berlin']), 1467870105, false], // 2016-07-07T07:41:45+02:00
+			[json_encode(['08:00 Europe/Berlin', '17:00 Europe/Berlin']), 1467873705, true], // 2016-07-07T08:41:45+02:00
+			[json_encode(['08:00 Europe/Berlin', '17:00 Europe/Berlin']), 1467902505, true], // 2016-07-07T16:41:45+02:00
+			[json_encode(['08:00 Europe/Berlin', '17:00 Europe/Berlin']), 1467906105, false], // 2016-07-07T17:41:45+02:00
+			[json_encode(['17:00 Europe/Berlin', '08:00 Europe/Berlin']), 1467870105, true], // 2016-07-07T07:41:45+02:00
+			[json_encode(['17:00 Europe/Berlin', '08:00 Europe/Berlin']), 1467873705, false], // 2016-07-07T08:41:45+02:00
+			[json_encode(['17:00 Europe/Berlin', '08:00 Europe/Berlin']), 1467902505, false], // 2016-07-07T16:41:45+02:00
+			[json_encode(['17:00 Europe/Berlin', '08:00 Europe/Berlin']), 1467906105, true], // 2016-07-07T17:41:45+02:00
+
+			[json_encode(['08:00 Australia/Adelaide', '17:00 Australia/Adelaide']), 1467843105, false], // 2016-07-07T07:41:45+09:30
+			[json_encode(['08:00 Australia/Adelaide', '17:00 Australia/Adelaide']), 1467846705, true], // 2016-07-07T08:41:45+09:30
+			[json_encode(['08:00 Australia/Adelaide', '17:00 Australia/Adelaide']), 1467875505, true], // 2016-07-07T16:41:45+09:30
+			[json_encode(['08:00 Australia/Adelaide', '17:00 Australia/Adelaide']), 1467879105, false], // 2016-07-07T17:41:45+09:30
+			[json_encode(['17:00 Australia/Adelaide', '08:00 Australia/Adelaide']), 1467843105, true], // 2016-07-07T07:41:45+09:30
+			[json_encode(['17:00 Australia/Adelaide', '08:00 Australia/Adelaide']), 1467846705, false], // 2016-07-07T08:41:45+09:30
+			[json_encode(['17:00 Australia/Adelaide', '08:00 Australia/Adelaide']), 1467875505, false], // 2016-07-07T16:41:45+09:30
+			[json_encode(['17:00 Australia/Adelaide', '08:00 Australia/Adelaide']), 1467879105, true], // 2016-07-07T17:41:45+09:30
+
+			[json_encode(['08:00 Pacific/Niue', '17:00 Pacific/Niue']), 1467916905, false], // 2016-07-07T07:41:45-11:00
+			[json_encode(['08:00 Pacific/Niue', '17:00 Pacific/Niue']), 1467920505, true], // 2016-07-07T08:41:45-11:00
+			[json_encode(['08:00 Pacific/Niue', '17:00 Pacific/Niue']), 1467949305, true], // 2016-07-07T16:41:45-11:00
+			[json_encode(['08:00 Pacific/Niue', '17:00 Pacific/Niue']), 1467952905, false], // 2016-07-07T17:41:45-11:00
+			[json_encode(['17:00 Pacific/Niue', '08:00 Pacific/Niue']), 1467916905, true], // 2016-07-07T07:41:45-11:00
+			[json_encode(['17:00 Pacific/Niue', '08:00 Pacific/Niue']), 1467920505, false], // 2016-07-07T08:41:45-11:00
+			[json_encode(['17:00 Pacific/Niue', '08:00 Pacific/Niue']), 1467949305, false], // 2016-07-07T16:41:45-11:00
+			[json_encode(['17:00 Pacific/Niue', '08:00 Pacific/Niue']), 1467952905, true], // 2016-07-07T17:41:45-11:00
+		];
+	}
+
+	/**
+	 * @dataProvider dataExecuteCheck
+	 * @param string $value
+	 * @param int $timestamp
+	 * @param bool $expected
+	 */
+	public function testExecuteCheckIn($value, $timestamp, $expected) {
+		$check = new \OCA\WorkflowEngine\Check\RequestTime($this->getL10NMock(), $this->timeFactory);
+
+		$this->timeFactory->expects($this->once())
+			->method('getTime')
+			->willReturn($timestamp);
+
+		$this->assertEquals($expected, $check->executeCheck('in', $value));
+	}
+
+	/**
+	 * @dataProvider dataExecuteCheck
+	 * @param string $value
+	 * @param int $timestamp
+	 * @param bool $expected
+	 */
+	public function testExecuteCheckNotIn($value, $timestamp, $expected) {
+		$check = new \OCA\WorkflowEngine\Check\RequestTime($this->getL10NMock(), $this->timeFactory);
+
+		$this->timeFactory->expects($this->once())
+			->method('getTime')
+			->willReturn($timestamp);
+
+		$this->assertEquals(!$expected, $check->executeCheck('!in', $value));
+	}
+
+	public function dataValidateCheck() {
+		return [
+			['in', '["08:00 Europe/Berlin","17:00 Europe/Berlin"]'],
+			['!in', '["08:00 Europe/Berlin","17:00 America/North_Dakota/Beulah"]'],
+			['in', '["08:00 America/Port-au-Prince","17:00 America/Argentina/San_Luis"]'],
+		];
+	}
+
+	/**
+	 * @dataProvider dataValidateCheck
+	 * @param string $operator
+	 * @param string $value
+	 */
+	public function testValidateCheck($operator, $value) {
+		$check = new \OCA\WorkflowEngine\Check\RequestTime($this->getL10NMock(), $this->timeFactory);
+		$check->validateCheck($operator, $value);
+	}
+
+	public function dataValidateCheckInvalid() {
+		return [
+			['!!in', '["08:00 Europe/Berlin","17:00 Europe/Berlin"]', 1, 'The given operator is invalid'],
+			['in', '["28:00 Europe/Berlin","17:00 Europe/Berlin"]', 2, 'The given time span is invalid'],
+			['in', '["08:00 Europe/Berlin","27:00 Europe/Berlin"]', 2, 'The given time span is invalid'],
+			['in', '["08:00 Europa/Berlin","17:00 Europe/Berlin"]', 3, 'The given start time is invalid'],
+			['in', '["08:00 Europe/Berlin","17:00 Europa/Berlin"]', 4, 'The given end time is invalid'],
+			['in', '["08:00 Europe/Bearlin","17:00 Europe/Berlin"]', 3, 'The given start time is invalid'],
+			['in', '["08:00 Europe/Berlin","17:00 Europe/Bearlin"]', 4, 'The given end time is invalid'],
+		];
+	}
+
+	/**
+	 * @dataProvider dataValidateCheckInvalid
+	 * @param string $operator
+	 * @param string $value
+	 * @param int $exceptionCode
+	 * @param string $exceptionMessage
+	 */
+	public function testValidateCheckInvalid($operator, $value, $exceptionCode, $exceptionMessage) {
+		$check = new \OCA\WorkflowEngine\Check\RequestTime($this->getL10NMock(), $this->timeFactory);
+
+		try {
+			$check->validateCheck($operator, $value);
+		} catch (\UnexpectedValueException $e) {
+			$this->assertEquals($exceptionCode, $e->getCode());
+			$this->assertEquals($exceptionMessage, $e->getMessage());
+		}
+	}
+}