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..74fc99b 100644 --- a/src/Controller/Component/CRUDComponent.php +++ b/src/Controller/Component/CRUDComponent.php @@ -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/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/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/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() + ] + ] + ))) + ); + } +}