Merge branch 'develop' of github.com:cerebrate-project/cerebrate into develop

refacto/CRUDComponent
iglocska 2023-10-05 11:05:41 +02:00
commit 6b53d6d81a
No known key found for this signature in database
GPG Key ID: BEA224F1FEF113AC
29 changed files with 571 additions and 45 deletions

View File

@ -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: |

View File

@ -178,7 +178,6 @@ return [
*/
'Error' => [
'errorLevel' => E_ALL,
'exceptionRenderer' => ExceptionRenderer::class,
'skipLog' => [],
'log' => true,
'trace' => true,

View File

@ -20,6 +20,9 @@
<testsuite name="api">
<directory>./tests/TestCase/Api</directory>
</testsuite>
<testsuite name="e2e">
<directory>./tests/TestCase/Integration</directory>
</testsuite>
</testsuites>
<extensions>

View File

@ -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)',

View File

@ -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)) {

View File

@ -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;

View File

@ -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();

View File

@ -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' => [
[

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -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' => [
[

View File

@ -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' => [
[

View File

@ -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,

View File

@ -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',

View File

@ -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();

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
class MetaTemplateDirectoryFixture extends TestFixture
{
public $connection = 'test';
public const META_TEMPLATE_DIRECTORY_ID = 1;
public const META_TEMPLATE_DIRECTORY_UUID = '99c7b7c0-23e2-4ba8-9ad4-97bcdadf4c81';
public function init(): void
{
$this->records = [
[
'id' => self::META_TEMPLATE_DIRECTORY_ID,
'uuid' => self::META_TEMPLATE_DIRECTORY_UUID,
'name' => 'Test Meta Template Directory',
'namespace' => 'cerebrate',
'version' => '1'
]
];
parent::init();
}
}

View File

@ -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,

View File

@ -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',

View File

@ -15,16 +15,18 @@ trait WireMockTestTrait
private $wiremock;
/** @var array<mixed> */
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()) {

View File

@ -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 ..._

View File

@ -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));
}
}

View File

@ -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
]

View File

@ -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']);
}
}

View File

@ -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);

View File

@ -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());
}
/**

View File

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Integration\Broods;
use Cake\TestSuite\TestCase;
use App\Test\Fixture\AuthKeysFixture;
use App\Test\Fixture\BroodsFixture;
use App\Test\Helper\ApiTestTrait;
use App\Test\Helper\WireMockTestTrait;
use \WireMock\Client\WireMock;
class TestBroodConnectionApiTest extends TestCase
{
use ApiTestTrait;
use WireMockTestTrait;
protected const ENDPOINT = '/broods/testConnection';
protected $fixtures = [
'app.Organisations',
'app.Individuals',
'app.Roles',
'app.Users',
'app.AuthKeys',
'app.Broods'
];
public function testTestBroodConnection(): void
{
$this->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
]
]
]
)))
);
}
}

View File

@ -0,0 +1,405 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Integration\LocalTools;
use Cake\TestSuite\TestCase;
use App\Test\Fixture\OrganisationsFixture;
use App\Test\Fixture\AuthKeysFixture;
use App\Test\Fixture\UsersFixture;
use App\Test\Fixture\RolesFixture;
use App\Test\Helper\ApiTestTrait;
use App\Test\Helper\WireMockTestTrait;
use \WireMock\Client\WireMock;
class MispInterConnectionTest extends TestCase
{
use ApiTestTrait;
use WireMockTestTrait;
protected $fixtures = [
'app.Organisations',
'app.Individuals',
'app.Roles',
'app.Users',
'app.AuthKeys',
'app.Broods',
'app.LocalTools',
'app.RemoteToolConnections',
'app.Inbox'
];
/** constants related to the local Cerebrate instance */
private const LOCAL_CEREBRATE_URL = 'http://localhost';
/** constants related to the local MISP instance */
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 = '/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 = '/MISP_REMOTE';
private const REMOTE_MISP_AUTHKEY = '19ca57ecebd2fe34c1c17d729980678eb648d541';
public function testInterConnectMispViaCerebrate(): void
{
$this->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()
]
]
)))
);
}
}