diff --git a/.gitignore b/.gitignore index f84928d..ce8fde9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ vendor webroot/theme/node_modules .vscode docker/run/ +.phpunit.result.cache +config.json +phpunit.xml diff --git a/INSTALL/INSTALL.md b/INSTALL/INSTALL.md index fc6e131..f4cef5f 100644 --- a/INSTALL/INSTALL.md +++ b/INSTALL/INSTALL.md @@ -74,12 +74,6 @@ sudo mysql -e "GRANT ALL PRIVILEGES ON cerebrate.* to cerebrate@localhost;" sudo mysql -e "FLUSH PRIVILEGES;" ``` -Load the default table structure into the database - -```bash -sudo mysql -u cerebrate -p cerebrate < /var/www/cerebrate/INSTALL/mysql.sql -``` - create your local configuration and set the db credentials ```bash diff --git a/INSTALL/mysql.sql b/INSTALL/mysql.sql deleted file mode 100644 index ed39991..0000000 --- a/INSTALL/mysql.sql +++ /dev/null @@ -1,408 +0,0 @@ --- MySQL dump 10.16 Distrib 10.1.44-MariaDB, for debian-linux-gnu (x86_64) --- --- Host: localhost Database: cerebrate --- ------------------------------------------------------ --- Server version 10.1.44-MariaDB-0ubuntu0.18.04.1 - -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8mb4 */; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; -/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; -/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; - --- --- Table structure for table `alignment_tags` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `alignment_tags` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `alignment_id` int(10) unsigned NOT NULL, - `tag_id` int(10) unsigned NOT NULL, - PRIMARY KEY (`id`), - KEY `alignment_id` (`alignment_id`), - KEY `tag_id` (`tag_id`), - CONSTRAINT `alignment_tags_ibfk_1` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`), - CONSTRAINT `alignment_tags_ibfk_10` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`), - CONSTRAINT `alignment_tags_ibfk_11` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`), - CONSTRAINT `alignment_tags_ibfk_12` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`), - CONSTRAINT `alignment_tags_ibfk_2` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`), - CONSTRAINT `alignment_tags_ibfk_3` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`), - CONSTRAINT `alignment_tags_ibfk_4` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`), - CONSTRAINT `alignment_tags_ibfk_5` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`), - CONSTRAINT `alignment_tags_ibfk_6` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`), - CONSTRAINT `alignment_tags_ibfk_7` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`), - CONSTRAINT `alignment_tags_ibfk_8` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`), - CONSTRAINT `alignment_tags_ibfk_9` FOREIGN KEY (`alignment_id`) REFERENCES `alignments` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `alignments` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `alignments` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `individual_id` int(10) unsigned NOT NULL, - `organisation_id` int(10) unsigned NOT NULL, - `type` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT 'member', - PRIMARY KEY (`id`), - KEY `individual_id` (`individual_id`), - KEY `organisation_id` (`organisation_id`), - CONSTRAINT `alignments_ibfk_1` FOREIGN KEY (`individual_id`) REFERENCES `individuals` (`id`), - CONSTRAINT `alignments_ibfk_2` FOREIGN KEY (`organisation_id`) REFERENCES `organisations` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `auth_keys` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `auth_keys` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL, - `authkey` varchar(72) CHARACTER SET ascii DEFAULT NULL, - `authkey_start` varchar(4) CHARACTER SET ascii DEFAULT NULL, - `authkey_end` varchar(4) CHARACTER SET ascii DEFAULT NULL, - `created` int(10) unsigned NOT NULL, - `expiration` int(10) unsigned NOT NULL, - `user_id` int(10) unsigned NOT NULL, - `comment` text COLLATE utf8mb4_unicode_ci, - PRIMARY KEY (`id`), - KEY `authkey_start` (`authkey_start`), - KEY `authkey_end` (`authkey_end`), - KEY `created` (`created`), - KEY `expiration` (`expiration`), - KEY `user_id` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `broods` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `broods` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `url` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `description` text COLLATE utf8mb4_unicode_ci, - `organisation_id` int(10) unsigned NOT NULL, - `trusted` tinyint(1) DEFAULT NULL, - `pull` tinyint(1) DEFAULT NULL, - `skip_proxy` tinyint(1) DEFAULT NULL, - `authkey` varchar(40) CHARACTER SET ascii DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `uuid` (`uuid`), - KEY `name` (`name`), - KEY `url` (`url`), - KEY `authkey` (`authkey`), - KEY `organisation_id` (`organisation_id`), - FOREIGN KEY (`organisation_id`) REFERENCES `organisations` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `encryption_keys` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `encryption_keys` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `type` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `encryption_key` text COLLATE utf8mb4_unicode_ci, - `revoked` tinyint(1) DEFAULT NULL, - `expires` int(10) unsigned DEFAULT NULL, - `owner_id` int(10) unsigned DEFAULT NULL, - `owner_type` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL, - PRIMARY KEY (`id`), - KEY `uuid` (`uuid`), - KEY `type` (`type`), - KEY `expires` (`expires`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `individual_encryption_keys` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `individual_encryption_keys` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `individual_id` int(10) unsigned NOT NULL, - `encryption_key_id` int(10) unsigned NOT NULL, - PRIMARY KEY (`id`), - KEY `individual_id` (`individual_id`), - KEY `encryption_key_id` (`encryption_key_id`), - CONSTRAINT `individual_encryption_keys_ibfk_1` FOREIGN KEY (`individual_id`) REFERENCES `individuals` (`id`), - CONSTRAINT `individual_encryption_keys_ibfk_2` FOREIGN KEY (`encryption_key_id`) REFERENCES `encryption_keys` (`id`), - CONSTRAINT `individual_encryption_keys_ibfk_3` FOREIGN KEY (`individual_id`) REFERENCES `individuals` (`id`), - CONSTRAINT `individual_encryption_keys_ibfk_4` FOREIGN KEY (`encryption_key_id`) REFERENCES `encryption_keys` (`id`), - CONSTRAINT `individual_encryption_keys_ibfk_5` FOREIGN KEY (`individual_id`) REFERENCES `individuals` (`id`), - CONSTRAINT `individual_encryption_keys_ibfk_6` FOREIGN KEY (`encryption_key_id`) REFERENCES `encryption_keys` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `individuals` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `individuals` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `email` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `first_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `last_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `position` text COLLATE utf8mb4_unicode_ci, - PRIMARY KEY (`id`), - KEY `uuid` (`uuid`), - KEY `email` (`email`), - KEY `first_name` (`first_name`), - KEY `last_name` (`last_name`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `local_tools` --- - -CREATE TABLE IF NOT EXISTS `local_tools` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `connector` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `settings` text COLLATE utf8mb4_unicode_ci, - `exposed` tinyint(1) NOT NULL, - `description` text COLLATE utf8mb4_unicode_ci, - PRIMARY KEY (`id`), - KEY `name` (`name`), - KEY `connector` (`connector`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - --- --- Table structure for table `organisation_encryption_keys` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `organisation_encryption_keys` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `organisation_id` int(10) unsigned NOT NULL, - `encryption_key_id` int(10) unsigned NOT NULL, - PRIMARY KEY (`id`), - KEY `organisation_id` (`organisation_id`), - KEY `encryption_key_id` (`encryption_key_id`), - CONSTRAINT `organisation_encryption_keys_ibfk_1` FOREIGN KEY (`organisation_id`) REFERENCES `organisations` (`id`), - CONSTRAINT `organisation_encryption_keys_ibfk_2` FOREIGN KEY (`encryption_key_id`) REFERENCES `encryption_keys` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `organisations` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `organisations` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `url` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `nationality` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `sector` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `type` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `contacts` text COLLATE utf8mb4_unicode_ci, - PRIMARY KEY (`id`), - KEY `uuid` (`uuid`), - KEY `name` (`name`), - KEY `url` (`url`), - KEY `nationality` (`nationality`), - KEY `sector` (`sector`), - KEY `type` (`type`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `roles` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `roles` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `is_default` tinyint(1) DEFAULT NULL, - `perm_admin` tinyint(1) DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `name` (`name`), - KEY `uuid` (`uuid`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `tags` --- - -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `tags` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `description` text COLLATE utf8mb4_unicode_ci, - `colour` varchar(6) CHARACTER SET ascii NOT NULL, - PRIMARY KEY (`id`), - KEY `name` (`name`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `users` --- - -CREATE TABLE IF NOT EXISTS `users` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `username` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `password` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `role_id` int(11) unsigned NOT NULL, - `individual_id` int(11) unsigned NOT NULL, - `disabled` tinyint(1) DEFAULT '0', - PRIMARY KEY (`id`), - KEY `uuid` (`uuid`), - KEY `role_id` (`role_id`), - KEY `individual_id` (`individual_id`), - CONSTRAINT `users_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`), - CONSTRAINT `users_ibfk_2` FOREIGN KEY (`individual_id`) REFERENCES `individuals` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; - -CREATE TABLE IF NOT EXISTS `sharing_groups` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `releasability` text DEFAULT NULL, - `description` text DEFAULT NULL, - `organisation_id` int(10) unsigned NOT NULL, - `user_id` int(10) unsigned NOT NULL, - `active` tinyint(1) DEFAULT '1', - `local` tinyint(1) DEFAULT '1', - PRIMARY KEY (`id`), - KEY `uuid` (`uuid`), - KEY `user_id` (`user_id`), - KEY `organisation_id` (`organisation_id`), - KEY `name` (`name`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -CREATE TABLE IF NOT EXISTS `sgo` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `sharing_group_id` int(10) unsigned NOT NULL, - `organisation_id` int(10) unsigned NOT NULL, - `deleted` tinyint(1) DEFAULT 0, - PRIMARY KEY (`id`), - KEY `sharing_group_id` (`sharing_group_id`), - KEY `organisation_id` (`organisation_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -CREATE TABLE IF NOT EXISTS `meta_fields` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `scope` varchar(191) NOT NULL, - `parent_id` int(10) unsigned NOT NULL, - `field` varchar(191) NOT NULL, - `value` varchar(191) NOT NULL, - `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, - `meta_template_id` int(10) unsigned NOT NULL, - `meta_template_field_id` int(10) unsigned NOT NULL, - `is_default` tinyint(1) NOT NULL DEFAULT 0, - PRIMARY KEY (`id`), - KEY `scope` (`scope`), - KEY `uuid` (`uuid`), - KEY `parent_id` (`parent_id`), - KEY `field` (`field`), - KEY `value` (`value`), - KEY `meta_template_id` (`meta_template_id`), - KEY `meta_template_field_id` (`meta_template_field_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -CREATE TABLE IF NOT EXISTS `meta_templates` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `scope` varchar(191) NOT NULL, - `name` varchar(191) NOT NULL, - `namespace` varchar(191) NOT NULL, - `description` text, - `version` varchar(191) NOT NULL, - `uuid` varchar(40) CHARACTER SET ascii, - `source` varchar(191), - `enabled` tinyint(1) DEFAULT 0, - `is_default` tinyint(1) NOT NULL DEFAULT 0, - PRIMARY KEY (`id`), - KEY `scope` (`scope`), - KEY `source` (`source`), - KEY `name` (`name`), - KEY `namespace` (`namespace`), - KEY `version` (`version`), - KEY `uuid` (`uuid`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -CREATE TABLE IF NOT EXISTS `meta_template_fields` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `field` varchar(191) NOT NULL, - `type` varchar(191) NOT NULL, - `meta_template_id` int(10) unsigned NOT NULL, - `regex` text, - `multiple` tinyint(1) DEFAULT 0, - `enabled` tinyint(1) DEFAULT 0, - PRIMARY KEY (`id`), - CONSTRAINT `meta_template_id` FOREIGN KEY (`meta_template_id`) REFERENCES `meta_templates` (`id`), - KEY `field` (`field`), - KEY `type` (`type`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -CREATE TABLE IF NOT EXISTS `audit_logs` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `created` datetime NOT NULL, - `user_id` int(10) unsigned DEFAULT NULL, - `authkey_id` int(10) unsigned DEFAULT NULL, - `request_ip` varbinary(16) DEFAULT NULL, - `request_type` tinyint NOT NULL, - `request_id` varchar(191) DEFAULT NULL, - `request_action` varchar(20) NOT NULL, - `model` varchar(80) NOT NULL, - `model_id` int(10) unsigned DEFAULT NULL, - `model_title` text DEFAULT NULL, - `change` blob, - PRIMARY KEY (`id`), - KEY `user_id` (`user_id`), - KEY `request_ip` (`request_ip`), - KEY `model` (`model`), - KEY `request_action` (`request_action`), - KEY `model_id` (`model_id`), - KEY `created` (`created`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; - --- Dump completed on 2020-06-22 14:30:02 diff --git a/composer.json b/composer.json index bc562a0..f51bc6f 100644 --- a/composer.json +++ b/composer.json @@ -19,9 +19,12 @@ "cakephp/bake": "^2.0.3", "cakephp/cakephp-codesniffer": "~4.0.0", "cakephp/debug_kit": "^4.0", + "fzaninotto/faker": "^1.9", "josegonzalez/dotenv": "^3.2", + "league/openapi-psr7-validator": "^0.16.4", "phpunit/phpunit": "^8.5", - "psy/psysh": "@stable" + "psy/psysh": "@stable", + "wiremock-php/wiremock-php": "^2.32" }, "suggest": { "markstory/asset_compress": "An asset compression plugin which provides file concatenation and a flexible filter system for preprocessing and minification.", @@ -44,7 +47,6 @@ "scripts": { "post-install-cmd": "App\\Console\\Installer::postInstall", "post-create-project-cmd": "App\\Console\\Installer::postInstall", - "post-autoload-dump": "Cake\\Composer\\Installer\\PluginInstaller::postAutoloadDump", "check": [ "@test", "@cs-check" @@ -52,11 +54,15 @@ "cs-check": "phpcs --colors -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/", "cs-fix": "phpcbf --colors --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/", "stan": "phpstan analyse src/", - "test": "phpunit --colors=always" + "test": [ + "sh ./tests/Helper/wiremock/start.sh", + "phpunit", + "sh ./tests/Helper/wiremock/stop.sh" + ] }, "prefer-stable": true, "config": { "sort-packages": true }, "minimum-stability": "dev" -} +} \ No newline at end of file diff --git a/config/Migrations/20210311000000_InitialSchema.php b/config/Migrations/20210311000000_InitialSchema.php new file mode 100644 index 0000000..d1e2ed1 --- /dev/null +++ b/config/Migrations/20210311000000_InitialSchema.php @@ -0,0 +1,1464 @@ +execute('SET unique_checks=0; SET foreign_key_checks=0;'); + $this->execute("ALTER DATABASE CHARACTER SET 'utf8mb4';"); + $this->execute("ALTER DATABASE COLLATE='utf8mb4_general_ci';"); + $this->table('broods', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'id', + ]) + ->addColumn('name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('url', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'name', + ]) + ->addColumn('description', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'url', + ]) + ->addColumn('organisation_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'description', + ]) + ->addColumn('trusted', 'boolean', [ + 'null' => true, + 'default' => null, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'organisation_id', + ]) + ->addColumn('pull', 'boolean', [ + 'null' => true, + 'default' => null, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'trusted', + ]) + ->addColumn('skip_proxy', 'boolean', [ + 'null' => true, + 'default' => null, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'pull', + ]) + ->addColumn('authkey', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'skip_proxy', + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->addIndex(['name'], [ + 'name' => 'name', + 'unique' => false, + ]) + ->addIndex(['url'], [ + 'name' => 'url', + 'unique' => false, + ]) + ->addIndex(['authkey'], [ + 'name' => 'authkey', + 'unique' => false, + ]) + ->addIndex(['organisation_id'], [ + 'name' => 'organisation_id', + 'unique' => false, + ]) + ->addForeignKey('organisation_id', 'organisations', 'id', [ + 'constraint' => 'broods_ibfk_1', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->create(); + $this->table('sharing_groups', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'id', + ]) + ->addColumn('name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('releasability', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'name', + ]) + ->addColumn('description', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'releasability', + ]) + ->addColumn('organisation_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'description', + ]) + ->addColumn('user_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'organisation_id', + ]) + ->addColumn('active', 'boolean', [ + 'null' => true, + 'default' => '1', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'user_id', + ]) + ->addColumn('local', 'boolean', [ + 'null' => true, + 'default' => '1', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'active', + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->addIndex(['user_id'], [ + 'name' => 'user_id', + 'unique' => false, + ]) + ->addIndex(['organisation_id'], [ + 'name' => 'organisation_id', + 'unique' => false, + ]) + ->addIndex(['name'], [ + 'name' => 'name', + 'unique' => false, + ]) + ->create(); + $this->table('alignment_tags', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('alignment_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'id', + ]) + ->addColumn('tag_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'alignment_id', + ]) + ->addIndex(['alignment_id'], [ + 'name' => 'alignment_id', + 'unique' => false, + ]) + ->addIndex(['tag_id'], [ + 'name' => 'tag_id', + 'unique' => false, + ]) + ->addForeignKey('alignment_id', 'alignments', 'id', [ + 'constraint' => 'alignment_tags_ibfk_1', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('tag_id', 'tags', 'id', [ + 'constraint' => 'alignment_tags_ibfk_10', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('alignment_id', 'alignments', 'id', [ + 'constraint' => 'alignment_tags_ibfk_11', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('tag_id', 'tags', 'id', [ + 'constraint' => 'alignment_tags_ibfk_12', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('tag_id', 'tags', 'id', [ + 'constraint' => 'alignment_tags_ibfk_2', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('alignment_id', 'alignments', 'id', [ + 'constraint' => 'alignment_tags_ibfk_3', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('tag_id', 'tags', 'id', [ + 'constraint' => 'alignment_tags_ibfk_4', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('alignment_id', 'alignments', 'id', [ + 'constraint' => 'alignment_tags_ibfk_5', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('tag_id', 'tags', 'id', [ + 'constraint' => 'alignment_tags_ibfk_6', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('alignment_id', 'alignments', 'id', [ + 'constraint' => 'alignment_tags_ibfk_7', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('tag_id', 'tags', 'id', [ + 'constraint' => 'alignment_tags_ibfk_8', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('alignment_id', 'alignments', 'id', [ + 'constraint' => 'alignment_tags_ibfk_9', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->create(); + $this->table('meta_templates', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('scope', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'id', + ]) + ->addColumn('name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'scope', + ]) + ->addColumn('namespace', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'name', + ]) + ->addColumn('description', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'namespace', + ]) + ->addColumn('version', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'description', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'version', + ]) + ->addColumn('source', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('enabled', 'boolean', [ + 'null' => true, + 'default' => '0', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'source', + ]) + ->addColumn('is_default', 'boolean', [ + 'null' => false, + 'default' => '0', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'enabled', + ]) + ->addIndex(['scope'], [ + 'name' => 'scope', + 'unique' => false, + ]) + ->addIndex(['source'], [ + 'name' => 'source', + 'unique' => false, + ]) + ->addIndex(['name'], [ + 'name' => 'name', + 'unique' => false, + ]) + ->addIndex(['namespace'], [ + 'name' => 'namespace', + 'unique' => false, + ]) + ->addIndex(['version'], [ + 'name' => 'version', + 'unique' => false, + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->create(); + $this->table('individuals', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'id', + ]) + ->addColumn('email', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('first_name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'email', + ]) + ->addColumn('last_name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'first_name', + ]) + ->addColumn('position', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'last_name', + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->addIndex(['email'], [ + 'name' => 'email', + 'unique' => false, + ]) + ->addIndex(['first_name'], [ + 'name' => 'first_name', + 'unique' => false, + ]) + ->addIndex(['last_name'], [ + 'name' => 'last_name', + 'unique' => false, + ]) + ->create(); + $this->table('organisations', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'id', + ]) + ->addColumn('name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('url', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'name', + ]) + ->addColumn('nationality', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'url', + ]) + ->addColumn('sector', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'nationality', + ]) + ->addColumn('type', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'sector', + ]) + ->addColumn('contacts', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'type', + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->addIndex(['name'], [ + 'name' => 'name', + 'unique' => false, + ]) + ->addIndex(['url'], [ + 'name' => 'url', + 'unique' => false, + ]) + ->addIndex(['nationality'], [ + 'name' => 'nationality', + 'unique' => false, + ]) + ->addIndex(['sector'], [ + 'name' => 'sector', + 'unique' => false, + ]) + ->addIndex(['type'], [ + 'name' => 'type', + 'unique' => false, + ]) + ->create(); + $this->table('encryption_keys', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'id', + ]) + ->addColumn('type', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('encryption_key', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'type', + ]) + ->addColumn('revoked', 'boolean', [ + 'null' => true, + 'default' => null, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'encryption_key', + ]) + ->addColumn('expires', 'integer', [ + 'null' => true, + 'default' => null, + 'limit' => '10', + 'signed' => false, + 'after' => 'revoked', + ]) + ->addColumn('owner_id', 'integer', [ + 'null' => true, + 'default' => null, + 'limit' => '10', + 'signed' => false, + 'after' => 'expires', + ]) + ->addColumn('owner_type', 'string', [ + 'null' => false, + 'limit' => 20, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'owner_id', + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->addIndex(['type'], [ + 'name' => 'type', + 'unique' => false, + ]) + ->addIndex(['expires'], [ + 'name' => 'expires', + 'unique' => false, + ]) + ->create(); + $this->table('meta_fields', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('scope', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'id', + ]) + ->addColumn('parent_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'scope', + ]) + ->addColumn('field', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'parent_id', + ]) + ->addColumn('value', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'field', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'value', + ]) + ->addColumn('meta_template_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'uuid', + ]) + ->addColumn('meta_template_field_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'meta_template_id', + ]) + ->addColumn('is_default', 'boolean', [ + 'null' => false, + 'default' => '0', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'meta_template_field_id', + ]) + ->addIndex(['scope'], [ + 'name' => 'scope', + 'unique' => false, + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->addIndex(['parent_id'], [ + 'name' => 'parent_id', + 'unique' => false, + ]) + ->addIndex(['field'], [ + 'name' => 'field', + 'unique' => false, + ]) + ->addIndex(['value'], [ + 'name' => 'value', + 'unique' => false, + ]) + ->addIndex(['meta_template_id'], [ + 'name' => 'meta_template_id', + 'unique' => false, + ]) + ->addIndex(['meta_template_field_id'], [ + 'name' => 'meta_template_field_id', + 'unique' => false, + ]) + ->create(); + $this->table('audit_logs', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('created', 'datetime', [ + 'null' => false, + 'after' => 'id', + ]) + ->addColumn('user_id', 'integer', [ + 'null' => true, + 'default' => null, + 'limit' => '10', + 'signed' => false, + 'after' => 'created', + ]) + ->addColumn('authkey_id', 'integer', [ + 'null' => true, + 'default' => null, + 'limit' => '10', + 'signed' => false, + 'after' => 'user_id', + ]) + ->addColumn('request_ip', 'varbinary', [ + 'null' => true, + 'default' => null, + 'limit' => 16, + 'after' => 'authkey_id', + ]) + ->addColumn('request_type', 'integer', [ + 'null' => false, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'request_ip', + ]) + ->addColumn('request_id', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'request_type', + ]) + ->addColumn('request_action', 'string', [ + 'null' => false, + 'limit' => 20, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'request_id', + ]) + ->addColumn('model', 'string', [ + 'null' => false, + 'limit' => 80, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'request_action', + ]) + ->addColumn('model_id', 'integer', [ + 'null' => true, + 'default' => null, + 'limit' => '10', + 'signed' => false, + 'after' => 'model', + ]) + ->addColumn('model_title', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'model_id', + ]) + ->addColumn('change', 'blob', [ + 'null' => true, + 'default' => null, + 'limit' => MysqlAdapter::BLOB_REGULAR, + 'after' => 'model_title', + ]) + ->addIndex(['user_id'], [ + 'name' => 'user_id', + 'unique' => false, + ]) + ->addIndex(['request_ip'], [ + 'name' => 'request_ip', + 'unique' => false, + ]) + ->addIndex(['model'], [ + 'name' => 'model', + 'unique' => false, + ]) + ->addIndex(['request_action'], [ + 'name' => 'request_action', + 'unique' => false, + ]) + ->addIndex(['model_id'], [ + 'name' => 'model_id', + 'unique' => false, + ]) + ->addIndex(['created'], [ + 'name' => 'created', + 'unique' => false, + ]) + ->create(); + $this->table('organisation_encryption_keys', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('organisation_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'id', + ]) + ->addColumn('encryption_key_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'organisation_id', + ]) + ->addIndex(['organisation_id'], [ + 'name' => 'organisation_id', + 'unique' => false, + ]) + ->addIndex(['encryption_key_id'], [ + 'name' => 'encryption_key_id', + 'unique' => false, + ]) + ->addForeignKey('organisation_id', 'organisations', 'id', [ + 'constraint' => 'organisation_encryption_keys_ibfk_1', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('encryption_key_id', 'encryption_keys', 'id', [ + 'constraint' => 'organisation_encryption_keys_ibfk_2', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->create(); + $this->table('users', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'id', + ]) + ->addColumn('username', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('password', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'username', + ]) + ->addColumn('role_id', 'integer', [ + 'null' => false, + 'limit' => MysqlAdapter::INT_REGULAR, + 'signed' => false, + 'after' => 'password', + ]) + ->addColumn('individual_id', 'integer', [ + 'null' => false, + 'limit' => MysqlAdapter::INT_REGULAR, + 'signed' => false, + 'after' => 'role_id', + ]) + ->addColumn('disabled', 'boolean', [ + 'null' => true, + 'default' => '0', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'individual_id', + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->addIndex(['role_id'], [ + 'name' => 'role_id', + 'unique' => false, + ]) + ->addIndex(['individual_id'], [ + 'name' => 'individual_id', + 'unique' => false, + ]) + ->addForeignKey('role_id', 'roles', 'id', [ + 'constraint' => 'users_ibfk_1', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('individual_id', 'individuals', 'id', [ + 'constraint' => 'users_ibfk_2', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->create(); + $this->table('roles', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 40, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'id', + ]) + ->addColumn('name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'uuid', + ]) + ->addColumn('is_default', 'boolean', [ + 'null' => true, + 'default' => null, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'name', + ]) + ->addColumn('perm_admin', 'boolean', [ + 'null' => true, + 'default' => null, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'is_default', + ]) + ->addIndex(['name'], [ + 'name' => 'name', + 'unique' => false, + ]) + ->addIndex(['uuid'], [ + 'name' => 'uuid', + 'unique' => false, + ]) + ->create(); + $this->table('tags', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'id', + ]) + ->addColumn('description', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'name', + ]) + ->addColumn('colour', 'string', [ + 'null' => false, + 'limit' => 6, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'description', + ]) + ->addIndex(['name'], [ + 'name' => 'name', + 'unique' => false, + ]) + ->create(); + $this->table('individual_encryption_keys', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('individual_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'id', + ]) + ->addColumn('encryption_key_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'individual_id', + ]) + ->addIndex(['individual_id'], [ + 'name' => 'individual_id', + 'unique' => false, + ]) + ->addIndex(['encryption_key_id'], [ + 'name' => 'encryption_key_id', + 'unique' => false, + ]) + ->addForeignKey('individual_id', 'individuals', 'id', [ + 'constraint' => 'individual_encryption_keys_ibfk_1', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('encryption_key_id', 'encryption_keys', 'id', [ + 'constraint' => 'individual_encryption_keys_ibfk_2', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('individual_id', 'individuals', 'id', [ + 'constraint' => 'individual_encryption_keys_ibfk_3', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('encryption_key_id', 'encryption_keys', 'id', [ + 'constraint' => 'individual_encryption_keys_ibfk_4', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('individual_id', 'individuals', 'id', [ + 'constraint' => 'individual_encryption_keys_ibfk_5', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('encryption_key_id', 'encryption_keys', 'id', [ + 'constraint' => 'individual_encryption_keys_ibfk_6', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->create(); + $this->table('meta_template_fields', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('field', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'id', + ]) + ->addColumn('type', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'field', + ]) + ->addColumn('meta_template_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'type', + ]) + ->addColumn('regex', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'meta_template_id', + ]) + ->addColumn('multiple', 'boolean', [ + 'null' => true, + 'default' => '0', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'regex', + ]) + ->addColumn('enabled', 'boolean', [ + 'null' => true, + 'default' => '0', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'multiple', + ]) + ->addIndex(['meta_template_id'], [ + 'name' => 'meta_template_id', + 'unique' => false, + ]) + ->addIndex(['field'], [ + 'name' => 'field', + 'unique' => false, + ]) + ->addIndex(['type'], [ + 'name' => 'type', + 'unique' => false, + ]) + ->addForeignKey('meta_template_id', 'meta_templates', 'id', [ + 'constraint' => 'meta_template_id', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->create(); + $this->table('auth_keys', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('uuid', 'string', [ + 'null' => false, + 'limit' => 40, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'id', + ]) + ->addColumn('authkey', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 72, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'uuid', + ]) + ->addColumn('authkey_start', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 4, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'authkey', + ]) + ->addColumn('authkey_end', 'string', [ + 'null' => true, + 'default' => null, + 'limit' => 4, + 'collation' => 'ascii_general_ci', + 'encoding' => 'ascii', + 'after' => 'authkey_start', + ]) + ->addColumn('created', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'authkey_end', + ]) + ->addColumn('expiration', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'created', + ]) + ->addColumn('user_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'expiration', + ]) + ->addColumn('comment', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'user_id', + ]) + ->addIndex(['authkey_start'], [ + 'name' => 'authkey_start', + 'unique' => false, + ]) + ->addIndex(['authkey_end'], [ + 'name' => 'authkey_end', + 'unique' => false, + ]) + ->addIndex(['created'], [ + 'name' => 'created', + 'unique' => false, + ]) + ->addIndex(['expiration'], [ + 'name' => 'expiration', + 'unique' => false, + ]) + ->addIndex(['user_id'], [ + 'name' => 'user_id', + 'unique' => false, + ]) + ->create(); + $this->table('local_tools', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('name', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'id', + ]) + ->addColumn('connector', 'string', [ + 'null' => false, + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'name', + ]) + ->addColumn('settings', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'connector', + ]) + ->addColumn('exposed', 'boolean', [ + 'null' => false, + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'settings', + ]) + ->addColumn('description', 'text', [ + 'null' => true, + 'default' => null, + 'limit' => 65535, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'exposed', + ]) + ->addIndex(['name'], [ + 'name' => 'name', + 'unique' => false, + ]) + ->addIndex(['connector'], [ + 'name' => 'connector', + 'unique' => false, + ]) + ->create(); + $this->table('alignments', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('individual_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'id', + ]) + ->addColumn('organisation_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'individual_id', + ]) + ->addColumn('type', 'string', [ + 'null' => true, + 'default' => 'member', + 'limit' => 191, + 'collation' => 'utf8mb4_unicode_ci', + 'encoding' => 'utf8mb4', + 'after' => 'organisation_id', + ]) + ->addIndex(['individual_id'], [ + 'name' => 'individual_id', + 'unique' => false, + ]) + ->addIndex(['organisation_id'], [ + 'name' => 'organisation_id', + 'unique' => false, + ]) + ->addForeignKey('individual_id', 'individuals', 'id', [ + 'constraint' => 'alignments_ibfk_1', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->addForeignKey('organisation_id', 'organisations', 'id', [ + 'constraint' => 'alignments_ibfk_2', + 'update' => 'RESTRICT', + 'delete' => 'RESTRICT', + ]) + ->create(); + $this->table('sgo', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'identity' => 'enable', + ]) + ->addColumn('sharing_group_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'id', + ]) + ->addColumn('organisation_id', 'integer', [ + 'null' => false, + 'limit' => '10', + 'signed' => false, + 'after' => 'sharing_group_id', + ]) + ->addColumn('deleted', 'boolean', [ + 'null' => true, + 'default' => '0', + 'limit' => MysqlAdapter::INT_TINY, + 'after' => 'organisation_id', + ]) + ->addIndex(['sharing_group_id'], [ + 'name' => 'sharing_group_id', + 'unique' => false, + ]) + ->addIndex(['organisation_id'], [ + 'name' => 'organisation_id', + 'unique' => false, + ]) + ->create(); + $this->execute('SET unique_checks=1; SET foreign_key_checks=1;'); + } +} diff --git a/config/Migrations/schema-dump-default.lock b/config/Migrations/schema-dump-default.lock index 291060c..8c9385c 100644 Binary files a/config/Migrations/schema-dump-default.lock and b/config/Migrations/schema-dump-default.lock differ diff --git a/debian/install b/debian/install index 2ee55e9..12d7cc8 100755 --- a/debian/install +++ b/debian/install @@ -7,4 +7,3 @@ webroot /usr/share/php-cerebrate config /usr/share/php-cerebrate debian/cerebrate.local.conf /etc/apache2/sites-available/ debian/config.php /etc/cerebrate/ -INSTALL/mysql.sql => /usr/share/dbconfig-common/data/php-cerebrate/install/mysql diff --git a/docker/README.md b/docker/README.md index 1074c0c..9bf0154 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,20 +1,3 @@ -# Database init - -For the `docker-compose` setup to work you must initialize database with -what is in `../INSTALL/mysql.sql` - -``` -mkdir -p run/dbinit/ -cp ../INSTALL/mysql.sql run/dbinit/ -``` - -The MariaDB container has a volume mounted as follow -`- ./run/dbinit:/docker-entrypoint-initdb.d/:ro` - -So that on startup the container will source files in this directory to seed -the database. Once it's done the container will run normally and Cerebrate will -be able to roll its database migration scripts - # Actual data and volumes The actual database will be located in `./run/database` exposed with the diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 6aa0b73..821ab5f 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -5,7 +5,6 @@ services: restart: always volumes: - ./run/database:/var/lib/mysql - - ./run/dbinit:/docker-entrypoint-initdb.d/:ro environment: MARIADB_RANDOM_ROOT_PASSWORD: "yes" MYSQL_DATABASE: "cerebrate" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7107122..403e1e5 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,13 +1,12 @@ - + - - + + + + + + @@ -15,17 +14,17 @@ tests/TestCase/ - + + ./tests/TestCase/Controller + + + ./tests/TestCase/Api + - - - - - - - - + + + @@ -37,4 +36,4 @@ - + \ No newline at end of file diff --git a/src/Controller/ApiController.php b/src/Controller/ApiController.php new file mode 100644 index 0000000..65cd11c --- /dev/null +++ b/src/Controller/ApiController.php @@ -0,0 +1,19 @@ +set('url', $url); + } +} diff --git a/src/Controller/Component/ACLComponent.php b/src/Controller/Component/ACLComponent.php index b2393ad..fb51f49 100644 --- a/src/Controller/Component/ACLComponent.php +++ b/src/Controller/Component/ACLComponent.php @@ -194,6 +194,9 @@ class ACLComponent extends Component 'getBookmarks' => ['*'], 'saveBookmark' => ['*'], 'deleteBookmark' => ['*'] + ], + 'Api' => [ + 'index' => ['*'] ] ); diff --git a/src/Controller/Component/Navigation/sidemenu.php b/src/Controller/Component/Navigation/sidemenu.php index fcca213..56a0154 100644 --- a/src/Controller/Component/Navigation/sidemenu.php +++ b/src/Controller/Component/Navigation/sidemenu.php @@ -45,7 +45,12 @@ class Sidemenu { 'label' => __('Broods'), 'icon' => $this->iconTable['Broods'], 'url' => '/broods/index', - ] + ], + 'API' => [ + 'label' => __('API'), + 'icon' => $this->iconTable['API'], + 'url' => '/api/index', + ], ], __('Administration') => [ 'Roles' => [ diff --git a/src/Controller/Component/NavigationComponent.php b/src/Controller/Component/NavigationComponent.php index f0dd453..b8caee7 100644 --- a/src/Controller/Component/NavigationComponent.php +++ b/src/Controller/Component/NavigationComponent.php @@ -34,6 +34,7 @@ class NavigationComponent extends Component 'LocalTools' => 'tools', 'Instance' => 'server', 'Tags' => 'tags', + 'API' => 'code', ]; public function initialize(array $config): void diff --git a/src/Controller/Component/ParamHandlerComponent.php b/src/Controller/Component/ParamHandlerComponent.php index f76a578..e52e5f8 100644 --- a/src/Controller/Component/ParamHandlerComponent.php +++ b/src/Controller/Component/ParamHandlerComponent.php @@ -4,6 +4,7 @@ namespace App\Controller\Component; use Cake\Controller\Component; use Cake\Core\Configure; +use Cake\Http\Exception\MethodNotAllowedException; class ParamHandlerComponent extends Component { @@ -47,7 +48,7 @@ class ParamHandlerComponent extends Component return $this->isRest; } if ($this->request->is('json')) { - if (!empty($this->request->input()) && empty($this->request->input('json_decode'))) { + if (!empty((string)$this->request->getBody()) && empty($this->request->getParsedBody())) { throw new MethodNotAllowedException('Invalid JSON input. Make sure that the JSON input is a correctly formatted JSON string. This request has been blocked to avoid an unfiltered request.'); } $this->isRest = true; diff --git a/src/Model/Table/AuditLogsTable.php b/src/Model/Table/AuditLogsTable.php index efd6339..7987af6 100644 --- a/src/Model/Table/AuditLogsTable.php +++ b/src/Model/Table/AuditLogsTable.php @@ -163,8 +163,8 @@ class AuditLogsTable extends AppTable if ($this->user !== null) { return $this->user; } - - $this->user = ['id' => 0, /*'org_id' => 0, */'authkey_id' => 0, 'request_type' => self::REQUEST_TYPE_DEFAULT]; + + $this->user = ['id' => 0, /*'org_id' => 0, */'authkey_id' => 0, 'request_type' => self::REQUEST_TYPE_DEFAULT, 'name' => '']; $isShell = (php_sapi_name() === 'cli'); if ($isShell) { diff --git a/templates/Api/index.php b/templates/Api/index.php new file mode 100644 index 0000000..96be4b8 --- /dev/null +++ b/templates/Api/index.php @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tests/Fixture/AuthKeysFixture.php b/tests/Fixture/AuthKeysFixture.php new file mode 100644 index 0000000..24034f3 --- /dev/null +++ b/tests/Fixture/AuthKeysFixture.php @@ -0,0 +1,85 @@ +records = [ + [ + 'id' => self::ADMIN_API_ID, + 'uuid' => $faker->uuid(), + 'authkey' => $hasher->hash(self::ADMIN_API_KEY), + 'authkey_start' => substr(self::ADMIN_API_KEY, 0, 4), + 'authkey_end' => substr(self::ADMIN_API_KEY, -4), + 'expiration' => 0, + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'comment' => '', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::SYNC_API_ID, + 'uuid' => $faker->uuid(), + 'authkey' => $hasher->hash(self::SYNC_API_KEY), + 'authkey_start' => substr(self::SYNC_API_KEY, 0, 4), + 'authkey_end' => substr(self::SYNC_API_KEY, -4), + 'expiration' => 0, + 'user_id' => UsersFixture::USER_SYNC_ID, + 'comment' => '', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::ORG_ADMIN_API_ID, + 'uuid' => $faker->uuid(), + 'authkey' => $hasher->hash(self::ORG_ADMIN_API_KEY), + 'authkey_start' => substr(self::ORG_ADMIN_API_KEY, 0, 4), + 'authkey_end' => substr(self::ORG_ADMIN_API_KEY, -4), + 'expiration' => 0, + 'user_id' => UsersFixture::USER_ORG_ADMIN_ID, + 'comment' => '', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::REGULAR_USER_API_ID, + 'uuid' => $faker->uuid(), + 'authkey' => $hasher->hash(self::REGULAR_USER_API_KEY), + 'authkey_start' => substr(self::REGULAR_USER_API_KEY, 0, 4), + 'authkey_end' => substr(self::REGULAR_USER_API_KEY, -4), + 'expiration' => 0, + 'user_id' => UsersFixture::USER_REGULAR_USER_ID, + 'comment' => '', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ] + ]; + parent::init(); + } +} diff --git a/tests/Fixture/BroodsFixture.php b/tests/Fixture/BroodsFixture.php new file mode 100644 index 0000000..c329e6a --- /dev/null +++ b/tests/Fixture/BroodsFixture.php @@ -0,0 +1,72 @@ +records = [ + [ + 'id' => self::BROOD_A_ID, + 'uuid' => $faker->uuid(), + 'name' => 'Brood A', + 'url' => $faker->url, + 'description' => $faker->text, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'trusted' => true, + 'pull' => true, + 'skip_proxy' => true, + 'authkey' => self::BROOD_A_API_KEY, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::BROOD_B_ID, + 'uuid' => $faker->uuid(), + 'name' => 'Brood A', + 'url' => $faker->url, + 'description' => $faker->text, + 'organisation_id' => OrganisationsFixture::ORGANISATION_B_ID, + 'trusted' => true, + 'pull' => true, + 'skip_proxy' => true, + 'authkey' => self::BROOD_B_API_KEY, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::BROOD_WIREMOCK_ID, + 'uuid' => $faker->uuid(), + 'name' => 'wiremock', + 'url' => 'http://localhost:8080', + 'description' => $faker->text, + 'organisation_id' => OrganisationsFixture::ORGANISATION_B_ID, + 'trusted' => true, + 'pull' => true, + 'skip_proxy' => true, + 'authkey' => self::BROOD_WIREMOCK_API_KEY, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ] + ]; + parent::init(); + } +} diff --git a/tests/Fixture/EncryptionKeysFixture.php b/tests/Fixture/EncryptionKeysFixture.php new file mode 100644 index 0000000..a9d6c27 --- /dev/null +++ b/tests/Fixture/EncryptionKeysFixture.php @@ -0,0 +1,133 @@ +records = [ + [ + 'id' => self::ENCRYPTION_KEY_ORG_A_ID, + 'uuid' => $faker->uuid(), + 'type' => self::TYPE_PGP, + 'encryption_key' => $this->getPublicKey(self::KEY_TYPE_EDCH), + 'revoked' => false, + 'expires' => null, + 'owner_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'owner_model' => 'Organisation', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::ENCRYPTION_KEY_ORG_B_ID, + 'uuid' => $faker->uuid(), + 'type' => self::TYPE_PGP, + 'encryption_key' => $this->getPublicKey(self::KEY_TYPE_EDCH), + 'revoked' => false, + 'expires' => null, + 'owner_id' => OrganisationsFixture::ORGANISATION_B_ID, + 'owner_model' => 'Organisation', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + ]; + parent::init(); + } + + public static function getPublicKey(string $type): string + { + switch ($type) { + case self::KEY_TYPE_EDCH: + return <<records = [ + [ + 'id' => self::INBOX_USER_REGISTRATION_ID, + 'uuid' => $faker->uuid(), + 'scope' => 'User', + 'action' => 'Registration', + 'title' => 'User account creation requested for foo@bar.com', + 'origin' => '::1', + 'comment' => null, + 'description' => 'Handle user account for this cerebrate instance', + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'data' => [ + 'email' => 'foo@bar.com', + 'password' => '$2y$10$dr5C0MWgBx1723yyws0HPudTqHz4k8wJ1PQ1ApVkNuH64LuZAr\/ve', + ], + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::INBOX_INCOMING_CONNECTION_REQUEST_ID, + 'uuid' => $faker->uuid(), + 'scope' => 'LocalTool', + 'action' => 'IncomingConnectionRequest', + 'title' => 'Request for MISP Inter-connection', + 'origin' => 'http://127.0.0.1', + 'comment' => null, + 'description' => 'Handle Phase I of inter-connection when another cerebrate instance performs the request.', + 'user_id' => UsersFixture::USER_ORG_ADMIN_ID, + 'data' => [ + 'connectorName' => 'MispConnector', + 'cerebrateURL' => 'http://127.0.0.1', + 'local_tool_id' => 1, + 'remote_tool_id' => 1, + ], + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + ]; + parent::init(); + } +} diff --git a/tests/Fixture/IndividualsFixture.php b/tests/Fixture/IndividualsFixture.php new file mode 100644 index 0000000..13aecdf --- /dev/null +++ b/tests/Fixture/IndividualsFixture.php @@ -0,0 +1,77 @@ +records = [ + [ + 'id' => self::INDIVIDUAL_ADMIN_ID, + 'uuid' => $faker->uuid(), + 'email' => $faker->email(), + 'first_name' => $faker->firstName, + 'last_name' => $faker->lastName, + 'position' => 'admin', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::INDIVIDUAL_SYNC_ID, + 'uuid' => $faker->uuid(), + 'email' => $faker->email(), + 'first_name' => $faker->firstName, + 'last_name' => $faker->lastName, + 'position' => 'sync', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::INDIVIDUAL_ORG_ADMIN_ID, + 'uuid' => $faker->uuid(), + 'email' => $faker->email(), + 'first_name' => $faker->firstName, + 'last_name' => $faker->lastName, + 'position' => 'org_admin', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::INDIVIDUAL_REGULAR_USER_ID, + 'uuid' => $faker->uuid(), + 'email' => $faker->email(), + 'first_name' => $faker->firstName, + 'last_name' => $faker->lastName, + 'position' => 'user', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::INDIVIDUAL_A_ID, + 'uuid' => $faker->uuid(), + 'email' => $faker->email(), + 'first_name' => $faker->firstName, + 'last_name' => $faker->lastName, + 'position' => 'user', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ] + ]; + parent::init(); + } +} diff --git a/tests/Fixture/OrganisationsFixture.php b/tests/Fixture/OrganisationsFixture.php new file mode 100644 index 0000000..8531d0c --- /dev/null +++ b/tests/Fixture/OrganisationsFixture.php @@ -0,0 +1,48 @@ +records = [ + [ + 'id' => self::ORGANISATION_A_ID, + 'uuid' => $faker->uuid(), + 'name' => 'Organisation A', + 'url' => $faker->url, + 'nationality' => $faker->countryCode, + 'sector' => 'IT', + 'type' => '', + 'contacts' => '', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::ORGANISATION_B_ID, + 'uuid' => $faker->uuid(), + 'name' => 'Organisation B', + 'url' => $faker->url, + 'nationality' => $faker->countryCode, + 'sector' => 'IT', + 'type' => '', + 'contacts' => '', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ] + ]; + parent::init(); + } +} diff --git a/tests/Fixture/RolesFixture.php b/tests/Fixture/RolesFixture.php new file mode 100644 index 0000000..1230e8f --- /dev/null +++ b/tests/Fixture/RolesFixture.php @@ -0,0 +1,62 @@ +records = [ + [ + 'id' => self::ROLE_ADMIN_ID, + 'uuid' => $faker->uuid(), + 'name' => 'admin', + 'is_default' => false, + 'perm_admin' => true, + 'perm_sync' => false, + 'perm_org_admin' => false + ], + [ + 'id' => self::ROLE_SYNC_ID, + 'uuid' => $faker->uuid(), + 'name' => 'sync', + 'is_default' => false, + 'perm_admin' => false, + 'perm_sync' => true, + 'perm_org_admin' => false + ], + [ + 'id' => self::ROLE_ORG_ADMIN_ID, + 'uuid' => $faker->uuid(), + 'name' => 'org_admin', + 'is_default' => false, + 'perm_admin' => false, + 'perm_sync' => false, + 'perm_org_admin' => true + ], + [ + 'id' => self::ROLE_REGULAR_USER_ID, + 'uuid' => $faker->uuid(), + 'name' => 'user', + 'is_default' => true, + 'perm_admin' => false, + 'perm_sync' => false, + 'perm_org_admin' => false + ] + ]; + parent::init(); + } +} diff --git a/tests/Fixture/SharingGroupsFixture.php b/tests/Fixture/SharingGroupsFixture.php new file mode 100644 index 0000000..79665b6 --- /dev/null +++ b/tests/Fixture/SharingGroupsFixture.php @@ -0,0 +1,50 @@ +records = [ + [ + 'id' => self::SHARING_GROUP_A_ID, + 'uuid' => $faker->uuid(), + 'name' => 'Sharing Group A', + 'releasability' => 'Sharing Group A releasability', + 'description' => 'Sharing Group A description', + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'active' => true, + 'local' => true, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::SHARING_GROUP_B_ID, + 'uuid' => $faker->uuid(), + 'name' => 'Sharing Group B', + 'releasability' => 'Sharing Group B releasability', + 'description' => 'Sharing Group B description', + 'organisation_id' => OrganisationsFixture::ORGANISATION_B_ID, + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'active' => true, + 'local' => true, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + ]; + parent::init(); + } +} diff --git a/tests/Fixture/TagsTaggedsFixture.php b/tests/Fixture/TagsTaggedsFixture.php new file mode 100644 index 0000000..e2bf70f --- /dev/null +++ b/tests/Fixture/TagsTaggedsFixture.php @@ -0,0 +1,40 @@ +records = [ + [ + 'tag_id' => TagsTagsFixture::TAG_ORG_A_ID, + 'fk_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'fk_model' => 'Organisations', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'tag_id' => TagsTagsFixture::TAG_ORG_B_ID, + 'fk_id' => OrganisationsFixture::ORGANISATION_B_ID, + 'fk_model' => 'Organisations', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + ]; + parent::init(); + } +} diff --git a/tests/Fixture/TagsTagsFixture.php b/tests/Fixture/TagsTagsFixture.php new file mode 100644 index 0000000..002bc9e --- /dev/null +++ b/tests/Fixture/TagsTagsFixture.php @@ -0,0 +1,87 @@ +records = [ + [ + 'id' => self::TAG_RED_ID, + 'name' => 'red', + 'namespace' => null, + 'predicate' => null, + 'value' => null, + 'colour' => 'FF0000', + 'counter' => 0, + 'text_colour' => 'red', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::TAG_GREEN_ID, + 'name' => 'green', + 'namespace' => null, + 'predicate' => null, + 'value' => null, + 'colour' => '00FF00', + 'counter' => 0, + 'text_colour' => 'green', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::TAG_BLUE_ID, + 'name' => 'blue', + 'namespace' => null, + 'predicate' => null, + 'value' => null, + 'colour' => '0000FF', + 'counter' => 0, + 'text_colour' => 'blue', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::TAG_ORG_A_ID, + 'name' => 'org-a', + 'namespace' => null, + 'predicate' => null, + 'value' => null, + 'colour' => '000000', + 'counter' => 0, + 'text_colour' => 'black', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::TAG_ORG_B_ID, + 'name' => 'org-b', + 'namespace' => null, + 'predicate' => null, + 'value' => null, + 'colour' => '000000', + 'counter' => 0, + 'text_colour' => 'black', + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ] + ]; + parent::init(); + } +} diff --git a/tests/Fixture/UsersFixture.php b/tests/Fixture/UsersFixture.php new file mode 100644 index 0000000..ba35e7a --- /dev/null +++ b/tests/Fixture/UsersFixture.php @@ -0,0 +1,92 @@ +records = [ + [ + 'id' => self::USER_ADMIN_ID, + 'uuid' => $faker->uuid(), + 'username' => self::USER_ADMIN_USERNAME, + 'password' => $hasher->hash(self::USER_ADMIN_PASSWORD), + 'role_id' => RolesFixture::ROLE_ADMIN_ID, + 'individual_id' => IndividualsFixture::INDIVIDUAL_ADMIN_ID, + 'disabled' => 0, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::USER_SYNC_ID, + 'uuid' => $faker->uuid(), + 'username' => self::USER_SYNC_USERNAME, + 'password' => $hasher->hash(self::USER_SYNC_PASSWORD), + 'role_id' => RolesFixture::ROLE_SYNC_ID, + 'individual_id' => IndividualsFixture::INDIVIDUAL_SYNC_ID, + 'disabled' => 0, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::USER_ORG_ADMIN_ID, + 'uuid' => $faker->uuid(), + 'username' => self::USER_ORG_ADMIN_USERNAME, + 'password' => $hasher->hash(self::USER_ORG_ADMIN_PASSWORD), + 'role_id' => RolesFixture::ROLE_ORG_ADMIN_ID, + 'individual_id' => IndividualsFixture::INDIVIDUAL_ORG_ADMIN_ID, + 'disabled' => 0, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::USER_REGULAR_USER_ID, + 'uuid' => $faker->uuid(), + 'username' => self::USER_REGULAR_USER_USERNAME, + 'password' => $hasher->hash(self::USER_REGULAR_USER_PASSWORD), + 'role_id' => RolesFixture::ROLE_REGULAR_USER_ID, + 'individual_id' => IndividualsFixture::INDIVIDUAL_REGULAR_USER_ID, + 'disabled' => 0, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ] + ]; + parent::init(); + } +} diff --git a/tests/Helper/ApiTestTrait.php b/tests/Helper/ApiTestTrait.php new file mode 100644 index 0000000..a55268c --- /dev/null +++ b/tests/Helper/ApiTestTrait.php @@ -0,0 +1,294 @@ +initializeOpenApiValidator(); + } + + public function setAuthToken(string $authToken): void + { + $this->_authToken = $authToken; + + // somehow this is not set automatically in test environment + $_SERVER['HTTP_AUTHORIZATION'] = $authToken; + + $this->configRequest([ + 'headers' => [ + 'Accept' => 'application/json', + 'Authorization' => $this->_authToken, + 'Content-Type' => 'application/json' + ] + ]); + } + + /** + * Skip OpenAPI validations. + * + * @return void + */ + public function skipOpenApiValidations(): void + { + $this->_skipOpenApiValidations = true; + } + + public function assertResponseContainsArray(array $expected): void + { + $responseArray = json_decode((string)$this->_response->getBody(), true); + throw new NotImplementedException('TODO: see codeception seeResponseContainsJson()'); + } + + /** + * Load OpenAPI specification validator + * + * @return void + */ + public function initializeOpenApiValidator(): void + { + if (!$this->_skipOpenApiValidations) { + $this->_validator = Configure::read('App.OpenAPIValidator'); + if ($this->_validator === null) { + throw new \Exception('OpenAPI validator is not configured'); + } + } + } + + /** + * Validates the API request against the OpenAPI spec + * + * @return void + */ + public function assertRequestMatchesOpenApiSpec(): void + { + $this->_validator->getRequestValidator()->validate($this->_psrRequest); + } + + /** + * Validates the API response against the OpenAPI spec + * + * @param string $path The path to the API endpoint + * @param string $method The HTTP method used to call the endpoint + * @return void + */ + public function assertResponseMatchesOpenApiSpec(string $endpoint, string $method = 'get'): void + { + $address = new OperationAddress($endpoint, $method); + $this->_validator->getResponseValidator()->validate($address, $this->_response); + } + + /** + * Validates a record exists in the database + * + * @param string $table The table name + * @param array $conditions The conditions to check + * @return void + * @throws \Exception + * @throws \Cake\Datasource\Exception\RecordNotFoundException + * + * @see https://book.cakephp.org/4/en/orm-query-builder.html + */ + public function assertDbRecordExists(string $table, array $conditions): void + { + $record = $this->getTableLocator()->get($table)->find()->where($conditions)->first(); + if (!$record) { + throw new \PHPUnit\Framework\AssertionFailedError("Record not found in table '$table' with conditions: " . json_encode($conditions)); + } + $this->assertNotEmpty($record); + } + + /** + * Validates a record do not exists in the database + * + * @param string $table The table name + * @param array $conditions The conditions to check + * @return void + * @throws \Exception + * @throws \Cake\Datasource\Exception\RecordNotFoundException + * + * @see https://book.cakephp.org/4/en/orm-query-builder.html + */ + public function assertDbRecordNotExists(string $table, array $conditions): void + { + $record = $this->getTableLocator()->get($table)->find()->where($conditions)->first(); + if ($record) { + throw new \PHPUnit\Framework\AssertionFailedError("Record found in table '$table' with conditions: " . json_encode($conditions)); + } + $this->assertEmpty($record); + } + + /** + * Parses the response body and returns the decoded JSON + * + * @return array + * @throws \Exception + */ + public function getJsonResponseAsArray(): array + { + if ($this->_response->getHeaders()['Content-Type'][0] !== 'application/json') { + throw new \Exception('The response is not a JSON response'); + } + + return json_decode((string)$this->_response->getBody(), true); + } + + /** + * Gets a database records as an array + * + * @param string $table The table name + * @param array $conditions The conditions to check + * @return array + * @throws \Cake\Datasource\Exception\RecordNotFoundException + */ + public function getRecordFromDb(string $table, array $conditions): array + { + return $this->getTableLocator()->get($table)->find()->where($conditions)->first()->toArray(); + } + + /** + * This method intercepts IntegrationTestTrait::_buildRequest() + * in the quest to get a PSR-7 request object and saves it for + * later inspection, also validates it against the OpenAPI spec. + * @see \Cake\TestSuite\IntegrationTestTrait::_buildRequest() + * + * @param string $url The URL + * @param string $method The HTTP method + * @param array|string $data The request data. + * @return array The request context + */ + protected function _buildRequest(string $url, $method, $data = []): array + { + $spec = $this->_buildRequestOriginal($url, $method, $data); + + $this->_psrRequest = $this->_createPsr7RequestFromSpec($spec); + + // Validate request against OpenAPI spec + if (!$this->_skipOpenApiValidations) { + try { + $this->assertRequestMatchesOpenApiSpec(); + } catch (\Exception $exception) { + $this->fail($exception->getMessage()); + } + } else { + $this->addWarning( + sprintf( + 'OpenAPI spec validations skipped for request [%s]%s.', + $this->_psrRequest->getMethod(), + $this->_psrRequest->getPath() + ) + ); + } + + return $spec; + } + + /** + * This method intercepts IntegrationTestTrait::_buildRequest() + * and validates the response against the OpenAPI spec. + * + * @see \Cake\TestSuite\IntegrationTestTrait::_sendRequest() + * + * @param array|string $url The URL + * @param string $method The HTTP method + * @param array|string $data The request data. + * @return void + * @throws \PHPUnit\Exception|\Throwable + */ + protected function _sendRequest($url, $method, $data = []): void + { + // Adding Content-Type: application/json $this->configRequest() prevents this from happening somehow + if (in_array($method, ['POST', 'PATCH', 'PUT']) && $this->_request['headers']['Content-Type'] === 'application/json') { + $data = json_encode($data); + } + + $this->_sendRequestOriginal($url, $method, $data); + + // Validate response against OpenAPI spec + if (!$this->_skipOpenApiValidations) { + $this->assertResponseMatchesOpenApiSpec( + $this->_psrRequest->getPath(), + strtolower($this->_psrRequest->getMethod()) + ); + } else { + $this->addWarning( + sprintf( + 'OpenAPI spec validations skipped for response of [%s]%s.', + $this->_psrRequest->getMethod(), + $this->_psrRequest->getPath() + ) + ); + } + } + + /** + * Create a PSR-7 request from the request spec. + * @see \Cake\TestSuite\MiddlewareDispatcher::_createRequest() + * + * @param array $spec The request spec. + * @return \Cake\Http\ServerRequest + */ + private function _createPsr7RequestFromSpec(array $spec): ServerRequest + { + if (isset($spec['input'])) { + $spec['post'] = []; + $spec['environment']['CAKEPHP_INPUT'] = $spec['input']; + } + $environment = array_merge( + array_merge($_SERVER, ['REQUEST_URI' => $spec['url']]), + $spec['environment'] + ); + if (strpos($environment['PHP_SELF'], 'phpunit') !== false) { + $environment['PHP_SELF'] = '/'; + } + return ServerRequestFactory::fromGlobals( + $environment, + $spec['query'], + $spec['post'], + $spec['cookies'], + $spec['files'] + ); + } +} diff --git a/tests/Helper/WireMockTestTrait.php b/tests/Helper/WireMockTestTrait.php new file mode 100644 index 0000000..9b42f7e --- /dev/null +++ b/tests/Helper/WireMockTestTrait.php @@ -0,0 +1,49 @@ + */ + private $config = [ + 'hostname' => 'localhost', + 'port' => 8080 + ]; + + public function initializeWireMock(): void + { + $this->wiremock = WireMock::create( + $_ENV['WIREMOCK_HOST'] ?? $this->config['hostname'], + $_ENV['WIREMOCK_PORT'] ?? $this->config['port'] + ); + + if (!$this->wiremock->isAlive()) { + throw new Exception('Failed to connect to WireMock server.'); + } + + $this->clearWireMockStubs(); + } + + public function clearWireMockStubs(): void + { + $this->wiremock->resetToDefault(); + } + + public function getWireMock(): WireMock + { + return $this->wiremock; + } + + public function getWireMockBaseUrl(): string + { + return sprintf('http://%s:%s', $this->config['hostname'], $this->config['port']); + } +} diff --git a/tests/Helper/wiremock/start.sh b/tests/Helper/wiremock/start.sh new file mode 100644 index 0000000..60b8197 --- /dev/null +++ b/tests/Helper/wiremock/start.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Adapted from @rowanhill wiremock start.sh script +# https://github.com/rowanhill/wiremock-php/blob/master/wiremock/start.sh + +cd ./tmp/ + +instance=1 +port=8080 +if [ $# -gt 0 ]; then + instance=$1 + port=$2 +fi +pidFile=wiremock.$instance.pid +logFile=wiremock.$instance.log + +# Ensure WireMock isn't already running +if [ -e $pidFile ]; then + echo WireMock is already started: see process `cat $pidFile` 1>&2 + exit 0 +fi + +# Download the wiremock jar if we need it +if ! [ -e wiremock-standalone.jar ]; then + echo WireMock standalone JAR missing. Downloading. + curl https://repo1.maven.org/maven2/com/github/tomakehurst/wiremock-jre8-standalone/2.32.0/wiremock-jre8-standalone-2.32.0.jar -o wiremock-standalone.jar + status=$? + if [ ${status} -ne 0 ]; then + echo curl could not download WireMock JAR 1>&2 + exit ${status} + fi +fi + +# Start WireMock in standalone mode (in a background process) and save its output to a log +java -jar wiremock-standalone.jar --port $port --root-dir $instance --disable-banner &> $logFile 2>&1 & +pgrep -f wiremock-standalone.jar > $pidFile + +echo WireMock $instance started on port $port \ No newline at end of file diff --git a/tests/Helper/wiremock/stop.sh b/tests/Helper/wiremock/stop.sh new file mode 100644 index 0000000..f9e3f9e --- /dev/null +++ b/tests/Helper/wiremock/stop.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Adapted from @rowanhill wiremock stop.sh script +# https://github.com/rowanhill/wiremock-php/blob/master/wiremock/stop.sh + +cd ./tmp/ + +instance=1 +if [ $# -gt 0 ]; then + instance=$1 +fi +pidFile=wiremock.$instance.pid + + +if [ -e $pidFile ]; then + kill -9 `cat $pidFile` + rm $pidFile +else + echo WireMock is not started 2>&1 + exit 1 +fi + +echo WireMock $instance stopped \ No newline at end of file diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..434f84a --- /dev/null +++ b/tests/README.md @@ -0,0 +1,123 @@ +# Testing +1. Add a `cerebrate_test` database to the database: +```mysql +CREATE DATABASE cerebrate_test; +GRANT ALL PRIVILEGES ON cerebrate_test.* to cerebrate@localhost; +FLUSH PRIVILEGES; +QUIT; +``` + +2. Add a the test database to your `config/app_local.php` config file and set `debug` mode to `true`. +```php +'debug' => true, +'Datasources' => [ + 'default' => [ + ... + ], + /* + * The test connection is used during the test suite. + */ + 'test' => [ + 'host' => 'localhost', + 'username' => 'cerebrate', + 'password' => 'cerebrate', + 'database' => 'cerebrate_test', + ], +], +``` + +## Runing the tests +``` +$ composer install +$ composer test +> sh ./tests/Helper/wiremock/start.sh +WireMock 1 started on port 8080 +> phpunit +[ * ] Running DB migrations, it may take some time ... + +The WireMock server is started ..... +port: 8080 +enable-browser-proxying: false +disable-banner: true +no-request-journal: false +verbose: false + +PHPUnit 8.5.22 by Sebastian Bergmann and contributors. + + +..... 5 / 5 (100%) + +Time: 11.61 seconds, Memory: 26.00 MB + +OK (5 tests, 15 assertions) +``` + +Running a specific suite: +``` +$ vendor/bin/phpunit --testsuite=api --testdox +``` +Available suites: +* `app`: runs all test suites +* `api`: runs only api tests +* `controller`: runs only controller tests +* _to be continued ..._ + +By default the database is re-generated before running the test suite, to skip this step and speed up the test run set the following env variable in `phpunit.xml`: +```xml + + ... + + +``` +## Extras +### WireMock +Some integration tests perform calls to external APIs, we use WireMock to mock the response of these API calls. + +To download and run WireMock run the following script in a separate terminal: + ``` + sh ./tests/Helper/wiremock/start.sh + ``` + +You can also run WireMock with docker, check the official docs: http://wiremock.org/docs/docker/ + +> NOTE: When running the tests with `composer test` WireMock is automatically started and stoped after the tests finish. + +The default `hostname` and `port` for WireMock are set in `phpunit.xml` as environment variables: +```xml + + ... + + + +``` +### Coverage +HTML: +``` +$ vendor/bin/phpunit --coverage-html tmp/coverage +``` + +XML: +``` +$ vendor/bin/phpunit --verbose --coverage-clover=coverage.xml +``` + +### OpenAPI validation +API tests can assert the API response matches the OpenAPI specification, after the request add this line: + +```php +$this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); +``` + +The default OpenAPI spec path is set in `phpunit.xml` as a environment variablea: +```xml + + ... + + +``` + +### Debugging tests +``` +$ export XDEBUG_CONFIG="idekey=IDEKEY" +$ phpunit +``` diff --git a/tests/TestCase/Api/AuthKeys/AddAuthKeyApiTest.php b/tests/TestCase/Api/AuthKeys/AddAuthKeyApiTest.php new file mode 100644 index 0000000..ca305e8 --- /dev/null +++ b/tests/TestCase/Api/AuthKeys/AddAuthKeyApiTest.php @@ -0,0 +1,72 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'authkey' => $faker->sha1, + 'expiration' => 0, + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'comment' => $faker->text + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"uuid": "%s"', $uuid)); + $this->assertDbRecordExists('AuthKeys', ['uuid' => $uuid]); + } + + public function testAddAdminAuthKeyNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'authkey' => $faker->sha1, + 'expiration' => 0, + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'comment' => $faker->text + ] + ); + + $this->assertResponseCode(404); + $this->addWarning('Should return 405 Method Not Allowed instead of 404 Not Found'); + $this->assertDbRecordNotExists('AuthKeys', ['uuid' => $uuid]); + } +} diff --git a/tests/TestCase/Api/AuthKeys/DeleteAuthKeyApiTest.php b/tests/TestCase/Api/AuthKeys/DeleteAuthKeyApiTest.php new file mode 100644 index 0000000..a621f37 --- /dev/null +++ b/tests/TestCase/Api/AuthKeys/DeleteAuthKeyApiTest.php @@ -0,0 +1,45 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, AuthKeysFixture::ADMIN_API_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('AuthKeys', ['id' => AuthKeysFixture::ADMIN_API_ID]); + } + + public function testDeleteOrgAdminAuthKeyNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, AuthKeysFixture::ORG_ADMIN_API_ID); + + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('AuthKeys', ['id' => AuthKeysFixture::ORG_ADMIN_API_ID]); + } +} diff --git a/tests/TestCase/Api/AuthKeys/IndexAuthKeysApiTest.php b/tests/TestCase/Api/AuthKeys/IndexAuthKeysApiTest.php new file mode 100644 index 0000000..0712480 --- /dev/null +++ b/tests/TestCase/Api/AuthKeys/IndexAuthKeysApiTest.php @@ -0,0 +1,42 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', AuthKeysFixture::ADMIN_API_ID)); + } + + public function testIndexDoesNotShowAdminAuthKeysAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseNotContains(sprintf('"id": %d', AuthKeysFixture::REGULAR_USER_API_KEY)); + } +} diff --git a/tests/TestCase/Api/Broods/AddBroodApiTest.php b/tests/TestCase/Api/Broods/AddBroodApiTest.php new file mode 100644 index 0000000..f064f61 --- /dev/null +++ b/tests/TestCase/Api/Broods/AddBroodApiTest.php @@ -0,0 +1,79 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'name' => 'Brood A', + 'url' => $faker->url, + 'description' => $faker->text, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'trusted' => true, + 'pull' => true, + 'skip_proxy' => true, + 'authkey' => $faker->sha1, + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"uuid": "%s"', $uuid)); + $this->assertDbRecordExists('Broods', ['uuid' => $uuid]); + } + + public function testAddBroodNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'name' => 'Brood A', + 'url' => $faker->url, + 'description' => $faker->text, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'trusted' => true, + 'pull' => true, + 'skip_proxy' => true, + 'authkey' => $faker->sha1, + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('Broods', ['uuid' => $uuid]); + } +} diff --git a/tests/TestCase/Api/Broods/DeleteBroodApiTest.php b/tests/TestCase/Api/Broods/DeleteBroodApiTest.php new file mode 100644 index 0000000..420bf01 --- /dev/null +++ b/tests/TestCase/Api/Broods/DeleteBroodApiTest.php @@ -0,0 +1,46 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, BroodsFixture::BROOD_A_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('Broods', ['id' => BroodsFixture::BROOD_A_ID]); + } + + public function testDeleteBroodNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, BroodsFixture::BROOD_A_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('Broods', ['id' => BroodsFixture::BROOD_A_ID]); + } +} diff --git a/tests/TestCase/Api/Broods/EditBroodApiTest.php b/tests/TestCase/Api/Broods/EditBroodApiTest.php new file mode 100644 index 0000000..ad5d70b --- /dev/null +++ b/tests/TestCase/Api/Broods/EditBroodApiTest.php @@ -0,0 +1,71 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, BroodsFixture::BROOD_A_ID); + $this->put( + $url, + [ + 'name' => 'Test Brood 4321', + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists( + 'Broods', + [ + 'id' => BroodsFixture::BROOD_A_ID, + 'name' => 'Test Brood 4321', + ] + ); + } + + public function testEditBroodNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, BroodsFixture::BROOD_B_ID); + $this->put( + $url, + [ + 'name' => 'Test Brood 1234' + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists( + 'Broods', + [ + 'id' => BroodsFixture::BROOD_B_ID, + 'name' => 'Test Brood 1234' + ] + ); + } +} diff --git a/tests/TestCase/Api/Broods/IndexBroodsApiTest.php b/tests/TestCase/Api/Broods/IndexBroodsApiTest.php new file mode 100644 index 0000000..d70bc53 --- /dev/null +++ b/tests/TestCase/Api/Broods/IndexBroodsApiTest.php @@ -0,0 +1,35 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', BroodsFixture::BROOD_A_ID)); + } +} diff --git a/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php b/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php new file mode 100644 index 0000000..ee1117f --- /dev/null +++ b/tests/TestCase/Api/Broods/TestBroodConnectionApiTest.php @@ -0,0 +1,68 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->initializeWireMock(); + $this->mockCerebrateStatusResponse(); + + $url = sprintf('%s/%d', self::ENDPOINT, BroodsFixture::BROOD_WIREMOCK_ID); + $this->get($url); + + $this->getWireMock()->verify( + WireMock::getRequestedFor(WireMock::urlEqualTo('/instance/status.json')) + ->withHeader('Content-Type', WireMock::equalTo('application/json')) + ->withHeader('Authorization', WireMock::equalTo(BroodsFixture::BROOD_WIREMOCK_API_KEY)) + ); + + $this->assertResponseOk(); + $this->assertResponseContains('"user": "wiremock"'); + } + + private function mockCerebrateStatusResponse(): \WireMock\Stubbing\StubMapping + { + return $this->getWireMock()->stubFor( + WireMock::get(WireMock::urlEqualTo('/instance/status.json')) + ->willReturn(WireMock::aResponse() + ->withHeader('Content-Type', 'application/json') + ->withBody((string)json_encode([ + "version" => "0.1", + "application" => "Cerebrate", + "user" => [ + "id" => 1, + "username" => "wiremock", + "role" => [ + "id" => 1 + ] + ] + ]))) + ); + } +} diff --git a/tests/TestCase/Api/Broods/ViewBroodApiTest.php b/tests/TestCase/Api/Broods/ViewBroodApiTest.php new file mode 100644 index 0000000..bd9e5a7 --- /dev/null +++ b/tests/TestCase/Api/Broods/ViewBroodApiTest.php @@ -0,0 +1,36 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, BroodsFixture::BROOD_A_ID); + $this->get($url); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', BroodsFixture::BROOD_A_ID)); + } +} diff --git a/tests/TestCase/Api/EncryptionKeys/AddEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/AddEncryptionKeyApiTest.php new file mode 100644 index 0000000..00cc377 --- /dev/null +++ b/tests/TestCase/Api/EncryptionKeys/AddEncryptionKeyApiTest.php @@ -0,0 +1,76 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'type' => EncryptionKeysFixture::TYPE_PGP, + 'encryption_key' => EncryptionKeysFixture::getPublicKey(EncryptionKeysFixture::KEY_TYPE_EDCH), + 'revoked' => false, + 'expires' => null, + 'owner_id' => UsersFixture::USER_ADMIN_ID, + 'owner_model' => 'User' + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"uuid": "%s"', $uuid)); + $this->assertDbRecordExists('EncryptionKeys', ['uuid' => $uuid]); + } + + public function testAddAdminUserEncryptionKeyNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'type' => EncryptionKeysFixture::TYPE_PGP, + 'encryption_key' => EncryptionKeysFixture::getPublicKey(EncryptionKeysFixture::KEY_TYPE_EDCH), + 'revoked' => false, + 'expires' => null, + 'owner_id' => UsersFixture::USER_ADMIN_ID, + 'owner_model' => 'User' + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('EncryptionKeys', ['uuid' => $uuid]); + } +} diff --git a/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php new file mode 100644 index 0000000..6ae8143 --- /dev/null +++ b/tests/TestCase/Api/EncryptionKeys/DeleteEncryptionKeyApiTest.php @@ -0,0 +1,46 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('EncryptionKeys', ['id' => EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID]); + } + + public function testDeleteEncryptionKeyNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, EncryptionKeysFixture::ENCRYPTION_KEY_ORG_B_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('EncryptionKeys', ['id' => EncryptionKeysFixture::ENCRYPTION_KEY_ORG_B_ID]); + } +} diff --git a/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php new file mode 100644 index 0000000..2636fc1 --- /dev/null +++ b/tests/TestCase/Api/EncryptionKeys/EditEncryptionKeyApiTest.php @@ -0,0 +1,70 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID); + $this->put( + $url, + [ + 'revoked' => true, + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists( + 'EncryptionKeys', + [ + 'id' => EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID, + 'revoked' => true, + ] + ); + } + + public function testRevokeAdminEncryptionKeyNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, EncryptionKeysFixture::ENCRYPTION_KEY_ORG_B_ID); + $this->put( + $url, + [ + 'revoked' => true + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists( + 'EncryptionKeys', + [ + 'id' => EncryptionKeysFixture::ENCRYPTION_KEY_ORG_B_ID, + 'revoked' => true + ] + ); + } +} diff --git a/tests/TestCase/Api/EncryptionKeys/IndexEncryptionKeysApiTest.php b/tests/TestCase/Api/EncryptionKeys/IndexEncryptionKeysApiTest.php new file mode 100644 index 0000000..844336d --- /dev/null +++ b/tests/TestCase/Api/EncryptionKeys/IndexEncryptionKeysApiTest.php @@ -0,0 +1,36 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID)); + } +} diff --git a/tests/TestCase/Api/EncryptionKeys/ViewEncryptionKeyApiTest.php b/tests/TestCase/Api/EncryptionKeys/ViewEncryptionKeyApiTest.php new file mode 100644 index 0000000..de324fb --- /dev/null +++ b/tests/TestCase/Api/EncryptionKeys/ViewEncryptionKeyApiTest.php @@ -0,0 +1,36 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID); + $this->get($url); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', EncryptionKeysFixture::ENCRYPTION_KEY_ORG_A_ID)); + } +} diff --git a/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php b/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php new file mode 100644 index 0000000..8434d62 --- /dev/null +++ b/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php @@ -0,0 +1,70 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + // to avoid $this->request->clientIp() to return null + $_SERVER['REMOTE_ADDR'] = '::1'; + + $url = sprintf("%s/%s/%s", self::ENDPOINT, 'User', 'Registration'); + $this->post( + $url, + [ + 'email' => 'john@example.com', + 'password' => 'Password12345!' + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains('"email": "john@example.com"'); + $this->assertDbRecordExists( + 'Inbox', + [ + 'id' => 3, // hacky, but `data` is json string cannot verify the value because of the hashed password + 'scope' => 'User', + 'action' => 'Registration', + ] + ); + } + + public function testAddUserRegistrationInboxNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $url = sprintf("%s/%s/%s", self::ENDPOINT, 'User', 'Registration'); + $this->post( + $url, + [ + 'email' => 'john@example.com', + 'password' => 'Password12345!' + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('Inbox', ['id' => 3]); + } +} diff --git a/tests/TestCase/Api/Inbox/IndexInboxApiTest.php b/tests/TestCase/Api/Inbox/IndexInboxApiTest.php new file mode 100644 index 0000000..ae9c039 --- /dev/null +++ b/tests/TestCase/Api/Inbox/IndexInboxApiTest.php @@ -0,0 +1,36 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', InboxFixture::INBOX_USER_REGISTRATION_ID)); + $this->assertResponseContains(sprintf('"id": %d', InboxFixture::INBOX_INCOMING_CONNECTION_REQUEST_ID)); + } +} diff --git a/tests/TestCase/Api/Individuals/AddIndividualApiTest.php b/tests/TestCase/Api/Individuals/AddIndividualApiTest.php new file mode 100644 index 0000000..fcff319 --- /dev/null +++ b/tests/TestCase/Api/Individuals/AddIndividualApiTest.php @@ -0,0 +1,59 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->post( + self::ENDPOINT, + [ + 'email' => 'john@example.com', + 'first_name' => 'John', + 'last_name' => 'Doe', + 'position' => 'Security Analyst' + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains('"email": "john@example.com"'); + $this->assertDbRecordExists('Individuals', ['email' => 'john@example.com']); + } + + // public function testAddUserNotAllowedAsRegularUser(): void + // { + // $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + // $this->post( + // self::ENDPOINT, + // [ + // 'email' => 'john@example.com', + // 'first_name' => 'John', + // 'last_name' => 'Doe', + // 'position' => 'Security Analyst' + // ] + // ); + + // $this->assertResponseCode(405); + // $this->assertDbRecordNotExists('Individuals', ['email' => 'john@example.com']); + // } +} diff --git a/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php b/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php new file mode 100644 index 0000000..e5657aa --- /dev/null +++ b/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php @@ -0,0 +1,45 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, IndividualsFixture::INDIVIDUAL_A_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('Individuals', ['id' => IndividualsFixture::INDIVIDUAL_A_ID]); + } + + public function testDeleteIndividualNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, IndividualsFixture::INDIVIDUAL_ADMIN_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('Individuals', ['id' => IndividualsFixture::INDIVIDUAL_ADMIN_ID]); + } +} diff --git a/tests/TestCase/Api/Individuals/EditIndividualApiTest.php b/tests/TestCase/Api/Individuals/EditIndividualApiTest.php new file mode 100644 index 0000000..c888bba --- /dev/null +++ b/tests/TestCase/Api/Individuals/EditIndividualApiTest.php @@ -0,0 +1,61 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, IndividualsFixture::INDIVIDUAL_REGULAR_USER_ID); + $this->put( + $url, + [ + 'email' => 'foo@bar.com', + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists('Individuals', [ + 'id' => IndividualsFixture::INDIVIDUAL_REGULAR_USER_ID, + 'email' => 'foo@bar.com' + ]); + } + + public function testEditAnyIndividualNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, IndividualsFixture::INDIVIDUAL_ADMIN_ID); + $this->put( + $url, + [ + 'email' => 'foo@bar.com', + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('Individuals', [ + 'id' => IndividualsFixture::INDIVIDUAL_ADMIN_ID, + 'email' => 'foo@bar.com' + ]); + } +} diff --git a/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php b/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php new file mode 100644 index 0000000..e5c92ce --- /dev/null +++ b/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php @@ -0,0 +1,34 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', IndividualsFixture::INDIVIDUAL_ADMIN_ID)); + } +} diff --git a/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php b/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php new file mode 100644 index 0000000..d4b94d9 --- /dev/null +++ b/tests/TestCase/Api/Individuals/ViewIndividualApiTest.php @@ -0,0 +1,35 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, IndividualsFixture::INDIVIDUAL_ADMIN_ID); + $this->get($url); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', IndividualsFixture::INDIVIDUAL_ADMIN_ID)); + } +} diff --git a/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php b/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php new file mode 100644 index 0000000..5a47554 --- /dev/null +++ b/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php @@ -0,0 +1,73 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'name' => 'Test Organisation', + 'description' => $faker->text, + 'uuid' => $uuid, + 'url' => 'http://example.com', + 'nationality' => 'US', + 'sector' => 'sector', + 'type' => 'type', + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"uuid": "%s"', $uuid)); + $this->assertDbRecordExists('Organisations', ['uuid' => $uuid]); + } + + public function testAddOrganisationNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'name' => 'Test Organisation', + 'description' => $faker->text, + 'uuid' => $uuid, + 'url' => 'http://example.com', + 'nationality' => 'US', + 'sector' => 'sector', + 'type' => 'type', + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('Organisations', ['uuid' => $uuid]); + } +} diff --git a/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php new file mode 100644 index 0000000..efdaa5c --- /dev/null +++ b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php @@ -0,0 +1,46 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_B_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('Organisations', ['id' => OrganisationsFixture::ORGANISATION_B_ID]); + } + + public function testDeleteOrganisationNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_B_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('Organisations', ['id' => OrganisationsFixture::ORGANISATION_B_ID]); + } +} diff --git a/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php b/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php new file mode 100644 index 0000000..6d14f3c --- /dev/null +++ b/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php @@ -0,0 +1,69 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->put( + $url, + [ + 'name' => 'Test Organisation 4321', + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists( + 'Organisations', + [ + 'id' => OrganisationsFixture::ORGANISATION_A_ID, + 'name' => 'Test Organisation 4321', + ] + ); + } + + public function testEditOrganisationNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_B_ID); + $this->put( + $url, + [ + 'name' => 'Test Organisation 1234' + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists( + 'Organisations', + [ + 'id' => OrganisationsFixture::ORGANISATION_B_ID, + 'name' => 'Test Organisation 1234' + ] + ); + } +} diff --git a/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php b/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php new file mode 100644 index 0000000..a22e0f4 --- /dev/null +++ b/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php @@ -0,0 +1,35 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', OrganisationsFixture::ORGANISATION_A_ID)); + $this->assertResponseContains(sprintf('"id": %d', OrganisationsFixture::ORGANISATION_B_ID)); + } +} diff --git a/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php b/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php new file mode 100644 index 0000000..f8bd194 --- /dev/null +++ b/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php @@ -0,0 +1,74 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->post( + $url, + [ + 'tag_list' => "[\"red\"]" + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists( + 'TagsTagged', + [ + 'tag_id' => TagsTagsFixture::TAG_RED_ID, + 'fk_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'fk_model' => 'Organisations' + ] + ); + } + + public function testTagOrganisationNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->post( + $url, + [ + 'tag_list' => "[\"green\"]" + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists( + 'TagsTagged', + [ + 'tag_id' => TagsTagsFixture::TAG_GREEN_ID, + 'fk_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'fk_model' => 'Organisations' + ] + ); + } +} diff --git a/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php b/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php new file mode 100644 index 0000000..59f1bea --- /dev/null +++ b/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php @@ -0,0 +1,74 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->post( + $url, + [ + 'tag_list' => "[\"org-a\"]" + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists( + 'TagsTagged', + [ + 'tag_id' => TagsTagsFixture::TAG_ORG_A_ID, + 'fk_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'fk_model' => 'Organisations' + ] + ); + } + + public function testUntagOrganisationNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->post( + $url, + [ + 'tag_list' => "[\"org-a\"]" + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordExists( + 'TagsTagged', + [ + 'tag_id' => TagsTagsFixture::TAG_ORG_A_ID, + 'fk_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'fk_model' => 'Organisations' + ] + ); + } +} diff --git a/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php b/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php new file mode 100644 index 0000000..a9a728b --- /dev/null +++ b/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php @@ -0,0 +1,37 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->get($url); + + $this->assertResponseOk(); + $this->assertResponseContains('"name": "Organisation A"'); + } +} diff --git a/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php new file mode 100644 index 0000000..cbfebbb --- /dev/null +++ b/tests/TestCase/Api/SharingGroups/AddSharingGroupApiTest.php @@ -0,0 +1,78 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'name' => 'Test Sharing Group', + 'releasability' => 'Test Sharing Group releasability', + 'description' => 'Test Sharing Group description', + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'active' => true, + 'local' => true + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"uuid": "%s"', $uuid)); + $this->assertDbRecordExists('SharingGroups', ['uuid' => $uuid]); + } + + public function testAddSharingGroupNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $faker = \Faker\Factory::create(); + $uuid = $faker->uuid; + + $this->post( + self::ENDPOINT, + [ + 'uuid' => $uuid, + 'name' => 'Test Sharing Group', + 'releasability' => 'Sharing Group A', + 'description' => 'Sharing Group A description', + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'active' => true, + 'local' => true + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('SharingGroups', ['uuid' => $uuid]); + } +} diff --git a/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php new file mode 100644 index 0000000..e2d1dc5 --- /dev/null +++ b/tests/TestCase/Api/SharingGroups/DeleteSharingGroupApiTest.php @@ -0,0 +1,46 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, SharingGroupsFixture::SHARING_GROUP_A_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('SharingGroups', ['id' => SharingGroupsFixture::SHARING_GROUP_A_ID]); + } + + public function testDeleteSharingGroupNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, SharingGroupsFixture::SHARING_GROUP_A_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('SharingGroups', ['id' => SharingGroupsFixture::SHARING_GROUP_A_ID]); + } +} diff --git a/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php new file mode 100644 index 0000000..07dff5b --- /dev/null +++ b/tests/TestCase/Api/SharingGroups/EditSharingGroupApiTest.php @@ -0,0 +1,70 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, SharingGroupsFixture::SHARING_GROUP_A_ID); + $this->put( + $url, + [ + 'name' => 'Test Sharing Group 4321', + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists( + 'SharingGroups', + [ + 'id' => SharingGroupsFixture::SHARING_GROUP_A_ID, + 'name' => 'Test Sharing Group 4321', + ] + ); + } + + public function testEditSharingGroupNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $url = sprintf('%s/%d', self::ENDPOINT, SharingGroupsFixture::SHARING_GROUP_B_ID); + $this->put( + $url, + [ + 'name' => 'Test Sharing Group 1234' + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists( + 'SharingGroups', + [ + 'id' => SharingGroupsFixture::SHARING_GROUP_B_ID, + 'name' => 'Test Sharing Group 1234' + ] + ); + } +} diff --git a/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php b/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php new file mode 100644 index 0000000..5286af2 --- /dev/null +++ b/tests/TestCase/Api/SharingGroups/IndexSharingGroupsApiTest.php @@ -0,0 +1,36 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', SharingGroupsFixture::SHARING_GROUP_A_ID)); + $this->assertResponseContains(sprintf('"id": %d', SharingGroupsFixture::SHARING_GROUP_B_ID)); + } +} diff --git a/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php b/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php new file mode 100644 index 0000000..06ceb93 --- /dev/null +++ b/tests/TestCase/Api/SharingGroups/ViewSharingGroupApiTest.php @@ -0,0 +1,36 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, SharingGroupsFixture::SHARING_GROUP_A_ID); + $this->get($url); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', SharingGroupsFixture::SHARING_GROUP_A_ID)); + } +} diff --git a/tests/TestCase/Api/Tags/IndexTagsApiTest.php b/tests/TestCase/Api/Tags/IndexTagsApiTest.php new file mode 100644 index 0000000..4b13b67 --- /dev/null +++ b/tests/TestCase/Api/Tags/IndexTagsApiTest.php @@ -0,0 +1,36 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + + $this->assertResponseOk(); + $this->assertResponseContains('"name": "red"'); + $this->assertResponseContains('"name": "green"'); + $this->assertResponseContains('"name": "blue"'); + } +} diff --git a/tests/TestCase/Api/Users/AddUserApiTest.php b/tests/TestCase/Api/Users/AddUserApiTest.php new file mode 100644 index 0000000..3437d29 --- /dev/null +++ b/tests/TestCase/Api/Users/AddUserApiTest.php @@ -0,0 +1,66 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->post( + self::ENDPOINT, + [ + 'individual_id' => UsersFixture::USER_REGULAR_USER_ID, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'role_id' => RolesFixture::ROLE_REGULAR_USER_ID, + 'disabled' => false, + 'username' => 'test', + 'password' => 'Password123456!', + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains('"username": "test"'); + $this->assertDbRecordExists('Users', ['username' => 'test']); + } + + public function testAddUserNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $this->post( + self::ENDPOINT, + [ + 'individual_id' => UsersFixture::USER_REGULAR_USER_ID, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'role_id' => RolesFixture::ROLE_REGULAR_USER_ID, + 'disabled' => false, + 'username' => 'test', + 'password' => 'Password123456!' + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('Users', ['username' => 'test']); + } +} diff --git a/tests/TestCase/Api/Users/ChangePasswordApiTest.php b/tests/TestCase/Api/Users/ChangePasswordApiTest.php new file mode 100644 index 0000000..201cb74 --- /dev/null +++ b/tests/TestCase/Api/Users/ChangePasswordApiTest.php @@ -0,0 +1,74 @@ +initializeOpenApiValidator(); + + $this->collection = new ComponentRegistry(); + $this->auth = new FormAuthenticate($this->collection, [ + 'userModel' => 'Users', + ]); + } + + public function testChangePasswordOwnUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $newPassword = 'Test12345678!'; + + $this->put( + self::ENDPOINT, + [ + 'password' => $newPassword, + ] + ); + + $this->assertResponseOk(); + + // Test new password with form login + $request = new ServerRequest([ + 'url' => 'users/login', + 'post' => [ + 'username' => UsersFixture::USER_REGULAR_USER_USERNAME, + 'password' => $newPassword + ], + ]); + $result = $this->auth->authenticate($request, new Response()); + + $this->assertEquals(UsersFixture::USER_REGULAR_USER_ID, $result['id']); + $this->assertEquals(UsersFixture::USER_REGULAR_USER_USERNAME, $result['username']); + } +} diff --git a/tests/TestCase/Api/Users/DeleteUserApiTest.php b/tests/TestCase/Api/Users/DeleteUserApiTest.php new file mode 100644 index 0000000..9288b9a --- /dev/null +++ b/tests/TestCase/Api/Users/DeleteUserApiTest.php @@ -0,0 +1,45 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_REGULAR_USER_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('Users', ['id' => UsersFixture::USER_REGULAR_USER_ID]); + } + + public function testDeleteUserNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_ORG_ADMIN_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('Users', ['id' => UsersFixture::USER_ORG_ADMIN_ID]); + } +} diff --git a/tests/TestCase/Api/Users/EditUserApiTest.php b/tests/TestCase/Api/Users/EditUserApiTest.php new file mode 100644 index 0000000..dd685b9 --- /dev/null +++ b/tests/TestCase/Api/Users/EditUserApiTest.php @@ -0,0 +1,62 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_REGULAR_USER_ID); + $this->put( + $url, + [ + 'id' => UsersFixture::USER_REGULAR_USER_ID, + 'role_id' => RolesFixture::ROLE_ORG_ADMIN_ID, + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists('Users', [ + 'id' => UsersFixture::USER_REGULAR_USER_ID, + 'role_id' => RolesFixture::ROLE_ORG_ADMIN_ID + ]); + } + + public function testEditRoleNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $this->put( + self::ENDPOINT, + [ + 'role_id' => RolesFixture::ROLE_ADMIN_ID, + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('Users', [ + 'id' => UsersFixture::USER_REGULAR_USER_ID, + 'role_id' => RolesFixture::ROLE_ADMIN_ID + ]); + } +} diff --git a/tests/TestCase/Api/Users/IndexUsersApiTest.php b/tests/TestCase/Api/Users/IndexUsersApiTest.php new file mode 100644 index 0000000..c30462b --- /dev/null +++ b/tests/TestCase/Api/Users/IndexUsersApiTest.php @@ -0,0 +1,34 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME)); + } +} diff --git a/tests/TestCase/Api/Users/ViewUserApiTest.php b/tests/TestCase/Api/Users/ViewUserApiTest.php new file mode 100644 index 0000000..e3ab847 --- /dev/null +++ b/tests/TestCase/Api/Users/ViewUserApiTest.php @@ -0,0 +1,44 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME)); + } + + public function testViewUserById(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_REGULAR_USER_ID); + $this->get($url); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_REGULAR_USER_USERNAME)); + } +} diff --git a/tests/TestCase/ApplicationTest.php b/tests/TestCase/ApplicationTest.php index cd09f1e..e2d3183 100644 --- a/tests/TestCase/ApplicationTest.php +++ b/tests/TestCase/ApplicationTest.php @@ -40,10 +40,14 @@ class ApplicationTest extends IntegrationTestCase $app->bootstrap(); $plugins = $app->getPlugins(); - $this->assertCount(3, $plugins); + $this->assertCount(7, $plugins); $this->assertSame('Bake', $plugins->get('Bake')->getName()); $this->assertSame('DebugKit', $plugins->get('DebugKit')->getName()); $this->assertSame('Migrations', $plugins->get('Migrations')->getName()); + $this->assertSame('Authentication', $plugins->get('Authentication')->getName()); + $this->assertSame('ADmad/SocialAuth', $plugins->get('ADmad/SocialAuth')->getName()); + $this->assertSame('Tags', $plugins->get('Tags')->getName()); + $this->assertSame('Cake/TwigView', $plugins->get('Cake/TwigView')->getName()); } /** diff --git a/tests/TestCase/Controller/PagesControllerTest.php b/tests/TestCase/Controller/PagesControllerTest.php deleted file mode 100644 index f2958f9..0000000 --- a/tests/TestCase/Controller/PagesControllerTest.php +++ /dev/null @@ -1,126 +0,0 @@ -get('/'); - $this->assertResponseOk(); - $this->get('/'); - $this->assertResponseOk(); - } - - /** - * testDisplay method - * - * @return void - */ - public function testDisplay() - { - $this->get('/pages/home'); - $this->assertResponseOk(); - $this->assertResponseContains('CakePHP'); - $this->assertResponseContains(''); - } - - /** - * Test that missing template renders 404 page in production - * - * @return void - */ - public function testMissingTemplate() - { - Configure::write('debug', false); - $this->get('/pages/not_existing'); - - $this->assertResponseError(); - $this->assertResponseContains('Error'); - } - - /** - * Test that missing template in debug mode renders missing_template error page - * - * @return void - */ - public function testMissingTemplateInDebug() - { - Configure::write('debug', true); - $this->get('/pages/not_existing'); - - $this->assertResponseFailure(); - $this->assertResponseContains('Missing Template'); - $this->assertResponseContains('Stacktrace'); - $this->assertResponseContains('not_existing.php'); - } - - /** - * Test directory traversal protection - * - * @return void - */ - public function testDirectoryTraversalProtection() - { - $this->get('/pages/../Layout/ajax'); - $this->assertResponseCode(403); - $this->assertResponseContains('Forbidden'); - } - - /** - * Test that CSRF protection is applied to page rendering. - * - * @reutrn void - */ - public function testCsrfAppliedError() - { - $this->post('/pages/home', ['hello' => 'world']); - - $this->assertResponseCode(403); - $this->assertResponseContains('CSRF'); - } - - /** - * Test that CSRF protection is applied to page rendering. - * - * @reutrn void - */ - public function testCsrfAppliedOk() - { - $this->enableCsrfToken(); - $this->post('/pages/home', ['hello' => 'world']); - - $this->assertResponseCode(200); - $this->assertResponseContains('CakePHP'); - } -} diff --git a/tests/TestCase/Controller/Users/UsersControllerTest.php b/tests/TestCase/Controller/Users/UsersControllerTest.php new file mode 100644 index 0000000..7ef6e4c --- /dev/null +++ b/tests/TestCase/Controller/Users/UsersControllerTest.php @@ -0,0 +1,32 @@ +enableCsrfToken(); + $this->enableSecurityToken(); + + $this->post('/users/login', [ + 'username' => UsersFixture::USER_ADMIN_USERNAME, + 'password' => UsersFixture::USER_ADMIN_PASSWORD, + ]); + + $this->assertSessionHasKey('authUser.uuid'); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 962815c..7523c28 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,4 +1,5 @@ runMany([ + ['connection' => 'test'], + ['plugin' => 'Tags', 'connection' => 'test'], + ['plugin' => 'ADmad/SocialAuth', 'connection' => 'test'] + ]); +} else { + echo "[ * ] Skipping DB migrations ...\n"; +} + +$specFile = $_ENV['OPENAPI_SPEC'] ?? APP . '../webroot/docs/openapi.yaml'; +// Initialize OpenAPI spec validator +Configure::write('App.OpenAPIValidator', (new ValidatorBuilder)->fromYamlFile($specFile)); diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml new file mode 100644 index 0000000..8077ce7 --- /dev/null +++ b/webroot/docs/openapi.yaml @@ -0,0 +1,2055 @@ +openapi: 3.0.0 +info: + version: 1.3.0 + title: Cerebrate Project API + description: | + Cerebrate is an open-source platform meant to act as a trusted contact information provider and interconnection orchestrator for other security tools. + +servers: + - url: https://cerebrate.local + +tags: + - name: Individuals + description: "Individuals are natural persons. They are meant to describe the basic information about an individual that may or may not be a user of this community. Users in genral require an individual object to identify the person behind them - however, no user account is required to store information about an individual. Individuals can have affiliations to organisations and broods as well as cryptographic keys, using which their messages can be verified and which can be used to securely contact them." + - name: Users + description: "Users enrolled in this Cerebrate instance." + - name: Organisations + description: "Organisations can be equivalent to legal entities or specific individual teams within such entities. Their purpose is to relate individuals to their affiliations and for release control of information using the Trust Circles." + - name: Tags + description: "Tags can be attached to entity to quickly classify them, allowing further filtering and searches." + - name: Inbox + description: "Inbox messages represent A list of requests to be manually processed." + - name: SharingGroups + description: "Sharing groups are distribution lists usable by tools that can exchange information with a list of trusted partners. Create recurring or ad hoc sharing groups and share them with the members of the sharing group." + - name: Broods + description: "Cerebrate can connect to other Cerebrate instances to exchange trust information and to instrument interconnectivity between connected local tools. Each such Cerebrate instance with its connected tools is considered to be a brood." + - name: EncryptionKeys + description: "Assign encryption keys to the user, used to securely communicate or validate messages coming from the user." + - name: AuthKeys + description: "Authkeys are used for API access. A user can have more than one authkey, so if you would like to use separate keys per tool that queries Cerebrate, add additional keys. Use the comment field to make identifying your keys easier." + +paths: + /individuals/index: + get: + summary: "Get individuals list" + operationId: getIndividuals + tags: + - Individuals + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/IndividualListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /individuals/view/{individualId}: + get: + summary: "Get individual by ID" + operationId: getIndividualById + tags: + - Individuals + parameters: + - $ref: "#/components/parameters/individualId" + responses: + "200": + $ref: "#/components/responses/IndividualResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /individuals/add: + post: + summary: "Add individual" + operationId: addIndividual + tags: + - Individuals + requestBody: + $ref: "#/components/requestBodies/CreateIndividualRequest" + responses: + "200": + $ref: "#/components/responses/IndividualResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /individuals/edit/{individualId}: + put: + summary: "Edit individual" + operationId: editIndividual + tags: + - Individuals + parameters: + - $ref: "#/components/parameters/individualId" + requestBody: + $ref: "#/components/requestBodies/EditIndividualRequest" + responses: + "200": + $ref: "#/components/responses/IndividualResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /individuals/delete/{individualId}: + delete: + summary: "Delete individual by ID" + operationId: deleteIndividualById + tags: + - Individuals + parameters: + - $ref: "#/components/parameters/individualId" + responses: + "200": + $ref: "#/components/responses/IndividualResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /users/index: + get: + summary: "Get users list" + operationId: getUsers + tags: + - Users + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/UserListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /users/view: + get: + summary: "Get information about the current user" + operationId: viewUserMe + tags: + - Users + responses: + "200": + $ref: "#/components/responses/UserResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /users/view/{userId}: + get: + summary: "Get information of a user by ID" + operationId: viewUserById + tags: + - Users + parameters: + - $ref: "#/components/parameters/userId" + responses: + "200": + $ref: "#/components/responses/UserResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /users/add: + post: + summary: "Add user" + operationId: addUser + tags: + - Users + requestBody: + $ref: "#/components/requestBodies/CreateUserRequest" + responses: + "200": + $ref: "#/components/responses/UserResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /users/edit: + put: + summary: "Edit current user" + operationId: editUser + tags: + - Users + requestBody: + $ref: "#/components/requestBodies/EditUserRequest" + responses: + "200": + $ref: "#/components/responses/UserResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /users/edit/{userId}: + put: + summary: "Edit current user" + operationId: editUserById + tags: + - Users + parameters: + - $ref: "#/components/parameters/userId" + requestBody: + $ref: "#/components/requestBodies/EditUserRequest" + responses: + "200": + $ref: "#/components/responses/UserResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /users/delete/{userId}: + delete: + summary: "Delete user by ID" + operationId: deleteUserById + tags: + - Users + parameters: + - $ref: "#/components/parameters/userId" + responses: + "200": + $ref: "#/components/responses/UserResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /organisations/add: + post: + summary: "Add organisation" + operationId: addOrganisation + tags: + - Organisations + requestBody: + $ref: "#/components/requestBodies/CreateOrganisationRequest" + responses: + "200": + $ref: "#/components/responses/OrganisationResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /organisations/edit/{organisationId}: + put: + summary: "Edit organisation" + operationId: editOrganisation + tags: + - Organisations + parameters: + - $ref: "#/components/parameters/organisationId" + requestBody: + $ref: "#/components/requestBodies/EditOrganisationRequest" + responses: + "200": + $ref: "#/components/responses/OrganisationResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /organisations/index: + get: + summary: "Get organisations" + operationId: getOrganisations + tags: + - Organisations + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/OrganisationListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /organisations/view/{organisationId}: + get: + summary: "View organisation by ID" + operationId: getOrganisationById + tags: + - Organisations + parameters: + - $ref: "#/components/parameters/organisationId" + responses: + "200": + $ref: "#/components/responses/OrganisationResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /organisations/delete/{organisationId}: + delete: + summary: "Delete organisation by ID" + operationId: deleteOrganisationById + tags: + - Organisations + parameters: + - $ref: "#/components/parameters/organisationId" + responses: + "200": + $ref: "#/components/responses/OrganisationResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /organisations/tag/{organisationId}: + post: + summary: "Tag organisation by ID" + operationId: tagOrganisationById + tags: + - Organisations + parameters: + - $ref: "#/components/parameters/organisationId" + requestBody: + $ref: "#/components/requestBodies/TagOrganisationRequest" + responses: + "200": + $ref: "#/components/responses/OrganisationResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /organisations/untag/{organisationId}: + post: + summary: "Remove organisation tag by ID" + operationId: untagOrganisationById + tags: + - Organisations + parameters: + - $ref: "#/components/parameters/organisationId" + requestBody: + $ref: "#/components/requestBodies/UntagOrganisationRequest" + responses: + "200": + $ref: "#/components/responses/OrganisationResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /tags/index: + get: + summary: "Get tags list" + operationId: getTags + tags: + - Tags + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/TagListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /inbox/index: + get: + summary: "Get inbox list" + operationId: getinbox + tags: + - Inbox + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/InboxListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /inbox/createEntry/User/Registration: + post: + summary: "Create user registration inbox entry" + operationId: createInboxEntry + tags: + - Inbox + requestBody: + $ref: "#/components/requestBodies/CreateUserRegistrationInboxEntryRequest" + responses: + "200": + $ref: "#/components/responses/CreateUserRegistrationInboxEntryResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /sharingGroups/index: + get: + summary: "Get a sharing groups list" + operationId: getSharingGroups + tags: + - SharingGroups + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/SharingGroupListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /sharingGroups/add: + post: + summary: "Add sharing group" + operationId: addSharingGroup + tags: + - SharingGroups + requestBody: + $ref: "#/components/requestBodies/CreateSharingGroupRequest" + responses: + "200": + $ref: "#/components/responses/IndividualResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /sharingGroups/view/{sharingGroupId}: + get: + summary: "Get sharing group by ID" + operationId: getSharingGroupById + tags: + - SharingGroups + parameters: + - $ref: "#/components/parameters/sharingGroupId" + responses: + "200": + $ref: "#/components/responses/SharingGroupResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /sharingGroups/delete/{sharingGroupId}: + delete: + summary: "Delete sharing group by ID" + operationId: deleteSharingGroupById + tags: + - SharingGroups + parameters: + - $ref: "#/components/parameters/sharingGroupId" + responses: + "200": + $ref: "#/components/responses/SharingGroupResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /sharingGroups/edit/{sharingGroupId}: + put: + summary: "Edit sharing group" + operationId: editSharingGroup + tags: + - SharingGroups + parameters: + - $ref: "#/components/parameters/sharingGroupId" + requestBody: + $ref: "#/components/requestBodies/EditSharingGroupRequest" + responses: + "200": + $ref: "#/components/responses/SharingGroupResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /broods/index: + get: + summary: "Get broods list" + operationId: getBroods + tags: + - Broods + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/BroodListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /broods/view/{broodId}: + get: + summary: "Get brood by ID" + operationId: getBroodById + tags: + - Broods + parameters: + - $ref: "#/components/parameters/broodId" + responses: + "200": + $ref: "#/components/responses/BroodResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /broods/add: + post: + summary: "Add brood" + operationId: addBrood + tags: + - Broods + requestBody: + $ref: "#/components/requestBodies/CreateBroodRequest" + responses: + "200": + $ref: "#/components/responses/BroodResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /broods/edit/{broodId}: + put: + summary: "Edit brood" + operationId: editBrood + tags: + - Broods + parameters: + - $ref: "#/components/parameters/broodId" + requestBody: + $ref: "#/components/requestBodies/EditBroodRequest" + responses: + "200": + $ref: "#/components/responses/BroodResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /broods/delete/{broodId}: + delete: + summary: "Delete brood by ID" + operationId: deleteBroodById + tags: + - Broods + parameters: + - $ref: "#/components/parameters/broodId" + responses: + "200": + $ref: "#/components/responses/BroodResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /broods/testConnection/{broodId}: + get: + summary: "Test brood connection by ID" + operationId: testBroodConnectionById + tags: + - Broods + parameters: + - $ref: "#/components/parameters/broodId" + responses: + "200": + $ref: "#/components/responses/TestBroodConnectionResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + # EncryptionKeys + /encryptionKeys/index: + get: + summary: "Get encryption keys list" + operationId: getEncryptionKeys + tags: + - EncryptionKeys + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/EncryptionKeyListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /encryptionKeys/view/{encryptionKeyId}: + get: + summary: "Get encryption key by ID" + operationId: getEncryptionKeyId + tags: + - EncryptionKeys + parameters: + - $ref: "#/components/parameters/encryptionKeyId" + responses: + "200": + $ref: "#/components/responses/EncryptionKeyResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /encryptionKeys/add: + post: + summary: "Add encryption key" + operationId: addEncryptionKey + tags: + - EncryptionKeys + requestBody: + $ref: "#/components/requestBodies/CreateEncryptionKeyRequest" + responses: + "200": + $ref: "#/components/responses/EncryptionKeyResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /encryptionKeys/edit/{encryptionKeyId}: + put: + summary: "Edit encryption key" + operationId: editEncryptionKey + tags: + - EncryptionKeys + parameters: + - $ref: "#/components/parameters/encryptionKeyId" + requestBody: + $ref: "#/components/requestBodies/EditEncryptionKeyRequest" + responses: + "200": + $ref: "#/components/responses/EncryptionKeyResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /encryptionKeys/delete/{encryptionKeyId}: + delete: + summary: "Delete encryption key by ID" + operationId: deleteEncryptionKeyById + tags: + - EncryptionKeys + parameters: + - $ref: "#/components/parameters/encryptionKeyId" + responses: + "200": + $ref: "#/components/responses/EncryptionKeyResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + # AuthKeys + /authKeys/index: + get: + summary: "Get auth keys list" + operationId: getAuthKeys + tags: + - AuthKeys + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/AuthKeyListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /authKeys/add: + post: + summary: "Add auth keys" + operationId: addAuthKey + tags: + - AuthKeys + requestBody: + $ref: "#/components/requestBodies/CreateAuthKeyRequest" + responses: + "200": + $ref: "#/components/responses/AuthKeyResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /authKeys/delete/{authKeyId}: + delete: + summary: "Delete auth key by ID" + operationId: deleteAuthKeyById + tags: + - AuthKeys + parameters: + - $ref: "#/components/parameters/authKeyId" + responses: + "200": + $ref: "#/components/responses/AuthKeyResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + +components: + schemas: + # General + UUID: + type: string + format: uuid + maxLength: 36 + example: "c99506a6-1255-4b71-afa5-7b8ba48c3b1b" + + ID: + type: integer + format: int32 + example: 1 + + DateTime: + type: string + format: datetime + example: "2022-01-05T11:19:26+00:00" + + Email: + type: string + format: email + example: "user@example.com" + + ModelName: + type: string + enum: + - "Organisation" + - "User" + - "Individual" + - "EncryptionKey" + - "Role" + - "Tag" + - "SharingGroup" + - "Brood" + + # Individuals + IndividualFirstName: + type: string + example: "John" + + IndividualLastName: + type: string + example: "Doe" + + IndividualFullName: + type: string + example: "John Doe" + + IndividualPosition: + type: string + example: "Security Analyst" + + Individual: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + email: + $ref: "#/components/schemas/Email" + first_name: + $ref: "#/components/schemas/IndividualFirstName" + last_name: + $ref: "#/components/schemas/IndividualLastName" + full_name: + $ref: "#/components/schemas/IndividualFullName" + position: + $ref: "#/components/schemas/IndividualPosition" + tags: + $ref: "#/components/schemas/TagList" + aligments: + $ref: "#/components/schemas/AligmentList" + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + + # Users + Username: + type: string + example: "admin" + + IndividualList: + type: array + items: + $ref: "#/components/schemas/Individual" + + User: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + username: + $ref: "#/components/schemas/Username" + role_id: + $ref: "#/components/schemas/ID" + individual_id: + $ref: "#/components/schemas/ID" + disabled: + type: boolean + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + organisation_id: + $ref: "#/components/schemas/ID" + organisation: + $ref: "#/components/schemas/Organisation" + individual: + $ref: "#/components/schemas/Individual" + role: + $ref: "#/components/schemas/Role" + # user_settings: TODO + # user_settings_by_name: TODO + # user_settings_by_name_with_fallback: TODO + + UserList: + type: array + items: + $ref: "#/components/schemas/User" + + # Organisations + OrganisationName: + type: string + + OrganisationUrl: + type: string + + OrganisationSector: + type: string + nullable: true + + OrganisationType: + type: string + nullable: true + + OrganisationContacts: + type: string + nullable: true + + OrganisationNationality: + type: string + nullable: true + + Organisation: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/OrganisationName" + url: + $ref: "#/components/schemas/OrganisationUrl" + nationality: + $ref: "#/components/schemas/OrganisationNationality" + sector: + $ref: "#/components/schemas/OrganisationSector" + type: + $ref: "#/components/schemas/OrganisationType" + contacts: + $ref: "#/components/schemas/OrganisationContacts" + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + tags: + $ref: "#/components/schemas/TagList" + aligments: + $ref: "#/components/schemas/AligmentList" + + OrganisationList: + type: array + items: + $ref: "#/components/schemas/Organisation" + + # Tags + TagName: + type: string + example: "white" + + TagNamespace: + type: string + nullable: true + example: "tlp" + + TagPredicate: + type: string + nullable: true + + TagValue: + type: string + nullable: true + + TagColour: + type: string + example: "FFFFFF" + + TagTextColour: + type: string + example: "white" + + Tag: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + name: + $ref: "#/components/schemas/TagName" + namespace: + $ref: "#/components/schemas/TagNamespace" + predicate: + $ref: "#/components/schemas/TagPredicate" + value: + $ref: "#/components/schemas/TagValue" + colour: + $ref: "#/components/schemas/TagColour" + text_colour: + $ref: "#/components/schemas/TagTextColour" + counter: + type: integer + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + + TagList: + type: array + items: + $ref: "#/components/schemas/Tag" + + # Alignments + Alignment: + type: object + + AligmentList: + type: array + items: + $ref: "#/components/schemas/Alignment" + + # Roles + RoleName: + type: string + maxLength: 255 + example: "admin" + + Role: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + name: + $ref: "#/components/schemas/RoleName" + is_default: + type: boolean + perm_admin: + type: boolean + perm_sync: + type: boolean + perm_org_admin: + type: boolean + + # Inbox + InboxScope: + type: string + enum: + - "User" + - "LocalTool" + - "Brood" + - "Proposal" + - "Synchronisation" + + InboxAction: + type: string + enum: + - "Registration" + - "IncomingConnectionRequest" + - "AcceptedRequest" + - "DeclinedRequest" + - "Synchronisation" + - "OneWaySynchronization" + - "ProposalEdit" + - "DataExchange" + + InboxTitle: + type: string + + InboxOrigin: + type: string + + InboxComment: + type: string + nullable: true + + InboxDescription: + type: string + nullable: true + + Inbox: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + scope: + $ref: "#/components/schemas/InboxScope" + action: + $ref: "#/components/schemas/InboxAction" + title: + $ref: "#/components/schemas/InboxTitle" + origin: + $ref: "#/components/schemas/InboxOrigin" + comment: + $ref: "#/components/schemas/InboxComment" + description: + $ref: "#/components/schemas/InboxDescription" + user_id: + $ref: "#/components/schemas/ID" + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + + UserRegistrationInbox: + type: object + allOf: + - $ref: "#/components/schemas/Inbox" + - type: object + properties: + data: + type: object + properties: + email: + type: string + format: email + password: + type: string + user: + $ref: "#/components/schemas/User" + local_tool_connector_name: + type: string + nullable: true + + IncomingConnectionRequestInbox: + type: object + allOf: + - $ref: "#/components/schemas/Inbox" + - type: object + properties: + data: + type: object + properties: + connectorName: + type: string + enum: + - "MispConnector" + cerebrateURL: + type: string + example: "http://192.168.0.1" + local_tool_id: + type: integer + remote_tool_id: + type: integer + + InboxList: + type: array + items: + anyOf: + - $ref: "#/components/schemas/UserRegistrationInbox" + - $ref: "#/components/schemas/IncomingConnectionRequestInbox" + + # SharingGroups + SharingGroupName: + type: string + + SharingGroupReleasability: + type: string + + SharingGroupDescription: + type: string + + SharingGroup: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/SharingGroupName" + releasability: + $ref: "#/components/schemas/SharingGroupReleasability" + description: + $ref: "#/components/schemas/SharingGroupDescription" + organisation_id: + $ref: "#/components/schemas/ID" + user_id: + $ref: "#/components/schemas/ID" + active: + type: boolean + local: + type: boolean + sharing_group_orgs: + type: array + items: + $ref: "#/components/schemas/Organisation" + user: + $ref: "#/components/schemas/User" + organisation: + $ref: "#/components/schemas/Organisation" + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + + SharingGroupList: + type: array + items: + $ref: "#/components/schemas/SharingGroup" + + # Broods + BroodName: + type: string + + BroodDescription: + type: string + + BroodUrl: + type: string + + BroodIsTrusted: + type: boolean + description: "Trusted upstream source" + + BroodIsPull: + type: boolean + description: "Enable pulling of trust information" + + Brood: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/BroodName" + url: + $ref: "#/components/schemas/BroodUrl" + description: + $ref: "#/components/schemas/BroodDescription" + organisation_id: + $ref: "#/components/schemas/ID" + trusted: + $ref: "#/components/schemas/BroodIsTrusted" + pull: + $ref: "#/components/schemas/BroodIsPull" + skip_proxy: + type: boolean + authkey: + $ref: "#/components/schemas/AuthKeyRaw" + organisation: + $ref: "#/components/schemas/Organisation" + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + + BroodList: + type: array + items: + $ref: "#/components/schemas/Brood" + + # EncryptionKeys + EncryptionKeyType: + type: string + enum: + - "pgp" + - "smime" + + EncryptionKeyValue: + type: string + example: | + -----BEGIN PGP PUBLIC KEY BLOCK----- + ... + -----END PGP PUBLIC KEY BLOCK----- + + EncryptionKeyExpiration: + type: integer + description: "UNIX timestamp or null of there is no expiration" + nullable: true + + EncryptionKey: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + type: + $ref: "#/components/schemas/EncryptionKeyType" + encryption_key: + $ref: "#/components/schemas/EncryptionKeyValue" + revoked: + type: boolean + expires: + $ref: "#/components/schemas/EncryptionKeyExpiration" + owner_id: + $ref: "#/components/schemas/ID" + owner_model: + $ref: "#/components/schemas/ModelName" + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + + EncryptionKeyList: + type: array + items: + $ref: "#/components/schemas/EncryptionKey" + + # AuthKeys + AuthKeyRaw: + type: string + + AuthKeyHashed: + type: string + + AuthKeyExpiration: + type: integer + description: "0 or UNIX timestamp" + example: 0 + + AuthKeyCreatedAt: + type: integer + description: "UNIX timestamp" + + AuthKeyComment: + type: string + + AuthKey: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + authkey: + $ref: "#/components/schemas/AuthKeyHashed" + authkey_start: + type: string + example: abcd + authkey_end: + type: string + example: abcd + created: + $ref: "#/components/schemas/AuthKeyCreatedAt" + expiration: + $ref: "#/components/schemas/AuthKeyExpiration" + type: integer + description: "0 or UNIX timestamp" + user_id: + $ref: "#/components/schemas/ID" + comment: + $ref: "#/components/schemas/AuthKeyComment" + user: + $ref: "#/components/schemas/User" + + AuthKeyList: + type: array + items: + $ref: "#/components/schemas/AuthKey" + + # Errors + ApiError: + type: object + required: + - message + - url + - code + properties: + message: + type: string + url: + type: string + example: "/users" + code: + type: integer + example: 500 + + UnauthorizedApiError: + type: object + required: + - message + - url + - code + properties: + message: + type: string + example: "Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header." + url: + type: string + example: "/users" + code: + type: integer + example: 403 + + MethodNotAllowedApiError: + type: object + required: + - message + - url + - code + properties: + message: + type: string + example: "You do not have permission to use this functionality." + url: + type: string + example: "/users/index" + code: + type: integer + example: 405 + + NotFoundApiError: + type: object + required: + - message + - url + - code + properties: + message: + type: string + example: "Invalid user" + url: + type: string + example: "/users/users/view/1234" + code: + type: integer + example: 404 + + parameters: + individualId: + name: individualId + in: path + description: "Numeric ID of the User" + required: true + schema: + $ref: "#/components/schemas/ID" + + userId: + name: userId + in: path + description: "Numeric ID of the User" + required: true + schema: + $ref: "#/components/schemas/ID" + + organisationId: + name: organisationId + in: path + description: "Numeric ID of the Organisation" + required: true + schema: + $ref: "#/components/schemas/ID" + + sharingGroupId: + name: sharingGroupId + in: path + description: "Numeric ID of the Sharing Group" + required: true + schema: + $ref: "#/components/schemas/ID" + + broodId: + name: broodId + in: path + description: "Numeric ID of the Brood" + required: true + schema: + $ref: "#/components/schemas/ID" + + encryptionKeyId: + name: encryptionKeyId + in: path + description: "Numeric ID of the EncryptionKey" + required: true + schema: + $ref: "#/components/schemas/ID" + + authKeyId: + name: authKeyId + in: path + description: "Numeric ID of the AuthKey" + required: true + schema: + $ref: "#/components/schemas/ID" + + quickFilter: + name: quickFilter + in: query + description: "Quick filter used to match multiple attributes such as name, description, emails, etc." + schema: + type: string + example: "user@example.com" + + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: Authorization + description: | + The authorization is performed by using the following header in the HTTP requests: + + Authorization: YOUR_API_KEY + + requestBodies: + # Individuals + CreateIndividualRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + email: + $ref: "#/components/schemas/Email" + first_name: + $ref: "#/components/schemas/IndividualFirstName" + last_name: + $ref: "#/components/schemas/IndividualLastName" + position: + $ref: "#/components/schemas/IndividualPosition" + + EditIndividualRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + email: + $ref: "#/components/schemas/IndividualLastName" + first_name: + $ref: "#/components/schemas/IndividualFirstName" + last_name: + type: boolean + position: + $ref: "#/components/schemas/IndividualPosition" + + # Users + CreateUserRequest: + required: true + content: + application/json: + schema: + type: object + properties: + individual_id: + $ref: "#/components/schemas/ID" + organisation_id: + $ref: "#/components/schemas/ID" + role_id: + $ref: "#/components/schemas/ID" + disabled: + type: boolean + username: + $ref: "#/components/schemas/Username" + password: + type: string + + EditUserRequest: + required: true + content: + application/json: + schema: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + individual_id: + $ref: "#/components/schemas/ID" + organisation_id: + $ref: "#/components/schemas/ID" + role_id: + $ref: "#/components/schemas/ID" + disabled: + type: boolean + username: + $ref: "#/components/schemas/Username" + password: + type: string + + # Organisations + CreateOrganisationRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/OrganisationName" + url: + $ref: "#/components/schemas/OrganisationUrl" + nationality: + $ref: "#/components/schemas/OrganisationNationality" + sector: + $ref: "#/components/schemas/OrganisationSector" + type: + $ref: "#/components/schemas/OrganisationType" + contacts: + $ref: "#/components/schemas/OrganisationContacts" + + EditOrganisationRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/OrganisationName" + url: + $ref: "#/components/schemas/OrganisationUrl" + nationality: + $ref: "#/components/schemas/OrganisationNationality" + sector: + $ref: "#/components/schemas/OrganisationSector" + type: + $ref: "#/components/schemas/OrganisationType" + contacts: + $ref: "#/components/schemas/OrganisationContacts" + + TagOrganisationRequest: + required: true + content: + application/json: + schema: + type: object + properties: + tag_list: + type: string + description: "Stringified JSON array of the tag names to add." + example: '["red"]' + + UntagOrganisationRequest: + required: true + content: + application/json: + schema: + type: object + properties: + tag_list: + type: string + description: "Stringified JSON array of the tag names to remove." + example: '["red"]' + + # Inbox + CreateUserRegistrationInboxEntryRequest: + description: "Create user registration inbox entry request" + content: + application/json: + schema: + type: object + properties: + email: + type: string + format: email + password: + type: string + + # SharingGroups + CreateSharingGroupRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/SharingGroupName" + releasability: + $ref: "#/components/schemas/SharingGroupReleasability" + description: + $ref: "#/components/schemas/SharingGroupDescription" + organisation_id: + $ref: "#/components/schemas/ID" + user_id: + $ref: "#/components/schemas/ID" + active: + type: boolean + local: + type: boolean + + EditSharingGroupRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/SharingGroupName" + releasability: + $ref: "#/components/schemas/SharingGroupReleasability" + description: + $ref: "#/components/schemas/SharingGroupDescription" + organisation_id: + $ref: "#/components/schemas/ID" + user_id: + $ref: "#/components/schemas/ID" + active: + type: boolean + local: + type: boolean + + # Broods + CreateBroodRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/BroodName" + url: + $ref: "#/components/schemas/BroodUrl" + description: + $ref: "#/components/schemas/BroodDescription" + organisation_id: + $ref: "#/components/schemas/ID" + trusted: + $ref: "#/components/schemas/BroodIsTrusted" + pull: + $ref: "#/components/schemas/BroodIsPull" + skip_proxy: + type: boolean + authkey: + $ref: "#/components/schemas/AuthKeyRaw" + + EditBroodRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + name: + $ref: "#/components/schemas/BroodName" + url: + $ref: "#/components/schemas/BroodUrl" + description: + $ref: "#/components/schemas/BroodDescription" + organisation_id: + $ref: "#/components/schemas/ID" + trusted: + $ref: "#/components/schemas/BroodIsTrusted" + pull: + $ref: "#/components/schemas/BroodIsPull" + skip_proxy: + type: boolean + authkey: + $ref: "#/components/schemas/AuthKeyRaw" + + CreateEncryptionKeyRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + type: + $ref: "#/components/schemas/EncryptionKeyType" + encryption_key: + $ref: "#/components/schemas/EncryptionKeyValue" + revoked: + type: boolean + expires: + $ref: "#/components/schemas/EncryptionKeyExpiration" + owner_id: + $ref: "#/components/schemas/ID" + owner_model: + $ref: "#/components/schemas/ModelName" + + EditEncryptionKeyRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + type: + $ref: "#/components/schemas/EncryptionKeyType" + encryption_key: + $ref: "#/components/schemas/EncryptionKeyValue" + revoked: + type: boolean + expires: + $ref: "#/components/schemas/EncryptionKeyExpiration" + owner_id: + $ref: "#/components/schemas/ID" + owner_model: + $ref: "#/components/schemas/ModelName" + + # AuthKeys + CreateAuthKeyRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + authkey: + $ref: "#/components/schemas/AuthKeyRaw" + expiration: + $ref: "#/components/schemas/AuthKeyExpiration" + user_id: + $ref: "#/components/schemas/ID" + comment: + $ref: "#/components/schemas/AuthKeyComment" + + responses: + # Individuals + IndividualResponse: + description: "Individual response" + content: + application/json: + schema: + $ref: "#/components/schemas/Individual" + + IndividualListResponse: + description: "Individuals list response" + content: + application/json: + schema: + $ref: "#/components/schemas/IndividualList" + + # Users + UserResponse: + description: "User response" + content: + application/json: + schema: + $ref: "#/components/schemas/User" + + UserListResponse: + description: "Users list response" + content: + application/json: + schema: + $ref: "#/components/schemas/UserList" + + # Organisations + OrganisationResponse: + description: "Organisation response" + content: + application/json: + schema: + $ref: "#/components/schemas/Organisation" + + OrganisationListResponse: + description: "Organisations list response" + content: + application/json: + schema: + $ref: "#/components/schemas/OrganisationList" + + # Tags + TagResponse: + description: "Tag response" + content: + application/json: + schema: + $ref: "#/components/schemas/Tag" + + TagListResponse: + description: "Tags list response" + content: + application/json: + schema: + $ref: "#/components/schemas/TagList" + + # Inbox + UserRegistrationInboxResponse: + description: "User registration inbox response" + content: + application/json: + schema: + $ref: "#/components/schemas/UserRegistrationInbox" + + IncomingConnectionRequestInboxResponse: + description: "Incoming connection request inbox response" + content: + application/json: + schema: + $ref: "#/components/schemas/IncomingConnectionRequestInbox" + + InboxListResponse: + description: "Inbox list response" + content: + application/json: + schema: + $ref: "#/components/schemas/InboxList" + + CreateUserRegistrationInboxEntryResponse: + description: "Inbox response" + content: + application/json: + schema: + type: object + properties: + data: + allOf: + - $ref: "#/components/schemas/UserRegistrationInbox" + - properties: + local_tool_connector_name: + type: string + nullable: true + success: + type: boolean + message: + type: string + example: "User account creation requested. Please wait for an admin to approve your account." + errors: + type: array + items: + type: object + # TODO: describe + + # SharingGroups + SharingGroupResponse: + description: "Sharing group response" + content: + application/json: + schema: + $ref: "#/components/schemas/SharingGroup" + + SharingGroupListResponse: + description: "Sharing groups list response" + content: + application/json: + schema: + $ref: "#/components/schemas/SharingGroupList" + + # Broods + BroodResponse: + description: "Brood response" + content: + application/json: + schema: + $ref: "#/components/schemas/Brood" + + BroodListResponse: + description: "Brood list response" + content: + application/json: + schema: + $ref: "#/components/schemas/BroodList" + + TestBroodConnectionResponse: + description: "Brood list response" + content: + application/json: + schema: + type: object + properties: + code: + type: integer + description: "HTTP status code" + example: 200 + response: + type: object + properties: + version: + type: string + example: "0.1" + application: + type: string + example: "Cerebrate" + user: + type: string + example: "sync" + ping: + type: number + format: float + + # EncryptionKeys + EncryptionKeyResponse: + description: "Encryption key response" + content: + application/json: + schema: + $ref: "#/components/schemas/EncryptionKey" + + EncryptionKeyListResponse: + description: "Encryption key list response" + content: + application/json: + schema: + $ref: "#/components/schemas/EncryptionKeyList" + + # AuthKeys + AuthKeyResponse: + description: "Auth key response" + content: + application/json: + schema: + $ref: "#/components/schemas/AuthKey" + + AuthKeyListResponse: + description: "Auth key list response" + content: + application/json: + schema: + $ref: "#/components/schemas/AuthKeyList" + + # Errors + ApiErrorResponse: + description: "Unexpected API error" + content: + application/json: + schema: + $ref: "#/components/schemas/ApiError" + + UnauthorizedApiErrorResponse: + description: "Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header." + content: + application/json: + schema: + $ref: "#/components/schemas/UnauthorizedApiError" + + MethodNotAllowedApiErrorResponse: + description: "Method not allowed. Your User Role is not allowed to access this resource." + content: + application/json: + schema: + $ref: "#/components/schemas/MethodNotAllowedApiError" + +security: + - ApiKeyAuth: []