From d87d5a6c242a6ab98c01b32c75dafe1d62e9d7b9 Mon Sep 17 00:00:00 2001 From: "B. Endres" Date: Fri, 4 Jan 2019 16:22:43 +0100 Subject: [PATCH 1/6] [#32] implementing webhook2api --- proxy/webhook2api.php | 247 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 proxy/webhook2api.php diff --git a/proxy/webhook2api.php b/proxy/webhook2api.php new file mode 100644 index 0000000..7579223 --- /dev/null +++ b/proxy/webhook2api.php @@ -0,0 +1,247 @@ + 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 { + // error - bad spec (path element is not array) + } + } + + } elseif (is_string($target_path)) { + webhook2api_setValue($data, [$target_path], $value); + + } else { + // error - bad spec + } +} \ No newline at end of file From 2207a95c2b811256dd45f2db2bb799078d8dc4a8 Mon Sep 17 00:00:00 2001 From: "B. Endres" Date: Fri, 4 Jan 2019 17:28:52 +0100 Subject: [PATCH 2/6] [#32] implementing webhook2api --- proxy/webhook2api.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/proxy/webhook2api.php b/proxy/webhook2api.php index 7579223..df93aa3 100644 --- a/proxy/webhook2api.php +++ b/proxy/webhook2api.php @@ -153,6 +153,9 @@ function webhook2api_processConfiguration($configuration, $post_input) { civiproxy_log("Webhook2API[{$configuration['name']}]: Missing api_key."); return ["Configuration error", 403]; } + $params['api_key'] = $configuration['api_key']; + + // run API call $result = civicrm_api3($configuration['entity'], $configuration['action'], $params); // process result From f044515feb71f187ce765e1755b8b16e0ca2dc27 Mon Sep 17 00:00:00 2001 From: "B. Endres" Date: Fri, 4 Jan 2019 17:41:45 +0100 Subject: [PATCH 3/6] [#32] improved documentation --- proxy/config.dist.php | 34 +++++++++++++++++++++++++++++++++- proxy/webhook2api.php | 11 +++++++---- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/proxy/config.dist.php b/proxy/config.dist.php index 6ad7b2a..59510bb 100644 --- a/proxy/config.dist.php +++ b/proxy/config.dist.php @@ -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" => [], + ] + ] +]; \ No newline at end of file diff --git a/proxy/webhook2api.php b/proxy/webhook2api.php index df93aa3..3ccd54e 100644 --- a/proxy/webhook2api.php +++ b/proxy/webhook2api.php @@ -129,7 +129,8 @@ function webhook2api_processConfiguration($configuration, $post_input) { // run modifiers foreach ($modifiers as $modifier) { - // TODO: + // TODO: implement + civiproxy_log("Webhook2API.modifiers: not implemented!"); } // set to target @@ -142,6 +143,7 @@ function webhook2api_processConfiguration($configuration, $post_input) { // sanitise data if (!empty($configuration['parameter_sanitation']) && is_array($configuration['parameter_sanitation'])) { // TODO: implement + civiproxy_log("Webhook2API.sanitation: not implemented!"); } // send to target REST API @@ -161,6 +163,7 @@ function webhook2api_processConfiguration($configuration, $post_input) { // process result if (!empty($configuration['response_mapping']) && is_array($configuration['response_mapping'])) { // TODO: implement + civiproxy_log("Webhook2API.response_mapping: not implemented!"); } else { // default behaviour: @@ -221,7 +224,7 @@ function webhook2api_getValue($data, $path) { function webhook2api_setValue(&$data, $target_path, $value) { if (is_array($target_path)) { if (count($target_path) == 0) { - // error - bad spec + civiproxy_log("Webhook2API.setValue: Empty target path!"); return; } elseif (count($target_path) == 1) { @@ -237,7 +240,7 @@ function webhook2api_setValue(&$data, $target_path, $value) { if (is_array($data[$element])) { webhook2api_setValue($data[$element], $target_path, $value); } else { - // error - bad spec (path element is not array) + civiproxy_log("Webhook2API.setValue: path node is not an array!"); } } @@ -245,6 +248,6 @@ function webhook2api_setValue(&$data, $target_path, $value) { webhook2api_setValue($data, [$target_path], $value); } else { - // error - bad spec + civiproxy_log("Webhook2API.setValue: path neither string nor array!"); } } \ No newline at end of file From 1ffa67865dc5dc6c9c410dc997ff572974c7dd09 Mon Sep 17 00:00:00 2001 From: Philipp Batroff Date: Wed, 17 Jul 2019 14:15:52 +0200 Subject: [PATCH 4/6] implementing webhook2api, amendment for json array parameter parsing. WiP - debuggin output for error_log --- proxy/webhook2api.php | 98 ++++++++++++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/proxy/webhook2api.php b/proxy/webhook2api.php index 3ccd54e..93fa90f 100644 --- a/proxy/webhook2api.php +++ b/proxy/webhook2api.php @@ -32,6 +32,7 @@ if (!empty($_REQUEST['id']) && isset($webhook2api['configurations'][$_REQUEST['i // 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]; @@ -84,10 +85,12 @@ function webhook2api_processConfiguration($configuration, $post_input) { // 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 @@ -99,6 +102,61 @@ function webhook2api_processConfiguration($configuration, $post_input) { } } + // 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 + return $result['internal_error']; + } + if (!empty($result['http_code'])) { + $http_code = $result['http_code']; + } else { + $http_code = 403; + break; + } + } + } else { + $result = webhook2api_callCiviApi($configuration, $data); + if(isset($result['internal_error'])) { + // internal communication Error occured. Aborting process + return $result['internal_error']; + } + if (!empty($result['http_code'])) { + $http_code = $result['http_code']; + } else { + $http_code = 403; + } + } + + // process result + if (!empty($configuration['response_mapping']) && is_array($configuration['response_mapping'])) { + // TODO: implement + //error_log("Webhook2API.response_mapping: not implemented!"); + + } else { + // default behaviour: + http_response_code($http_code); + } + // 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) { @@ -107,11 +165,12 @@ function webhook2api_processConfiguration($configuration, $post_input) { if (substr($check, 0, 6) == "equal:") { // check if terms a equal if (substr($check, 6) != $value) { - return ["Access denied", 403]; + return ["internal_error" => "Access denied", 403]; } } else { + echo "Error"; // unknown instruction - civiproxy_log("Webhook2API[{$configuration['name']}]: don't understad sentinel '{$check}'. Ignored."); + // //error_log("Webhook2API[{$configuration['name']}]: don't understad sentinel '{$check}'. Ignored."); } } } @@ -130,7 +189,7 @@ function webhook2api_processConfiguration($configuration, $post_input) { // run modifiers foreach ($modifiers as $modifier) { // TODO: implement - civiproxy_log("Webhook2API.modifiers: not implemented!"); + //error_log("Webhook2API.modifiers: not implemented!"); } // set to target @@ -143,45 +202,26 @@ function webhook2api_processConfiguration($configuration, $post_input) { // sanitise data if (!empty($configuration['parameter_sanitation']) && is_array($configuration['parameter_sanitation'])) { // TODO: implement - civiproxy_log("Webhook2API.sanitation: not implemented!"); + //error_log("Webhook2API.sanitation: not implemented!"); } // send to target REST API if (empty($configuration['entity']) || empty($configuration['action'])) { - civiproxy_log("Webhook2API[{$configuration['name']}]: Missing entity/action."); - return ["Configuration error", 403]; + //error_log("Webhook2API[{$configuration['name']}]: Missing entity/action."); + return ["internal_error" => "Configuration error", 403]; } if (empty($configuration['api_key'])) { - civiproxy_log("Webhook2API[{$configuration['name']}]: Missing api_key."); - return ["Configuration error", 403]; + //error_log("Webhook2API[{$configuration['name']}]: Missing api_key."); + return ["internal_error" => "Configuration error", 403]; } $params['api_key'] = $configuration['api_key']; // run API call - $result = civicrm_api3($configuration['entity'], $configuration['action'], $params); + return civicrm_api3($configuration['entity'], $configuration['action'], $params); - // process result - if (!empty($configuration['response_mapping']) && is_array($configuration['response_mapping'])) { - // TODO: implement - civiproxy_log("Webhook2API.response_mapping: not implemented!"); - - } else { - // default behaviour: - if (empty($result['is_error'])) { - http_response_code(200); - } else { - if (!empty($result['http_code'])) { - http_response_code($result['http_code']); - } else { - http_response_code(403); - } - } - } - - // all done - exit(); } + /** * Get the value from a multidimensional array, * specified by the path From f83fc0c77e3e1bbb656b96a590d8ec3caa67b01a Mon Sep 17 00:00:00 2001 From: Philipp Batroff Date: Thu, 18 Jul 2019 11:34:18 +0200 Subject: [PATCH 5/6] udpate parsing of response codes from civicrm api --- proxy/webhook2api.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proxy/webhook2api.php b/proxy/webhook2api.php index 93fa90f..da9a38f 100644 --- a/proxy/webhook2api.php +++ b/proxy/webhook2api.php @@ -112,8 +112,8 @@ function webhook2api_processConfiguration($configuration, $post_input) { // internal communication Error occured. Aborting process return $result['internal_error']; } - if (!empty($result['http_code'])) { - $http_code = $result['http_code']; + if (!empty($result['values']['http_code'])) { + $http_code = $result['values']['http_code']; } else { $http_code = 403; break; @@ -125,8 +125,8 @@ function webhook2api_processConfiguration($configuration, $post_input) { // internal communication Error occured. Aborting process return $result['internal_error']; } - if (!empty($result['http_code'])) { - $http_code = $result['http_code']; + if (!empty($result['values']['http_code'])) { + $http_code = $result['values']['http_code']; } else { $http_code = 403; } From 917af8650d4c44a0f325c7d2caab9af11f947834 Mon Sep 17 00:00:00 2001 From: Philipp Batroff Date: Thu, 18 Jul 2019 12:18:38 +0200 Subject: [PATCH 6/6] Changed http return code to 200 if the command was parsed successfully, but a civiCRM error occured. In that case we shouldn't return an error code, because it's an internal error --- proxy/webhook2api.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/proxy/webhook2api.php b/proxy/webhook2api.php index da9a38f..92c993b 100644 --- a/proxy/webhook2api.php +++ b/proxy/webhook2api.php @@ -110,6 +110,7 @@ function webhook2api_processConfiguration($configuration, $post_input) { $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'])) { @@ -123,6 +124,7 @@ function webhook2api_processConfiguration($configuration, $post_input) { $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'])) { @@ -131,15 +133,20 @@ function webhook2api_processConfiguration($configuration, $post_input) { $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($http_code); + http_response_code('200'); } // all done exit();