Commit f2f731fa authored by john_a's avatar john_a Committed by John.Avery
Browse files

Issue #3154681 by john_a: Create a new sub-module for Audit calls

parent 8279793b
MARKETING CLOUD AUDIT API
===========================
CONTENTS OF THIS FILE
---------------------
* Introduction
* Service functions
* Requirements
* Installation & Configuration
INTRODUCTION
------------
This module enables the audit API in the Marketing Cloud as a service.
For details on individual API calls and the Marketing Cloud REST API, please
visit
https://developer.salesforce.com/docs/atlas.en-us.noversion.mc-apis.meta/mc-apis/routes.htm
SERVICE FUNCTIONS
-----------------
| Name | Function |
| ------------------- | -------------------------- |
| Get Audit Events | getAuditEvents($params) |
| Get Security Events | getSecurityEvents($params) |
REQUIREMENTS
------------
* marketing_cloud
INSTALLATION & CONFIGURATION
----------------------------
This module will add a tab to admin > config > marketing cloud. here you can
edit the individual rest call definitions.
Please see the
[community documentation pages](https://www.drupal.org/docs/8/modules/marketing-cloud)
for information on installation and configuration.
definitions:
get_audit_events:
method: "get"
endpoint: "/data/v1/audit/auditEvents"
schema: ""
get_security_events:
method: "get"
endpoint: "/data/v1/audit/securityEvents"
schema: ""
marketing_cloud_audit.settings:
type: config_object
label: Marketing Cloud Audit settings
mapping:
definitions:
type: sequence
sequence:
type: mapping
mapping:
method:
type: string
label: Method
endpoint:
type: string
label: Endpoint
schema:
type: string
label: Schema
name: Marketing Cloud Audit
type: module
description: Gives drupal the ability to send audit message requests to Marketing Cloud
core_version_requirement: ^8.8 || ^9
package: Marketing Cloud
configure: marketing_cloud_audit.settings
dependencies:
- drupal:marketing_cloud
marketing_cloud_audit.settings:
title: Audit Endpoints
description: Configure the Marketing Cloud Audit module
parent: marketing_cloud.settings
route_name: marketing_cloud_audit.settings
marketing_cloud_audit.settings:
route_name: marketing_cloud_audit.settings
title: Audit Endpoints
description: Configure the Marketing Cloud Audit module
base_route: marketing_cloud.settings
<?php
/**
* @file
* Module that defines the marketing cloud address service.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function marketing_cloud_audit_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.marketing_cloud_audit':
$text = file_get_contents(dirname(__FILE__) . '/README.md');
if (!\Drupal::moduleHandler()->moduleExists('markdown')) {
\Drupal::messenger()->addMessage('To view this page correctly, please
install and enable the markdown module.');
return '<pre>' . $text . '</pre>';
}
else {
// Use the Markdown filter to render the README.
$filter_manager = \Drupal::service('plugin.manager.filter');
$settings = \Drupal::configFactory()->get('markdown.settings')->getRawData();
$config = ['settings' => $settings];
$filter = $filter_manager->createInstance('markdown', $config);
return $filter->process($text, 'en');
}
}
return NULL;
}
marketing_cloud_audit.settings:
path: /admin/config/marketing_cloud/audit
defaults:
_form: \Drupal\marketing_cloud_audit\Form\AuditSettings
_title: Marketing Cloud Audit settings
requirements:
_permission: administer_marketing_cloud
services:
marketing_cloud_audit.service:
class: Drupal\marketing_cloud_audit\AuditService
arguments: ['@config.factory', '@logger.factory', '@http_client', '@messenger']
<?php
namespace Drupal\marketing_cloud_audit;
use Drupal\marketing_cloud\MarketingCloudService;
/**
* Class AddressService.
*
* For all of the API service calls, a correct JSON data payload is expected.
* This is then validated against the JSON Schema. This approach minimises
* any short-term issues with changes in the SF API, provides a sanitized
* interface to send API calls and leaves flexibility for any modules that
* want to use this as a base-module.
*
* @package Drupal\marketing_cloud
*/
class AuditService extends MarketingCloudService {
/**
* The machine name of the sub-module.
*
* @var string
*/
private $moduleName = 'marketing_cloud_audit';
/**
* Retrieves logged Audit Trail audit events for the current account and its children. Logins are audited at the enterprise level.
*
* @param array $params
* URL filter params. Permissible keys:
* $page number
* Page number to return from the paged results.
* $pagesize number
* Number of results per page to return.
* $orderBy string
* Determines which property to use for sorting and the direction in which to sort the data.
* startdate string
* Start date of the date range to search for security events.
* enddate string
* End date of the date range to search for security events.
*
* @return array|bool|null
* The result of the API call or FALSE on failure.
*
* @see https://developer.salesforce.com/docs/atlas.en-us.mc-apis.meta/mc-apis/getAuditEvents.htm
*/
public function getAuditEvents(array $params = NULL) {
$machineName = 'get_audit_events';
return $this->apiCall($this->moduleName, $machineName, new \stdClass(), [], $params);
}
/**
* Retrieves logged Audit Trail security events for the authenticated user’s account and its children. Logins are audited at the enterprise level.
*
* @param array $params
* URL filter params. Permissible keys:
* $page number
* Page number to return from the paged results.
* $pagesize number
* Number of results per page to return.
* $orderBy string
* Determines which property to use for sorting and the direction in which to sort the data.
* startdate string
* Start date of the date range to search for security events.
* enddate string
* End date of the date range to search for security events.
*
* @return array|bool|null
* The result of the API call or FALSE on failure.
*
* @see https://developer.salesforce.com/docs/atlas.en-us.mc-apis.meta/mc-apis/getSecurityEvents.htm
*/
public function getSecurityEvents(array $params = NULL) {
$machineName = 'get_security_events';
return $this->apiCall($this->moduleName, $machineName, new \stdClass(), [], $params);
}
}
<?php
namespace Drupal\marketing_cloud_audit\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Configure custom_rest settings for this site.
*/
class AuditSettings extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'marketing_cloud_audit_settings';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['marketing_cloud_audit.settings'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('marketing_cloud_audit.settings');
$form['definitions'] = [
'#type' => 'details',
'#title' => $this->t('Endpoint definitions'),
'#description' => $this->t('These are the endpoint definitions for Mobile Connect. <b>Important: Do not edit these unless you absolutely have to</b>.'),
'#open' => FALSE,
'#tree' => TRUE,
];
foreach ($config->get('definitions') as $key => $value) {
$form['definitions'][$key] = [
'#type' => 'details',
'#title' => $key,
'#open' => FALSE,
'#tree' => TRUE,
];
$form['definitions'][$key]['method'] = [
'#type' => 'textfield',
'#title' => $this->t('Method'),
'#attributes' => ['placeholder' => $this->t('Please enter delivery method for %endpoint endpoint', ['%endpoint' => $key])],
'#default_value' => $value['method'],
];
$form['definitions'][$key]['endpoint'] = [
'#type' => 'textfield',
'#title' => $this->t('Endpoint'),
'#attributes' => ['placeholder' => $this->t('Please enter endpoint for %endpoint endpoint', ['%endpoint' => $key])],
'#default_value' => $value['endpoint'],
];
$form['definitions'][$key]['schema'] = [
'#type' => 'textarea',
'#title' => $this->t('Schema'),
'#attributes' => ['placeholder' => $this->t('Please enter schema for %endpoint endpoint', ['%endpoint' => $key])],
'#default_value' => $value['schema'],
];
}
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$config = $this->config('marketing_cloud_audit.settings');
$definitions = $config->get('definitions');
foreach ($definitions as $key => $value) {
$config->set(
"definitions.$key.method",
$form_state->getValue(['definitions', $key, 'method'])
);
$config->set(
"definitions.$key.endpoint",
$form_state->getValue(['definitions', $key, 'endpoint'])
);
$config->set(
"definitions.$key.schema",
$form_state->getValue(['definitions', $key, 'schema'])
);
}
$config->save();
parent::submitForm($form, $form_state);
}
}
<?php
namespace Drupal\Tests\marketing_cloud_audit\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the base marketing_cloud_audit module.
*
* @group marketing_cloud
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*/
class MarketingCloudAuditTest extends BrowserTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['marketing_cloud', 'marketing_cloud_audit'];
/**
* Admin user.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* Address service.
*
* @var \Drupal\marketing_cloud_audit\AuditService
*/
protected $service;
/**
* Config service.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $moduleConfig;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create user.
$this->adminUser = $this->drupalCreateUser(['administer_marketing_cloud']);
$this->drupalLogin($this->adminUser);
// Set module config.
$this->config('marketing_cloud.settings')
->set('client_id', 'testingid')
->set('client_secret', 'testingsecret')
->set('validate_json', TRUE)
->set('do_not_send', TRUE)
->save();
// Create service.
$this->service = \Drupal::service('marketing_cloud_audit.service');
// Get marketing_cloud_assets config object.
$this->moduleConfig = \Drupal::config('marketing_cloud_audit.settings');
}
/**
* Tests the services and schemas for marketing_cloud_audit.
*/
public function testDefinitions() {
// Test schema.
$this->validateDefinition('get_audit_events');
// Test getAuditEvents against expected inputs.
$result = $this->service
->getAuditEvents($this->getParamData());
$this->assertEquals('https://www.exacttargetapis.com/data/v1/audit/auditEvents?$page=2&$pagesize=10&$orderBy=createdDate&startdate=-60&enddate=-30',
$result['url'],
'Unexpected URL.');
$this->assertEquals('{}', $result['data'], 'Unexpected JSON data.');
$this->assertEquals('get', $result['method'], 'Unexpected URL.');
// Test getSecurityEvents against expected inputs.
$this->validateDefinition('get_security_events');
$result = $this->service
->getSecurityEvents($this->getParamData());
$this->assertEquals('https://www.exacttargetapis.com/data/v1/audit/securityEvents?$page=2&$pagesize=10&$orderBy=createdDate&startdate=-60&enddate=-30',
$result['url'],
'Unexpected URL.');
$this->assertEquals('{}', $result['data'], 'Unexpected JSON data.');
$this->assertEquals('get', $result['method'], 'Unexpected URL.');
}
/**
* Test that the Json-Schema is valid, and that the API method id correct.
*
* @param string $machineName
* The machine name for the api call definition.
*/
protected function validateDefinition($machineName) {
// Validate schema.
$schema = $this->moduleConfig->get("definitions.$machineName.schema");
$this->assertEquals($schema, '', "json schema for $machineName should be an empty string.");
}
/**
* Get parameter data for tests.
*
* @return array
* Array of parameter data.
*/
private function getParamData() {
return [
'$page' => 2,
'$pagesize' => 10,
'$orderBy' => 'createdDate',
'startdate' => -60,
'enddate' => -30,
];
}
}
......@@ -384,10 +384,11 @@ class MarketingCloudInteractionTest extends BrowserTestBase {
return [
"key" => "ixn-created-via-the-api",
"name" => "API-Created journey",
"workflowApiVersion" => "1.0",
"triggers" => [],
"goals" => [],
"activities" => [],
"description" => "api created journey",
"workflowApiVersion" => 1.0,
"triggers" => ['sdf'],
"goals" => ['sag','asdg'],
"activities" => ['qwert', 'ertyu', 'sdfgh'],
];
}
......
......@@ -150,74 +150,65 @@ abstract class MarketingCloudService {
if ($validateJson) {
// Fetch endpoint JSON schema.
$schema = $subModuleConfig->get("definitions.$machineName.schema");
if (empty($schema)) {
$message = $this->t('Could not fetch the schema for %machine_name. Please check the configuration: %module_name.',
[
'%machine_name' => $machineName,
'%module_name' => $moduleName,
]
);
$this->messenger->addError($message);
$this->loggerFactory->get(__METHOD__)->error($message);
return FALSE;
}
// Decode the JSON schema for validation use.
$schema = json_decode($schema);
if ($schema === NULL && json_last_error() !== JSON_ERROR_NONE) {
$message = $this->t('Could not decode the schema for %machine_name. Please check the configuration: %module_name.',
[
'%machine_name' => $machineName,
'%module_name' => $moduleName,
]
);
$this->messenger->addError($message);
$this->loggerFactory->get(__METHOD__)->error($message);
return FALSE;
}
if ($schema !== "") {
$schema = json_decode($schema);
if (json_last_error() !== JSON_ERROR_NONE) {
$message = $this->t('Could not decode the schema for %machine_name. Please check the configuration: %module_name.',
[
'%machine_name' => $machineName,
'%module_name' => $moduleName,
]
);
$this->messenger->addError($message);
$this->loggerFactory->get(__METHOD__)->error($message);
return FALSE;
}
// Load the JSON Schema.
try {
$validator = Schema::import($schema);
}
catch (Exception $e) {
$message = $this->t('Errors were found in the schema for %machine_name in %module_name. Please check the logs.', ['%machine_name' => $machineName, '%module_name' => $moduleName]);
$this->messenger->addError($message);
$message = $this->t('Error in the JSON schema for the %machine_name in %module_name schema: %error',
[
'%machine_name' => $machineName,
'%module_name' => $moduleName,
'%error' => $e->getMessage(),
]
);
$this->messenger->addError($message);
$this->loggerFactory->get(__METHOD__)->error($message);
return FALSE;
}
catch (InvalidValue $e) {
$message = $this->t('Errors were found in the schema for %machine_name in %module_name. Please check the logs.', ['%machine_name' => $machineName, '%module_name' => $moduleName]);
$this->messenger->addError($message);
$message = $this->t('Error in the JSON schema for the %machine_name in %module_name schema: %error',
[
'%machine_name' => $machineName,
'%module_name' => $moduleName,
'%error' => $e->getMessage(),
]
);
$this->messenger->addError($message);
$this->loggerFactory->get(__METHOD__)->error($message);
return FALSE;
}
// Load the JSON Schema.
try {
$validator = Schema::import($schema);
}
catch (Exception $e) {
$message = $this->t('Errors were found in the schema for %machine_name in %module_name. Please check the logs.', ['%machine_name' => $machineName, '%module_name' => $moduleName]);
$this->messenger->addError($message);
$message = $this->t('Error in the JSON schema for the %machine_name in %module_name schema: %error',
[
'%machine_name' => $machineName,
'%module_name' => $moduleName,
'%error' => $e->getMessage(),
]
);
$this->messenger->addError($message);
$this->loggerFactory->get(__METHOD__)->error($message);
return FALSE;
}
catch (InvalidValue $e) {
$message = $this->t('Errors were found in the schema for %machine_name in %module_name. Please check the logs.', ['%machine_name' => $machineName, '%module_name' => $moduleName]);
$this->messenger->addError($message);
$message = $this->t('Error in the JSON schema for the %machine_name in %module_name schema: %error',
[
'%machine_name' => $machineName,
'%module_name' => $moduleName,
'%error' => $e->getMessage(),
]
);
$this->messenger->addError($message);
$this->loggerFactory->get(__METHOD__)->error($message);
return FALSE;
}
// Validate the JSON against the Schema.
try {
$validator->in($data);
}
catch (Exception $e) {
$message = $this->t('Data error against the schema: %error', ['%error' => $e->getMessage()]);
$this->messenger->addError($message);
$this->loggerFactory->get(__METHOD__)->error($message);
return FALSE;
// Validate the JSON against the Schema.
try {
$validator->in($data);
}
catch (Exception $e) {
$message = $this->t('Data error against the schema: %error', ['%error' => $e->getMessage()]);