[#32] merged webhook2api (dev_32)
This commit is contained in:
commit
8aeaa3120b
|
|
@ -111,7 +111,7 @@ $rest_allowed_actions = array(
|
|||
),
|
||||
),
|
||||
),
|
||||
'123.45.678.1' => array(
|
||||
'123.45.67.8' => array(
|
||||
'Contact' => array(
|
||||
'getsingle' => array(
|
||||
'first_name' => 'string',
|
||||
|
|
@ -124,3 +124,35 @@ $rest_allowed_actions = array(
|
|||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
/****************************************************************
|
||||
** WebHook2API CONFIGURATIONS **
|
||||
****************************************************************/
|
||||
# remove if you don't want this feature or rename to $webhook2api to activate
|
||||
$_webhook2api = [
|
||||
"configurations" => [
|
||||
"default" => [
|
||||
"name" => "Example",
|
||||
"ip_sources" => ['172.10.0.1/24', '192.168.1.1/24'], // only accept source ID from the given range
|
||||
"data_sources" => ["POST/json", "REQUEST"], // POST/json json-decodes the post data, REQUEST is PHP's $_REQUEST array
|
||||
"sentinel" => [["type", "equal:customer.created"]], // only execute if all of these are true
|
||||
"entity" => "Contact",
|
||||
"action" => "create",
|
||||
"api_key" => "api key",
|
||||
"parameter_mapping" => [
|
||||
[["data", "object", "metadata", "salutation"], ["prefix_id"]],
|
||||
[["data", "object", "metadata", "first_name"], ["first_name"]],
|
||||
[["data", "object", "metadata", "last_name"], ["last_name"]],
|
||||
[["data", "object", "metadata", "street"], ["street_address"]],
|
||||
[["data", "object", "metadata", "zip_code"], ["postal_code"]],
|
||||
[["data", "object", "metadata", "city"], ["city"]],
|
||||
[["data", "object", "metadata", "country"], ["country_id"]],
|
||||
[["data", "object", "metadata", "telephone"], ["phone"]],
|
||||
[["data", "object", "metadata", "birthday"], ["birth_date"]],
|
||||
[["data", "object", "metadata", "email"], ["email"]]
|
||||
],
|
||||
"parameter_sanitation" => [],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
|
@ -0,0 +1,300 @@
|
|||
<?php
|
||||
/*--------------------------------------------------------+
|
||||
| SYSTOPIA CiviProxy |
|
||||
| a simple proxy solution for external access to CiviCRM |
|
||||
| Copyright (C) 2019 SYSTOPIA |
|
||||
| Author: B. Endres (endres -at- systopia.de) |
|
||||
| http://www.systopia.de/ |
|
||||
+---------------------------------------------------------*/
|
||||
|
||||
require_once "config.php";
|
||||
require_once "proxy.php";
|
||||
|
||||
// first check if webhooks are enabled
|
||||
if (empty($webhook2api)) civiproxy_http_error("Feature disabled", 405);
|
||||
|
||||
// basic check
|
||||
if (!civiproxy_security_check('webhook2api')) {
|
||||
civiproxy_http_error("Access denied", 403);
|
||||
}
|
||||
|
||||
// find the right configuration
|
||||
if (!empty($_REQUEST['id']) && isset($webhook2api['configurations'][$_REQUEST['id']])) {
|
||||
// we found the if in the configurations
|
||||
$configurations = [$webhook2api['configurations'][$_REQUEST['id']]];
|
||||
} elseif (empty($_REQUEST['id']) && isset($webhook2api['configurations']['default'])) {
|
||||
// this is teh default configuration
|
||||
$configurations = [$webhook2api['configurations']['default']];
|
||||
} else {
|
||||
// use all of them (first one matching is executed)
|
||||
$configurations = $webhook2api['configurations'];
|
||||
}
|
||||
|
||||
// read some input
|
||||
$post_input = @file_get_contents('php://input');
|
||||
error_log("DEBUG: " . json_encode($post_input));
|
||||
|
||||
// MAIN: iterate through all (eligible) configurations
|
||||
$last_error = ["No handler found", 501];
|
||||
foreach ($configurations as $configuration) {
|
||||
$last_error = webhook2api_processConfiguration($configuration, $post_input);
|
||||
if ($last_error == NULL) {
|
||||
// success!
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// finally - if there was only errors, return the last one
|
||||
if ($last_error) {
|
||||
civiproxy_http_error($last_error[0], $last_error[1]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Apply the given configuration. If it matches and executes,
|
||||
* it returns NULL, otherwise
|
||||
*
|
||||
* @param $configuration array configuration/specification
|
||||
* @return null|array [status_code, error message]
|
||||
*/
|
||||
function webhook2api_processConfiguration($configuration, $post_input) {
|
||||
// check the IP/range restrictions
|
||||
if (!empty($configuration['ip_sources']) && is_array($configuration['ip_sources'])) {
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
$access_granted = FALSE;
|
||||
foreach ($configuration['ip_sources'] as $netmask) {
|
||||
// copied from https://secure.php.net/manual/de/ref.network.php
|
||||
list ($net, $mask) = explode("/", $netmask);
|
||||
$ip_net = ip2long ($net);
|
||||
$ip_mask = ~((1 << (32 - $mask)) - 1);
|
||||
$ip_ip = ip2long ($ip);
|
||||
$ip_ip_net = $ip_ip & $ip_mask;
|
||||
if ($ip_ip_net == $ip_net) {
|
||||
$access_granted = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$access_granted) {
|
||||
// this configuration is not eligible
|
||||
return ["Access denied", 403];
|
||||
}
|
||||
}
|
||||
|
||||
// gather source data
|
||||
$data = [];
|
||||
if (!empty($configuration['data_sources']) && is_array($configuration['data_sources'])) {
|
||||
error_log(json_encode($configuration));
|
||||
foreach ($configuration['data_sources'] as $data_source) {
|
||||
switch ($data_source) {
|
||||
case 'POST/json': # JSON data in POST field
|
||||
$more_data = json_decode($post_input, TRUE);
|
||||
error_log(json_encode($more_data));
|
||||
$data = array_merge_recursive($data, $more_data);
|
||||
break;
|
||||
case 'REQUEST': # simple request parameters
|
||||
$data = array_merge_recursive($data, $_REQUEST);
|
||||
break;
|
||||
default:
|
||||
civiproxy_log("Webhook2API[{$configuration['name']}]: unknown source '{$data_source}' in configuration. Ignored.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// default return code if everything goes according to plan
|
||||
$http_code = 200;
|
||||
// check if we have a json_array and react accordingly
|
||||
if (isset($data[0]) && is_array($data[0])) {
|
||||
foreach ($data as $d) {
|
||||
$result = webhook2api_callCiviApi($configuration, $d);
|
||||
if(isset($result['internal_error'])) {
|
||||
// internal communication Error occured. Aborting process
|
||||
civiproxy_log("Webhook2API[{$configuration['name']}]: internal error occured: " . json_encode($result['internal_error']));
|
||||
return $result['internal_error'];
|
||||
}
|
||||
if (!empty($result['values']['http_code'])) {
|
||||
$http_code = $result['values']['http_code'];
|
||||
} else {
|
||||
$http_code = 403;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$result = webhook2api_callCiviApi($configuration, $data);
|
||||
if(isset($result['internal_error'])) {
|
||||
// internal communication Error occured. Aborting process
|
||||
civiproxy_log("Webhook2API[{$configuration['name']}]: internal error occured: " . json_encode($result['internal_error']));
|
||||
return $result['internal_error'];
|
||||
}
|
||||
if (!empty($result['values']['http_code'])) {
|
||||
$http_code = $result['values']['http_code'];
|
||||
} else {
|
||||
$http_code = 403;
|
||||
}
|
||||
}
|
||||
if ($http_code != '200') {
|
||||
// we received and parsed the webhook event successfully, but an error occured with civicrm:
|
||||
civiproxy_log("Webhook2API[{$configuration['name']}]: Internal CiviCRM Error. Error Code: {$http_code}. Full Message: " . json_encode($result));
|
||||
}
|
||||
|
||||
// process result
|
||||
if (!empty($configuration['response_mapping']) && is_array($configuration['response_mapping'])) {
|
||||
// TODO: implement
|
||||
//error_log("Webhook2API.response_mapping: not implemented!");
|
||||
http_response_code('200');
|
||||
|
||||
} else {
|
||||
// default behaviour:
|
||||
http_response_code('200');
|
||||
}
|
||||
// all done
|
||||
exit();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse Configuration and given data set, apply it and send it to civicrm.
|
||||
* Returns an internal error if communication to civicrm isn't successful
|
||||
*
|
||||
* @param $configuration
|
||||
* @param $data
|
||||
*
|
||||
* @return array|mixed|void
|
||||
* @throws \CiviCRM_API3_Exception
|
||||
*/
|
||||
function webhook2api_callCiviApi($configuration, $data) {
|
||||
// evaluate sentinels
|
||||
if (!empty($configuration['sentinel']) && is_array($configuration['sentinel'])) {
|
||||
foreach ($configuration['sentinel'] as $sentinel) {
|
||||
list($value_source, $check) = $sentinel;
|
||||
$value = webhook2api_getValue($data, $value_source);
|
||||
if (substr($check, 0, 6) == "equal:") {
|
||||
// check if terms a equal
|
||||
if (substr($check, 6) != $value) {
|
||||
return ["internal_error" => "Access denied", 403];
|
||||
}
|
||||
} else {
|
||||
echo "Error";
|
||||
// unknown instruction
|
||||
// //error_log("Webhook2API[{$configuration['name']}]: don't understad sentinel '{$check}'. Ignored.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compile API query
|
||||
$params = [];
|
||||
if (!empty($configuration['parameter_mapping']) && is_array($configuration['parameter_mapping'])) {
|
||||
foreach ($configuration['parameter_mapping'] as $mapping) {
|
||||
$source_path = $mapping[0];
|
||||
$target_path = $mapping[1];
|
||||
$modifiers = isset($mapping[2]) ? $mapping[2] : [];
|
||||
|
||||
// get value
|
||||
$value = webhook2api_getValue($data, $source_path);
|
||||
|
||||
// run modifiers
|
||||
foreach ($modifiers as $modifier) {
|
||||
// TODO: implement
|
||||
//error_log("Webhook2API.modifiers: not implemented!");
|
||||
}
|
||||
|
||||
// set to target
|
||||
webhook2api_setValue($params, $target_path, $value);
|
||||
}
|
||||
} else {
|
||||
$params = $data;
|
||||
}
|
||||
|
||||
// sanitise data
|
||||
if (!empty($configuration['parameter_sanitation']) && is_array($configuration['parameter_sanitation'])) {
|
||||
// TODO: implement
|
||||
//error_log("Webhook2API.sanitation: not implemented!");
|
||||
}
|
||||
|
||||
// send to target REST API
|
||||
if (empty($configuration['entity']) || empty($configuration['action'])) {
|
||||
//error_log("Webhook2API[{$configuration['name']}]: Missing entity/action.");
|
||||
return ["internal_error" => "Configuration error", 403];
|
||||
}
|
||||
if (empty($configuration['api_key'])) {
|
||||
//error_log("Webhook2API[{$configuration['name']}]: Missing api_key.");
|
||||
return ["internal_error" => "Configuration error", 403];
|
||||
}
|
||||
$params['api_key'] = $configuration['api_key'];
|
||||
|
||||
// run API call
|
||||
return civicrm_api3($configuration['entity'], $configuration['action'], $params);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the value from a multidimensional array,
|
||||
* specified by the path
|
||||
*
|
||||
* @param $data array multidimensional data array
|
||||
* @param $path array|string path description
|
||||
* @return mixed value
|
||||
*/
|
||||
function webhook2api_getValue($data, $path) {
|
||||
if (is_string($path)) {
|
||||
if (isset($data[$path])) {
|
||||
return $data[$path];
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
} elseif (is_array($path)) {
|
||||
if (count($path) == 0) {
|
||||
return NULL;
|
||||
} elseif (count($path) == 1) {
|
||||
return webhook2api_getValue($data, $path[0]);
|
||||
} else {
|
||||
$path_element = array_shift($path);
|
||||
$sub_data = webhook2api_getValue($data, $path_element);
|
||||
if (is_array($sub_data)) {
|
||||
return webhook2api_getValue($sub_data, $path);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value from a multidimensional array as specified by the path
|
||||
*
|
||||
* @param $data array the data
|
||||
* @param $target_path array destination
|
||||
* @param $value mixed value
|
||||
*/
|
||||
function webhook2api_setValue(&$data, $target_path, $value) {
|
||||
if (is_array($target_path)) {
|
||||
if (count($target_path) == 0) {
|
||||
civiproxy_log("Webhook2API.setValue: Empty target path!");
|
||||
return;
|
||||
|
||||
} elseif (count($target_path) == 1) {
|
||||
// last element -> set value
|
||||
$data[$target_path[0]] = $value;
|
||||
|
||||
} else {
|
||||
// not last element
|
||||
$element = array_shift($target_path);
|
||||
if (!isset($data[$element])) {
|
||||
$data[$element] = [];
|
||||
}
|
||||
if (is_array($data[$element])) {
|
||||
webhook2api_setValue($data[$element], $target_path, $value);
|
||||
} else {
|
||||
civiproxy_log("Webhook2API.setValue: path node is not an array!");
|
||||
}
|
||||
}
|
||||
|
||||
} elseif (is_string($target_path)) {
|
||||
webhook2api_setValue($data, [$target_path], $value);
|
||||
|
||||
} else {
|
||||
civiproxy_log("Webhook2API.setValue: path neither string nor array!");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue