diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a2c763a..1d39de6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,7 @@ name: test on: push: - branches: [main, develop] + branches: [main, develop, fix-test-action] pull_request: branches: [main, develop] @@ -14,9 +14,9 @@ jobs: fail-fast: false matrix: os: [ubuntu-20.04] - php: ["7.4"] + php: ["8.2"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Create config files run: | diff --git a/config/app.php b/config/app.php index 8faea7d..480c342 100644 --- a/config/app.php +++ b/config/app.php @@ -178,7 +178,6 @@ return [ */ 'Error' => [ 'errorLevel' => E_ALL, - 'exceptionRenderer' => ExceptionRenderer::class, 'skipLog' => [], 'log' => true, 'trace' => true, diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 403e1e5..ae814bc 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -20,6 +20,9 @@ ./tests/TestCase/Api + + ./tests/TestCase/Integration + diff --git a/plugins/Tags/src/View/Helper/TagHelper.php b/plugins/Tags/src/View/Helper/TagHelper.php index 405c513..7cb1411 100644 --- a/plugins/Tags/src/View/Helper/TagHelper.php +++ b/plugins/Tags/src/View/Helper/TagHelper.php @@ -106,7 +106,7 @@ class TagHelper extends Helper $deleteButton = $this->Bootstrap->button([ 'size' => 'sm', 'icon' => 'times', - 'class' => ['ms-1', 'border-0', "text-${textColour}"], + 'class' => ['ms-1', 'border-0', "text-$textColour"], 'variant' => 'text', 'title' => __('Delete tag'), 'onclick' => sprintf('deleteTag(\'%s\', \'%s\', this)', diff --git a/src/Command/ImporterCommand.php b/src/Command/ImporterCommand.php index dee4e24..02c4164 100644 --- a/src/Command/ImporterCommand.php +++ b/src/Command/ImporterCommand.php @@ -136,7 +136,7 @@ class ImporterCommand extends Command $entity = null; if (isset($item[$primary_key])) { $query = $table->find('all') - ->where(["${primary_key}" => $item[$primary_key]]); + ->where(["$primary_key" => $item[$primary_key]]); $entity = $query->first(); } if (is_null($entity)) { diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php index 47269ff..5a69186 100644 --- a/src/Controller/Component/CRUDComponent.php +++ b/src/Controller/Component/CRUDComponent.php @@ -102,7 +102,7 @@ class CRUDComponent extends Component if (!$this->Controller->ParamHandler->isRest()) { $this->setRequestedEntryAmount(); - } else if (!empty($this->request->getQuery('limit'))) { + } else if (empty($this->request->getQuery('limit'))) { $this->Controller->paginate['limit'] = PHP_INT_MAX; // Make sure to download the entire filtered table } $data = $this->Controller->paginate($query, $this->Controller->paginate ?? []); @@ -1489,7 +1489,7 @@ class CRUDComponent extends Component { $prefixedConditions = []; foreach ($conditions as $condField => $condValue) { - $prefixedConditions["${prefix}.${condField}"] = $condValue; + $prefixedConditions["$prefix.$condField"] = $condValue; } return $prefixedConditions; } @@ -1613,13 +1613,13 @@ class CRUDComponent extends Component [sprintf('%s.id = %s.%s', $this->Table->getAlias(), $associatedTable->getAlias(), $association->getForeignKey())] ) ->where([ - ["${field} IS NOT" => NULL] + ["$field IS NOT" => NULL] ]); } else if ($associationType == 'manyToOne') { $fieldToExtract = sprintf('%s.%s', Inflector::singularize(strtolower($model)), $subField); $query = $this->Table->find()->contain($model); } else { - throw new Exception("Association ${associationType} not supported in CRUD Component"); + throw new Exception("Association $associationType not supported in CRUD Component"); } } else { $fieldToExtract = $field; diff --git a/src/Controller/Component/RestResponseComponent.php b/src/Controller/Component/RestResponseComponent.php index 8da9128..19a522b 100644 --- a/src/Controller/Component/RestResponseComponent.php +++ b/src/Controller/Component/RestResponseComponent.php @@ -390,7 +390,7 @@ class RestResponseComponent extends Component return '[]'; } - public function saveFailResponse($controller, $action, $id = false, $validationErrors, $format = false) + public function saveFailResponse($controller, $action, $id, $validationErrors, $format = false) { $this->autoRender = false; $response = array(); diff --git a/templates/Broods/index.php b/templates/Broods/index.php index a78cf5a..d26db0d 100644 --- a/templates/Broods/index.php +++ b/templates/Broods/index.php @@ -61,6 +61,7 @@ echo $this->element('genericElements/IndexTable/index_table', [ ], 'title' => __('Broods Index'), '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.'), + 'includeAllPagination' => true, 'pull' => 'right', 'actions' => [ [ diff --git a/templates/Individuals/index.php b/templates/Individuals/index.php index d7d228f..463f4de 100644 --- a/templates/Individuals/index.php +++ b/templates/Individuals/index.php @@ -78,6 +78,7 @@ echo $this->element('genericElements/IndexTable/index_table', [ ], 'title' => __('ContactDB Individuals Index'), 'description' => __('A list of individuals known by your Cerebrate instance. This list can get populated either directly, by adding new individuals or by fetching them from trusted remote sources. Additionally, users created for the platform will always have an individual identity.'), + 'includeAllPagination' => true, 'actions' => [ [ 'url' => '/individuals/view', diff --git a/templates/MetaTemplates/index.php b/templates/MetaTemplates/index.php index 1ac384f..0c3acfc 100644 --- a/templates/MetaTemplates/index.php +++ b/templates/MetaTemplates/index.php @@ -169,6 +169,7 @@ echo $this->element('genericElements/IndexTable/index_table', [ ], 'title' => __('Meta Field Templates'), 'description' => __('The various templates used to enrich certain objects by a set of standardised fields.'), + 'includeAllPagination' => true, 'actions' => [ [ 'url' => '/metaTemplates/view', diff --git a/templates/OrgGroups/index.php b/templates/OrgGroups/index.php index 33569ad..ad714bf 100644 --- a/templates/OrgGroups/index.php +++ b/templates/OrgGroups/index.php @@ -62,6 +62,7 @@ echo $this->element('genericElements/IndexTable/index_table', [ ], 'title' => __('Organisation Groups Index'), 'description' => __('OrgGroups are an administrative concept, multiple organisations can belong to a grouping that allows common management by so called "GroupAdmins". This helps grouping organisations by sector, country or other commonalities into co-managed sub-communities.'), + 'includeAllPagination' => true, 'actions' => [ [ 'url' => '/orgGroups/view', diff --git a/templates/Organisations/index.php b/templates/Organisations/index.php index 2cf96de..c3fb2ae 100644 --- a/templates/Organisations/index.php +++ b/templates/Organisations/index.php @@ -97,6 +97,7 @@ echo $this->element('genericElements/IndexTable/index_table', [ ], 'title' => __('ContactDB Organisation Index'), 'description' => __('A list of organisations known by your Cerebrate instance. This list can get populated either directly, by adding new organisations or by fetching them from trusted remote sources.'), + 'includeAllPagination' => true, 'actions' => [ [ 'url' => '/organisations/view', diff --git a/templates/Roles/index.php b/templates/Roles/index.php index 8e12ed9..a5f2ffb 100644 --- a/templates/Roles/index.php +++ b/templates/Roles/index.php @@ -77,6 +77,7 @@ echo $this->element('genericElements/IndexTable/index_table', [ ], 'title' => __('Roles Index'), 'description' => __('A list of configurable user roles. Create or modify user access roles based on the settings below.'), + 'includeAllPagination' => true, 'pull' => 'right', 'actions' => [ [ diff --git a/templates/SharingGroups/index.php b/templates/SharingGroups/index.php index 0162c23..bb4843e 100644 --- a/templates/SharingGroups/index.php +++ b/templates/SharingGroups/index.php @@ -57,6 +57,7 @@ echo $this->element('genericElements/IndexTable/index_table', [ ], 'title' => __('Sharing Groups Index'), '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.'), + 'includeAllPagination' => true, 'pull' => 'right', 'actions' => [ [ diff --git a/tests/Fixture/BroodsFixture.php b/tests/Fixture/BroodsFixture.php index c329e6a..7cc26a1 100644 --- a/tests/Fixture/BroodsFixture.php +++ b/tests/Fixture/BroodsFixture.php @@ -56,7 +56,7 @@ class BroodsFixture extends TestFixture 'id' => self::BROOD_WIREMOCK_ID, 'uuid' => $faker->uuid(), 'name' => 'wiremock', - 'url' => 'http://localhost:8080', + 'url' => sprintf('http://%s:%s', $_ENV['WIREMOCK_HOST'] ?? 'localhost', $_ENV['WIREMOCK_PORT'] ?? '8080'), 'description' => $faker->text, 'organisation_id' => OrganisationsFixture::ORGANISATION_B_ID, 'trusted' => true, diff --git a/tests/Fixture/InboxFixture.php b/tests/Fixture/InboxFixture.php index 40303b2..d274a56 100644 --- a/tests/Fixture/InboxFixture.php +++ b/tests/Fixture/InboxFixture.php @@ -11,8 +11,8 @@ class InboxFixture extends TestFixture public $connection = 'test'; public $table = 'inbox'; - public const INBOX_USER_REGISTRATION_ID = 1; - public const INBOX_INCOMING_CONNECTION_REQUEST_ID = 2; + public const INBOX_USER_REGISTRATION_UUID = 'e783b13a-7019-48f5-848e-582bb930a833'; + public const INBOX_INCOMING_CONNECTION_REQUEST_UUID = '9810bd94-16f9-42e0-b364-af59dba50a34'; public function init(): void { @@ -20,8 +20,7 @@ class InboxFixture extends TestFixture $this->records = [ [ - 'id' => self::INBOX_USER_REGISTRATION_ID, - 'uuid' => $faker->uuid(), + 'uuid' => self::INBOX_USER_REGISTRATION_UUID, 'scope' => 'User', 'action' => 'Registration', 'title' => 'User account creation requested for foo@bar.com', @@ -37,15 +36,14 @@ class InboxFixture extends TestFixture 'modified' => $faker->dateTime()->getTimestamp() ], [ - 'id' => self::INBOX_INCOMING_CONNECTION_REQUEST_ID, - 'uuid' => $faker->uuid(), + 'uuid' => self::INBOX_INCOMING_CONNECTION_REQUEST_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, + 'user_id' => UsersFixture::USER_ADMIN_ID, 'data' => [ 'connectorName' => 'MispConnector', 'cerebrateURL' => 'http://127.0.0.1', diff --git a/tests/Fixture/IndividualsFixture.php b/tests/Fixture/IndividualsFixture.php index 13aecdf..9e33d53 100644 --- a/tests/Fixture/IndividualsFixture.php +++ b/tests/Fixture/IndividualsFixture.php @@ -15,6 +15,7 @@ class IndividualsFixture extends TestFixture public const INDIVIDUAL_ORG_ADMIN_ID = 3; public const INDIVIDUAL_REGULAR_USER_ID = 4; public const INDIVIDUAL_A_ID = 5; + public const INDIVIDUAL_B_ID = 6; public function init(): void { @@ -70,6 +71,16 @@ class IndividualsFixture extends TestFixture 'position' => 'user', 'created' => $faker->dateTime()->getTimestamp(), 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::INDIVIDUAL_B_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/MetaTemplateDirectoryFixture.php b/tests/Fixture/MetaTemplateDirectoryFixture.php new file mode 100644 index 0000000..04c6242 --- /dev/null +++ b/tests/Fixture/MetaTemplateDirectoryFixture.php @@ -0,0 +1,30 @@ +records = [ + [ + 'id' => self::META_TEMPLATE_DIRECTORY_ID, + 'uuid' => self::META_TEMPLATE_DIRECTORY_UUID, + 'name' => 'Test Meta Template Directory', + 'namespace' => 'cerebrate', + 'version' => '1' + ] + ]; + + parent::init(); + } +} diff --git a/tests/Fixture/MetaTemplateFieldsFixture.php b/tests/Fixture/MetaTemplateFieldsFixture.php index 141a7aa..ff38317 100644 --- a/tests/Fixture/MetaTemplateFieldsFixture.php +++ b/tests/Fixture/MetaTemplateFieldsFixture.php @@ -17,6 +17,7 @@ class MetaTemplateFieldsFixture extends TestFixture 'field' => 'test_field_1', 'type' => 'text', 'meta_template_id' => MetaTemplatesFixture::ENABLED_TEST_ORG_META_TEMPLATE_ID, + 'meta_template_directory_id' => MetaTemplateDirectoryFixture::META_TEMPLATE_DIRECTORY_ID, 'regex' => null, 'multiple' => 1, 'enabled' => 1, diff --git a/tests/Fixture/MetaTemplatesFixture.php b/tests/Fixture/MetaTemplatesFixture.php index cbe96a2..6eed2bb 100644 --- a/tests/Fixture/MetaTemplatesFixture.php +++ b/tests/Fixture/MetaTemplatesFixture.php @@ -44,6 +44,7 @@ class MetaTemplatesFixture extends TestFixture $this->records = [ [ 'id' => self::ENABLED_TEST_ORG_META_TEMPLATE_ID, + 'meta_template_directory_id' => MetaTemplateDirectoryFixture::META_TEMPLATE_DIRECTORY_ID, 'scope' => 'organisation', 'name' => 'Test Meta Template (enabled)', 'namespace' => 'cerebrate', @@ -58,6 +59,7 @@ class MetaTemplatesFixture extends TestFixture ], [ 'id' => self::DISABLED_TEST_ORG_META_TEMPLATE_ID, + 'meta_template_directory_id' => MetaTemplateDirectoryFixture::META_TEMPLATE_DIRECTORY_ID, 'scope' => 'organisation', 'name' => 'Test Meta Template (disabled)', 'namespace' => 'cerebrate', diff --git a/tests/Helper/WireMockTestTrait.php b/tests/Helper/WireMockTestTrait.php index 41aea61..69d33c5 100644 --- a/tests/Helper/WireMockTestTrait.php +++ b/tests/Helper/WireMockTestTrait.php @@ -15,16 +15,18 @@ trait WireMockTestTrait private $wiremock; /** @var array */ - private $config = [ - 'hostname' => 'localhost', - 'port' => 8080 - ]; + private $config; public function initializeWireMock(): void { + $this->config = [ + 'hostname' => $_ENV['WIREMOCK_HOST'] ?? 'localhost', + 'port' => $_ENV['WIREMOCK_PORT'] ?? 8080 + ]; + $this->wiremock = WireMock::create( - $_ENV['WIREMOCK_HOST'] ?? $this->config['hostname'], - $_ENV['WIREMOCK_PORT'] ?? $this->config['port'] + $this->config['hostname'], + $this->config['port'] ); if (!$this->wiremock->isAlive()) { diff --git a/tests/README.md b/tests/README.md index 434f84a..bfdead7 100644 --- a/tests/README.md +++ b/tests/README.md @@ -59,6 +59,7 @@ $ vendor/bin/phpunit --testsuite=api --testdox Available suites: * `app`: runs all test suites * `api`: runs only api tests +* `e2e`: runs only integration tests (requires wiremock running) * `controller`: runs only controller tests * _to be continued ..._ diff --git a/tests/TestCase/Api/Inbox/IndexInboxApiTest.php b/tests/TestCase/Api/Inbox/IndexInboxApiTest.php index b8af1c6..0a60393 100644 --- a/tests/TestCase/Api/Inbox/IndexInboxApiTest.php +++ b/tests/TestCase/Api/Inbox/IndexInboxApiTest.php @@ -30,7 +30,7 @@ class IndexInboxApiTest extends TestCase $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)); + $this->assertResponseContains(sprintf('"uuid": "%s"', InboxFixture::INBOX_USER_REGISTRATION_UUID)); + $this->assertResponseContains(sprintf('"uuid": "%s"', InboxFixture::INBOX_INCOMING_CONNECTION_REQUEST_UUID)); } } diff --git a/tests/TestCase/Api/LocalTools/MispInterConnectionTest.php b/tests/TestCase/Api/LocalTools/MispInterConnectionTest.php index 7f643d3..e81c4ec 100644 --- a/tests/TestCase/Api/LocalTools/MispInterConnectionTest.php +++ b/tests/TestCase/Api/LocalTools/MispInterConnectionTest.php @@ -31,20 +31,20 @@ class MispInterConnectionTest extends TestCase ]; /** constants related to the local Cerebrate instance */ - private const LOCAL_CEREBRATE_URL = 'http://127.0.0.1'; + private const LOCAL_CEREBRATE_URL = 'http://localhost'; /** constants related to the local MISP instance */ - private const LOCAL_MISP_INSTANCE_URL = 'http://localhost:8080/MISP_LOCAL'; + private const LOCAL_MISP_INSTANCE_URL = '/MISP_LOCAL'; private const LOCAL_MISP_ADMIN_USER_AUTHKEY = 'b17ce79ac0f05916f382ab06ea4790665dbc174c'; /** constants related to the remote Cerebrate instance */ - private const REMOTE_CEREBRATE_URL = 'http://127.0.0.1:8080/CEREBRATE_REMOTE'; + private const REMOTE_CEREBRATE_URL = '/CEREBRATE_REMOTE'; private const REMOTE_CEREBRATE_AUTHKEY = 'a192ba3c749b545f9cec6b6bba0643736f6c3022'; /** constants related to the remote MISP instance */ private const REMOTE_MISP_SYNC_USER_ID = 333; private const REMOTE_MISP_SYNC_USER_EMAIL = 'sync@misp.remote'; - private const REMOTE_MISP_INSTANCE_URL = 'http://localhost:8080/MISP_REMOTE'; + private const REMOTE_MISP_INSTANCE_URL = '/MISP_REMOTE'; private const REMOTE_MISP_AUTHKEY = '19ca57ecebd2fe34c1c17d729980678eb648d541'; @@ -64,7 +64,7 @@ class MispInterConnectionTest extends TestCase 'name' => 'MISP_LOCAL', 'connector' => 'MispConnector', 'settings' => json_encode([ - 'url' => self::LOCAL_MISP_INSTANCE_URL, + 'url' => $this->getWireMockBaseUrl() . self::LOCAL_MISP_INSTANCE_URL, 'authkey' => self::LOCAL_MISP_ADMIN_USER_AUTHKEY, 'skip_ssl' => true, ]), @@ -90,7 +90,7 @@ class MispInterConnectionTest extends TestCase [ 'uuid' => $LOCAL_BROOD_UUID, 'name' => 'Local Brood', - 'url' => self::REMOTE_CEREBRATE_URL, + 'url' => $this->getWireMockBaseUrl() . self::REMOTE_CEREBRATE_URL, 'description' => $faker->text, 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, 'trusted' => true, @@ -228,10 +228,10 @@ class MispInterConnectionTest extends TestCase [ 'email' => self::REMOTE_MISP_SYNC_USER_EMAIL, 'authkey' => self::REMOTE_MISP_AUTHKEY, - 'url' => self::REMOTE_MISP_INSTANCE_URL, + 'url' => $this->getWireMockBaseUrl() . self::REMOTE_MISP_INSTANCE_URL, 'reflected_user_id' => self::REMOTE_MISP_SYNC_USER_ID, 'connectorName' => 'MispConnector', - 'cerebrateURL' => self::REMOTE_CEREBRATE_URL, + 'cerebrateURL' => $this->getWireMockBaseUrl() . self::REMOTE_CEREBRATE_URL, 'local_tool_id' => 1, 'remote_tool_id' => 1, 'tool_name' => 'MISP_REMOTE' @@ -250,7 +250,7 @@ class MispInterConnectionTest extends TestCase self::LOCAL_MISP_ADMIN_USER_AUTHKEY, [ 'authkey' => self::REMOTE_MISP_AUTHKEY, - 'url' => self::REMOTE_MISP_INSTANCE_URL, + 'url' => $this->getWireMockBaseUrl() . self::REMOTE_MISP_INSTANCE_URL, 'name' => 'MISP_REMOTE', 'remote_org_id' => OrganisationsFixture::ORGANISATION_A_ID ] diff --git a/tests/TestCase/Api/Users/AddUserApiTest.php b/tests/TestCase/Api/Users/AddUserApiTest.php index 3437d29..8d10139 100644 --- a/tests/TestCase/Api/Users/AddUserApiTest.php +++ b/tests/TestCase/Api/Users/AddUserApiTest.php @@ -6,7 +6,7 @@ namespace App\Test\TestCase\Api\Users; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; -use App\Test\Fixture\UsersFixture; +use App\Test\Fixture\IndividualsFixture; use App\Test\Fixture\OrganisationsFixture; use App\Test\Fixture\RolesFixture; use App\Test\Helper\ApiTestTrait; @@ -31,18 +31,20 @@ class AddUserApiTest extends TestCase $this->post( self::ENDPOINT, [ - 'individual_id' => UsersFixture::USER_REGULAR_USER_ID, + 'individual_id' => IndividualsFixture::INDIVIDUAL_B_ID, 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, 'role_id' => RolesFixture::ROLE_REGULAR_USER_ID, 'disabled' => false, - 'username' => 'test', + 'username' => 'test123', 'password' => 'Password123456!', ] ); + print_r($this->getJsonResponseAsArray()); + $this->assertResponseOk(); - $this->assertResponseContains('"username": "test"'); - $this->assertDbRecordExists('Users', ['username' => 'test']); + $this->assertResponseContains('"username": "test123"'); + $this->assertDbRecordExists('Users', ['username' => 'test123']); } public function testAddUserNotAllowedAsRegularUser(): void @@ -51,7 +53,7 @@ class AddUserApiTest extends TestCase $this->post( self::ENDPOINT, [ - 'individual_id' => UsersFixture::USER_REGULAR_USER_ID, + 'individual_id' => IndividualsFixture::INDIVIDUAL_B_ID, 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, 'role_id' => RolesFixture::ROLE_REGULAR_USER_ID, 'disabled' => false, @@ -61,6 +63,6 @@ class AddUserApiTest extends TestCase ); $this->assertResponseCode(405); - $this->assertDbRecordNotExists('Users', ['username' => 'test']); + $this->assertDbRecordNotExists('Users', ['username' => 'test123']); } } diff --git a/tests/TestCase/Api/Users/DeleteUserApiTest.php b/tests/TestCase/Api/Users/DeleteUserApiTest.php index 9288b9a..2ffe0be 100644 --- a/tests/TestCase/Api/Users/DeleteUserApiTest.php +++ b/tests/TestCase/Api/Users/DeleteUserApiTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Test\TestCase\Api\Users; +use Cake\Core\Configure; use Cake\TestSuite\TestCase; use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\UsersFixture; @@ -25,6 +26,7 @@ class DeleteUserApiTest extends TestCase public function testDeleteUser(): void { + Configure::write('user.allow-user-deletion', true); $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); $url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_REGULAR_USER_ID); $this->delete($url); diff --git a/tests/TestCase/ApplicationTest.php b/tests/TestCase/ApplicationTest.php index e2d3183..5704f68 100644 --- a/tests/TestCase/ApplicationTest.php +++ b/tests/TestCase/ApplicationTest.php @@ -40,14 +40,12 @@ class ApplicationTest extends IntegrationTestCase $app->bootstrap(); $plugins = $app->getPlugins(); - $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/Integration/Broods/TestBroodConnectionApiTest.php b/tests/TestCase/Integration/Broods/TestBroodConnectionApiTest.php new file mode 100644 index 0000000..8f0a0df --- /dev/null +++ b/tests/TestCase/Integration/Broods/TestBroodConnectionApiTest.php @@ -0,0 +1,65 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->initializeWireMock(); + $stub = $this->mockCerebrateStatusResponse(); + + $url = sprintf('%s/%d', self::ENDPOINT, BroodsFixture::BROOD_WIREMOCK_ID); + $this->get($url); + + $this->verifyStubCalled($stub); + $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/Integration/LocalTools/MispInterConnectionTest.php b/tests/TestCase/Integration/LocalTools/MispInterConnectionTest.php new file mode 100644 index 0000000..33f9eac --- /dev/null +++ b/tests/TestCase/Integration/LocalTools/MispInterConnectionTest.php @@ -0,0 +1,405 @@ +initializeWireMock(); + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + $faker = \Faker\Factory::create(); + + /** + * 1. Create LocalTool connection to `MISP LOCAL` (local MISP instance) + */ + $this->post( + sprintf('%s/localTools/add', self::LOCAL_CEREBRATE_URL), + [ + 'name' => 'MISP_LOCAL', + 'connector' => 'MispConnector', + 'settings' => json_encode([ + 'url' => $this->getWireMockBaseUrl() . self::LOCAL_MISP_INSTANCE_URL, + 'authkey' => self::LOCAL_MISP_ADMIN_USER_AUTHKEY, + 'skip_ssl' => true, + ]), + 'description' => 'MISP local instance', + 'exposed' => true + ] + ); + $this->assertResponseOk(); + $this->assertDbRecordExists('LocalTools', ['name' => 'MISP_LOCAL']); + + /** + * 2. Create a new Brood (connect to a remote Cerebrate instance) + * This step assumes that the remote Cerebrate instance is already + * running and has a user created for the local Cerebrate instance. + * + * NOTE: Uses OrganisationsFixture::ORGANISATION_A_ID from the + * fixtures as the local Organisation. + */ + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $LOCAL_BROOD_UUID = $faker->uuid; + $this->post( + '/broods/add', + [ + 'uuid' => $LOCAL_BROOD_UUID, + 'name' => 'Local Brood', + 'url' => $this->getWireMockBaseUrl() . self::REMOTE_CEREBRATE_URL, + 'description' => $faker->text, + 'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'trusted' => true, + 'pull' => true, + 'skip_proxy' => true, + 'authkey' => self::REMOTE_CEREBRATE_AUTHKEY, + ] + ); + $this->assertResponseOk(); + $this->assertDbRecordExists('Broods', ['uuid' => $LOCAL_BROOD_UUID]); + $brood = $this->getJsonResponseAsArray(); + + /** + * 3. Create a new Cerebrate local user for the remote Cerebrate + * These includes: + * - 3.a: Create a new Organisation + * - 3.b: Create a new Individual + * - 3.c: Create a new User + * - 3.d: Create a new Authkey + */ + // Create Organisation + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $remoteOrgUuid = $faker->uuid; + $this->post( + '/organisations/add', + [ + 'name' => 'Remote Organisation', + 'description' => $faker->text, + 'uuid' => $remoteOrgUuid, + 'url' => 'http://cerebrate.remote', + 'nationality' => 'US', + 'sector' => 'sector', + 'type' => 'type', + ] + ); + $this->assertResponseOk(); + $this->assertDbRecordExists('Organisations', ['uuid' => $remoteOrgUuid]); + $remoteOrg = $this->getJsonResponseAsArray(); + + // Create Individual + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->post( + '/individuals/add', + [ + 'email' => 'sync@cerebrate.remote', + 'first_name' => 'Remote', + 'last_name' => 'Cerebrate' + ] + ); + $this->assertResponseOk(); + $this->assertDbRecordExists('Individuals', ['email' => 'sync@cerebrate.remote']); + $remoteIndividual = $this->getJsonResponseAsArray(); + + // Create User + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->post( + '/users/add', + [ + 'individual_id' => $remoteIndividual['id'], + 'organisation_id' => $remoteOrg['id'], + 'role_id' => RolesFixture::ROLE_SYNC_ID, + 'disabled' => false, + 'username' => 'remote_cerebrate', + 'password' => 'Password123456!', + ] + ); + $this->assertResponseOk(); + $this->assertDbRecordExists('Users', ['username' => 'remote_cerebrate']); + $user = $this->getJsonResponseAsArray(); + + // Create Authkey + $remoteCerebrateAuthkey = $faker->sha1; + $remoteAuthkeyUuid = $faker->uuid; + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->post( + '/authKeys/add', + [ + 'uuid' => $remoteAuthkeyUuid, + 'authkey' => $remoteCerebrateAuthkey, + 'expiration' => 0, + 'user_id' => $user['id'], + 'comment' => $faker->text + ] + ); + $this->assertResponseOk(); + $this->assertDbRecordExists('AuthKeys', ['uuid' => $remoteAuthkeyUuid]); + + /** + * 4. Get remote Cerebrate exposed tools + */ + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->mockCerebrateGetExposedToolsResponse('CEREBRATE_REMOTE', self::REMOTE_CEREBRATE_AUTHKEY); + $this->get(sprintf('/localTools/broodTools/%s', $brood['id'])); + $this->assertResponseOk(); + $tools = $this->getJsonResponseAsArray(); + + /** + * 5. Issue a connection request to the remote MISP instance + */ + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->mockCerebrateGetExposedToolsResponse('CEREBRATE_REMOTE', self::REMOTE_CEREBRATE_AUTHKEY); + $this->mockMispViewOrganisationByUuid( + 'MISP_LOCAL', + self::LOCAL_MISP_ADMIN_USER_AUTHKEY, + OrganisationsFixture::ORGANISATION_A_UUID, + OrganisationsFixture::ORGANISATION_A_ID + ); + $this->mockMispCreateSyncUser( + 'MISP_LOCAL', + self::LOCAL_MISP_ADMIN_USER_AUTHKEY, + self::REMOTE_MISP_SYNC_USER_ID, + self::REMOTE_MISP_SYNC_USER_EMAIL + ); + $this->mockCerebrateCreateMispIncommingConnectionRequest( + 'CEREBRATE_REMOTE', + UsersFixture::USER_ADMIN_ID, + self::LOCAL_CEREBRATE_URL, + self::REMOTE_CEREBRATE_AUTHKEY, + self::LOCAL_MISP_INSTANCE_URL + ); + $this->post( + sprintf('/localTools/connectionRequest/%s/%s', $brood['id'], $tools[0]['id']), + [ + 'local_tool_id' => 1 + ] + ); + $this->assertResponseOk(); + + /** + * 6. Remote Cerebrate accepts the connection request + */ + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->post( + '/inbox/createEntry/LocalTool/AcceptedRequest', + [ + 'email' => self::REMOTE_MISP_SYNC_USER_EMAIL, + 'authkey' => self::REMOTE_MISP_AUTHKEY, + 'url' => $this->getWireMockBaseUrl() . self::REMOTE_MISP_INSTANCE_URL, + 'reflected_user_id' => self::REMOTE_MISP_SYNC_USER_ID, + 'connectorName' => 'MispConnector', + 'cerebrateURL' => $this->getWireMockBaseUrl() . self::REMOTE_CEREBRATE_URL, + 'local_tool_id' => 1, + 'remote_tool_id' => 1, + 'tool_name' => 'MISP_REMOTE' + ] + ); + $this->assertResponseOk(); + $acceptRequest = $this->getJsonResponseAsArray(); + + /** + * 7. Finalize the connection + */ + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->mockEnableMispSyncUser('MISP_LOCAL', self::LOCAL_MISP_ADMIN_USER_AUTHKEY, self::REMOTE_MISP_SYNC_USER_ID); + $this->mockAddMispServer( + 'MISP_LOCAL', + self::LOCAL_MISP_ADMIN_USER_AUTHKEY, + [ + 'authkey' => self::REMOTE_MISP_AUTHKEY, + 'url' => $this->getWireMockBaseUrl() . self::REMOTE_MISP_INSTANCE_URL, + 'name' => 'MISP_REMOTE', + 'remote_org_id' => OrganisationsFixture::ORGANISATION_A_ID + ] + ); + $this->post(sprintf('/inbox/process/%s', $acceptRequest['data']['id'])); + $this->assertResponseOk(); + $this->assertResponseContains('"success": true'); + $this->verifyAllStubsCalled(); + } + + private function mockCerebrateGetExposedToolsResponse(string $instance, string $cerebrateAuthkey): \WireMock\Stubbing\StubMapping + { + return $this->getWireMock()->stubFor( + WireMock::get(WireMock::urlEqualTo("/$instance/localTools/exposedTools")) + ->withHeader('Authorization', WireMock::equalTo($cerebrateAuthkey)) + ->willReturn(WireMock::aResponse() + ->withHeader('Content-Type', 'application/json') + ->withBody((string)json_encode( + [ + [ + "id" => 1, + "name" => "MISP ($instance)", + "connector" => "MispConnector", + ] + ] + ))) + ); + } + + private function mockMispViewOrganisationByUuid(string $instance, string $mispAuthkey, string $orgUuid, int $orgId): \WireMock\Stubbing\StubMapping + { + return $this->getWireMock()->stubFor( + WireMock::get(WireMock::urlEqualTo("/$instance/organisations/view/$orgUuid/limit:50")) + ->withHeader('Authorization', WireMock::equalTo($mispAuthkey)) + ->willReturn(WireMock::aResponse() + ->withHeader('Content-Type', 'application/json') + ->withBody((string)json_encode( + [ + "Organisation" => [ + "id" => $orgId, + "name" => $instance . ' Organisation', + "uuid" => $orgUuid, + "local" => true + ] + ] + ))) + ); + } + + private function mockMispCreateSyncUser(string $instance, string $mispAuthkey, int $userId, string $email): \WireMock\Stubbing\StubMapping + { + $faker = \Faker\Factory::create(); + return $this->getWireMock()->stubFor( + WireMock::post(WireMock::urlEqualTo("/$instance/admin/users/add")) + ->withHeader('Authorization', WireMock::equalTo($mispAuthkey)) + ->willReturn(WireMock::aResponse() + ->withHeader('Content-Type', 'application/json') + ->withBody((string)json_encode( + [ + "User" => [ + "id" => $userId, + "email" => $email, + "authkey" => $faker->sha1 + ] + ] + ))) + ); + } + + private function mockCerebrateCreateMispIncommingConnectionRequest( + string $instance, + int $userId, + string $cerebrateUrl, + string $cerebrateAuthkey, + string $mispUrl + ): \WireMock\Stubbing\StubMapping { + $faker = \Faker\Factory::create(); + + return $this->getWireMock()->stubFor( + WireMock::post(WireMock::urlEqualTo("/$instance/inbox/createEntry/LocalTool/IncomingConnectionRequest")) + ->withHeader('Authorization', WireMock::equalTo($cerebrateAuthkey)) + ->willReturn(WireMock::aResponse() + ->withHeader('Content-Type', 'application/json') + ->withBody((string)json_encode( + [ + 'data' => [ + 'id' => $faker->randomNumber(), + 'uuid' => $faker->uuid, + 'origin' => $cerebrateUrl, + 'user_id' => $userId, + 'data' => [ + 'connectorName' => 'MispConnector', + 'cerebrateURL' => $cerebrateUrl, + 'url' => $mispUrl, + 'tool_connector' => 'MispConnector', + 'local_tool_id' => 1, + 'remote_tool_id' => 1, + ], + 'title' => 'Request for MISP Inter-connection', + 'scope' => 'LocalTool', + 'action' => 'IncomingConnectionRequest', + 'description' => 'Handle Phase I of inter-connection when another cerebrate instance performs the request.', + 'local_tool_connector_name' => 'MispConnector', + 'created' => date('c'), + 'modified' => date('c') + ], + 'success' => true, + 'message' => 'LocalTool request for IncomingConnectionRequest created', + 'errors' => [], + ] + ))) + ); + } + + private function mockEnableMispSyncUser(string $instance, string $mispAuthkey, int $userId): \WireMock\Stubbing\StubMapping + { + return $this->getWireMock()->stubFor( + WireMock::post(WireMock::urlEqualTo("/$instance/admin/users/edit/$userId")) + ->withHeader('Authorization', WireMock::equalTo($mispAuthkey)) + ->withRequestBody(WireMock::equalToJson(json_encode(['disabled' => false]))) + ->willReturn(WireMock::aResponse() + ->withHeader('Content-Type', 'application/json') + ->withBody((string)json_encode( + [ + "User" => [ + "id" => $userId, + ] + ] + ))) + ); + } + + private function mockAddMispServer(string $instance, string $mispAuthkey, array $body): \WireMock\Stubbing\StubMapping + { + $faker = \Faker\Factory::create(); + + return $this->getWireMock()->stubFor( + WireMock::post(WireMock::urlEqualTo("/$instance/servers/add")) + ->withHeader('Authorization', WireMock::equalTo($mispAuthkey)) + ->withRequestBody(WireMock::equalToJson(json_encode($body))) + ->willReturn(WireMock::aResponse() + ->withHeader('Content-Type', 'application/json') + ->withBody((string)json_encode( + [ + 'Server' => [ + 'id' => $faker->randomNumber() + ] + ] + ))) + ); + } +}