additional_plugins/serendipity_event_recaptcha/serendipity_event_recaptcha.php
Garvin Hicking d2f63305a3 Added draft of DSGVO GPDR plugin.
Currently supports:

* Show cookie consent layer
* Create footer link to privacy statement (internal, or also a staticpage allowed)
* Allow to enforce visitors need to confirm a checkbox when submitting comments
* Shows information about what s9y does in regards of DSGVO/GPDR
* Shows information about what s9y plugins do in regard of DSGVO/GPDR (new 'legal' property bag)

TODO:

* See todo.txt file.
* Most important: Go through EVERY plugin and add the legal property bag to any plugin that operates on user data.
* Allow to prevent session_start on frontend, allow to patch REMOTE_ADDR
* Warnings when plugin is not installed

+++++ HELP NEEDED +++++
2018-03-29 16:24:46 +02:00

427 lines
18 KiB
PHP

<?php #
if (IN_serendipity !== true) {
die ("Don't hack!");
}
// Probe for a language include with constants. Still include defines later on, if some constants were missing
$probelang = dirname(__FILE__) . '/' . $serendipity['charset'] . 'lang_' . $serendipity['lang'] . '.inc.php';
if (file_exists($probelang)) {
include $probelang;
}
include dirname(__FILE__) . '/lang_en.inc.php';
require_once dirname(__FILE__) . '/recaptcha/recaptchalib.php';
class serendipity_event_recaptcha extends serendipity_event
{
var $error=null;
function introspect(&$propbag)
{
global $serendipity;
$this->title = PLUGIN_EVENT_RECAPTCHA_TITLE;
$propbag->add('name', PLUGIN_EVENT_RECAPTCHA_TITLE);
$propbag->add('description', PLUGIN_EVENT_RECAPTCHA_DESC);
$propbag->add('stackable', false);
$propbag->add('author', 'Christian Brabandt (based on work of Garvin Hicking, Sebastian Nohn)');
$propbag->add('requirements', array(
'serendipity' => '1.0',
'smarty' => '2.6.7',
'php' => '4.1.0'
));
$propbag->add('version', '0.20.3');
$propbag->add('event_hooks', array(
'frontend_configure' => true,
'frontend_saveComment' => true,
'frontend_comment' => true
));
$propbag->add('legal', array(
'services' => array(
'Google reCaptcha' => array(
'url' => 'https://developers.google.com/recaptcha/',
'desc' => 'Transmits captcha form data to Google Servers to check for human (=non robot) input to prevent spam'
)
),
'frontend' => array(
'If logging is enabled, saves user comment data (name, email, url, user agent, ip, comment body, timestamp, referrer) in database or file.',
'Transmits comment data to the Google ReCaptcha API'
),
'backend' => array(
),
'cookies' => array(
),
'sessiondata' => array(
),
'stores_user_input' => true,
'stores_ip' => true,
'uses_ip' => true,
'transmits_user_input' => true
));
$propbag->add('configuration', array(
'info',
'sep',
'hide_for_authors',
'recaptcha',
'recaptcha_style',
'recaptcha_pub',
'recaptcha_priv',
'captchas_ttl',
'logtype',
'logfile'));
$propbag->add('groups', array('ANTISPAM'));
}
function introspect_config_item($name, &$propbag)
{
global $serendipity;
switch($name) {
case 'recaptcha':
$propbag->add('type', 'radio');
$propbag->add('name', PLUGIN_EVENT_RECAPTCHA_RECAPTCHA);
$propbag->add('description', PLUGIN_EVENT_RECAPTCHA_RECAPTCHA_DESC);
$propbag->add('default', 'no');
$propbag->add('radio', array(
'value' => array('yes2', 'no'),
'desc' => array(YES . ' (v2)', NO)
));
break;
case 'recaptcha_pub':
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_RECAPTCHA_RECAPTCHA_PUB);
$propbag->add('description', PLUGIN_EVENT_RECAPTCHA_RECAPTCHA_PUB_DESC);
$propbag->add('default', '');
break;
case 'recaptcha_priv':
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_RECAPTCHA_RECAPTCHA_PRIV);
$propbag->add('description', PLUGIN_EVENT_RECAPTCHA_RECAPTCHA_PRIV_DESC);
$propbag->add('default', '');
break;
case 'recaptcha_style':
$propbag->add('type', 'radio');
$propbag->add('name', PLUGIN_EVENT_RECAPTCHA_RECAPTCHA_STYLE);
$propbag->add('description', PLUGIN_EVENT_RECAPTCHA_RECAPTCHA_STYLE_DESC);
$propbag->add('default', 'red');
$propbag->add('radio', array(
'value' => array('red', 'white', 'blackglass'),
'desc' => array('Red', 'White', 'Blackglass')
));
break;
case 'hide_for_authors':
$_groups =& serendipity_getAllGroups();
$groups = array(
'all' => ALL_AUTHORS,
'none' => NONE
);
foreach($_groups AS $group) {
$groups[$group['confkey']] = $group['confvalue'];
}
$propbag->add('type', 'multiselect');
$propbag->add('name', PLUGIN_EVENT_RECAPTCHA_HIDE);
$propbag->add('description', PLUGIN_EVENT_RECAPTCHA_HIDE_DESC);
$propbag->add('select_values', $groups);
$propbag->add('select_size', 5);
$propbag->add('default', 'all');
break;
case 'logfile':
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_RECAPTCHA_LOGFILE);
$propbag->add('description', PLUGIN_EVENT_RECAPTCHA_LOGFILE_DESC);
$propbag->add('default', $serendipity['serendipityPath'] . 'spamblock.log');
break;
case 'logtype':
$propbag->add('type', 'radio');
$propbag->add('name', PLUGIN_EVENT_RECAPTCHA_LOGTYPE);
$propbag->add('description', PLUGIN_EVENT_RECAPTCHA_LOGTYPE_DESC);
$propbag->add('default', 'db');
$propbag->add('radio', array(
'value' => array('file', 'db', 'none'),
'desc' => array(PLUGIN_EVENT_RECAPTCHA_LOGTYPE_FILE, PLUGIN_EVENT_RECAPTCHA_LOGTYPE_DB, PLUGIN_EVENT_RECAPTCHA_LOGTYPE_NONE)
));
$propbag->add('radio_per_row', '1');
break;
case 'captchas_ttl':
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_RECAPTCHA_CAPTCHAS_TTL);
$propbag->add('description', PLUGIN_EVENT_RECAPTCHA_CAPTCHAS_TTL_DESC);
$propbag->add('default', '7');
break;
case 'info':
$suche='!http(?:s)?:\/\/(?:(?:[^.]*)\.)?([^.\/]*)!';
$result=preg_match($suche,$serendipity['baseURL'],$domain);
$propbag->add('type', 'content');
$propbag->add('default', PLUGIN_EVENT_RECAPTCHA_INFO1.$domain[1]. PLUGIN_EVENT_RECAPTCHA_INFO2);
break;
case 'sep':
$propbag->add('type', 'seperator');
break;
default:
return false;
}
return true;
}
function performConfig(&$bag) {
// set "yes" (recaptcha v1 is active) to "yes2" (recaptcha v2 is active)
//because v1 has been phased out
if ($this->get_config('recaptcha', 'no') === 'yes') { $this->set_config('recaptcha', 'yes2'); };
}
function generate_content(&$title) {
$title = $this->title;
}
// Checks whether the current author is contained in one of the groups that
// need no spam checking
function inGroup() {
global $serendipity;
$checkgroups = explode('^', $this->get_config('hide_for_authors'));
if (!isset($serendipity['authorid']) || !is_array($checkgroups)) {
return false;
}
$mygroups =& serendipity_getGroups($serendipity['authorid'], true);
if (!is_array($mygroups)) {
return false;
}
foreach($checkgroups AS $key => $groupid) {
if ($groupid == 'all') {
return true;
} elseif (in_array($groupid, $mygroups)) {
return true;
}
}
return false;
}
function event_hook($event, &$bag, &$eventData, $addData = null) {
global $serendipity;
$hooks = &$bag->get('event_hooks');
if (isset($hooks[$event])) {
$captchas_ttl = $this->get_config('captchas_ttl', 7);
$_recaptcha = $this->get_config('recaptcha', 'no');
$recaptcha = ($_recaptcha === 'yes' || $_recaptcha === 'yes2' || $_recaptcha !== 'no' || serendipity_db_bool($_recaptcha));
// Check if the entry is older than the allowed amount of time.
// Enforce captchas if that is true of if captchas are activated
// for every entry
$show_captcha = (($recaptcha) && isset($eventData['timestamp']) && ($captchas_ttl < 1 || ($eventData['timestamp'] < (time() - ($captchas_ttl*60*60*24)))) ? true : false);
switch($event) {
case 'frontend_configure':
// set a variable, so that the spamblock plugin can disable the captcha when recaptcha is found.
if ($_recaptcha) {
$serendipity['plugins']['disable_internal_captcha'] = true;
}
return true;
break;
case 'frontend_saveComment':
if (!is_array($eventData) || serendipity_db_bool($eventData['allow_comments'])) {
//$serendipity['csuccess'] = 'true';
$logfile = $this->logfile = $this->get_config('logfile', $serendipity['serendipityPath'] . 'spamblock.log');
// Check whether to allow comments from registered authors
if (serendipity_userLoggedIn() && $this->inGroup()) {
return true;
}
// Captcha checking
if ($show_captcha && $addData['type'] == 'NORMAL') {
$privatekey = $this->get_config('recaptcha_priv');
if ($_POST["recaptcha_response_field"] != 1) {
// interpret "yes" as "yes2"
if ($_recaptcha === 'yes2' || $_recaptcha === 'yes') {
$resp_valid = '';
$resp_error = '';
$url = 'https://www.google.com/recaptcha/api/siteverify';
if (function_exists('serendipity_request_url')) {
$data = serendipity_request_url(
$url,
'POST',
null,
array(
'secret' => $privatekey,
'response' => $_POST["g-recaptcha-response"],
'remoteip' => $_SERVER['REMOTE_ADDR']
)
);
} else {
require_once S9Y_PEAR_PATH . 'HTTP/Request.php';
serendipity_request_start();
$req = new HTTP_Request($url, array('method' => HTTP_REQUEST_METHOD_POST, 'allowRedirects' => true, 'timeout' => 20, 'readTimeout' => array(5,0), 'maxRedirects' => 3));
$req->addPostData('secret', $privatekey);
$req->addPostData('response', $_POST["g-recaptcha-response"]);
$req->addPostData('remoteip', $_SERVER['REMOTE_ADDR']);
$req->sendRequest();
$data = $req->getResponseBody();
serendipity_request_end();
}
if (empty($data)) {
$resp_valid = false;
$resp_error = 'Empty Request';
} else {
$json_data = json_decode($data);
if (!is_object($json_data)) {
$resp_valid = false;
$resp_error = 'Invalid JSON return: ' . $data;
} else {
$resp_valid = $json_data->success;
$resp_error = $json_data->{'error-codes'};
}
}
}
if (!$resp_valid) {
# set the error code so that we can display it
$this->error = $resp_error;
$this->log($logfile, $eventData['id'], 'REJECTED', $this->error, $addData);
$eventData = array('allow_comments' => false);
$serendipity['messagestack']['comments'][] = PLUGIN_EVENT_RECAPTCHA_ERROR_CAPTCHAS;
return false;
}
} else {
return false;
}
}
}
return true;
break;
case 'frontend_comment':
// Check whether to allow comments from registered authors
if (serendipity_userLoggedIn() && $this->inGroup()) {
return true;
}
if ($show_captcha) {
$pubkey = $this->get_config('recaptcha_pub');
$privkey = $this->get_config('recaptcha_priv');
if ( $recaptcha && (($pubkey == null || $pubkey == '') || ($privkey == null || $pubkey == '')) ) {
$recaptcha = false;
//$captchas = true;
printf('<div class="serendipity_center serendipity_msg_important">%s</div>',PLUGIN_EVENT_RECAPTCHA_ERROR_RECAPTCHA);
}
// The response from recaptcha.net
// interpret "yes" as "yes2"
if ($_recaptcha === 'yes2' || $_recaptcha === 'yes') {
echo "<script src='https://www.google.com/recaptcha/api.js'></script>";
echo '<div class="g-recaptcha" data-sitekey="' . $pubkey . '"></div>';
}
}
return true;
break;
default:
return false;
break;
}
} else {
return false;
}
}
function log($logfile, $id, $switch, $reason, $comment) {
global $serendipity;
$method = $this->get_config('logtype');
switch($method) {
case 'file':
if (empty($logfile)) {
return;
}
$fp = @fopen($logfile, 'a+');
if (!is_resource($fp)) {
return;
}
fwrite($fp, sprintf(
'[%s] - [%s: %s] - [#%s, Name "%s", E-Mail "%s", URL "%s", User-Agent "%s", IP %s] - [%s]' . "\n",
date('Y-m-d H:i:s', serendipity_serverOffsetHour()),
$switch,
$reason,
$id,
str_replace("\n", ' ', $comment['name']),
str_replace("\n", ' ', $comment['email']),
str_replace("\n", ' ', $comment['url']),
str_replace("\n", ' ', $_SERVER['HTTP_USER_AGENT']),
$_SERVER['REMOTE_ADDR'],
str_replace("\n", ' ', $comment['comment'])
));
fclose($fp);
break;
case 'none':
return;
break;
case 'db':
default:
$q = sprintf("INSERT INTO {$serendipity['dbPrefix']}spamblocklog
(timestamp, type, reason, entry_id, author, email, url, useragent, ip, referer, body)
VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')",
serendipity_serverOffsetHour(),
serendipity_db_escape_string($switch),
serendipity_db_escape_string($reason),
serendipity_db_escape_string($id),
serendipity_db_escape_string($comment['name']),
serendipity_db_escape_string($comment['email']),
serendipity_db_escape_string($comment['url']),
serendipity_db_escape_string($_SERVER['HTTP_USER_AGENT']),
serendipity_db_escape_string($_SERVER['REMOTE_ADDR']),
serendipity_db_escape_string(isset($_SESSION['HTTP_REFERER']) ? $_SESSION['HTTP_REFERER'] : $_SERVER['HTTP_REFERER']),
serendipity_db_escape_string($comment['comment'])
);
serendipity_db_query($q);
break;
}
}
}
/* vim: set sts=4 ts=4 expandtab : */