Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Partners
CiviCooP
Article 19
CiviProxy
Commits
8aeaa312
Commit
8aeaa312
authored
Nov 26, 2019
by
BjoernE
👨👩👧👦
Browse files
[#32] merged webhook2api (dev_32)
parents
5763a2b7
917af865
Changes
2
Hide whitespace changes
Inline
Side-by-side
proxy/config.dist.php
View file @
8aeaa312
...
...
@@ -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
proxy/webhook2api.php
0 → 100644
View file @
8aeaa312
<?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!"
);
}
}
\ No newline at end of file
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment