forked from NikolaiT/SVG-Captcha
-
Notifications
You must be signed in to change notification settings - Fork 1
/
plugin.php
565 lines (480 loc) · 23.9 KB
/
plugin.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
<?php
/**
* Plugin Name: SVGCaptcha
* Plugin URI: http://incolumitas.com/SVGCaptcha
* Description: This plugin generates SVG captchas and displays them on the login form and comment forms. You can customize the inner working of SVGCaptcha exhaustively and you can react to spamming attacks with a increased difficulty for instance.
* Version: 0.2
* Author: Nikolai Tschacher
* Author URI: http://incolumitas.com/about
* License: A "Slug" license name e.g. GPL2
*/
/* Copyright 2013 Nikolai Tschacher (email : admin *[at]* incolumitas.com)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2, as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
defined('ABSPATH') or exit;
class SVGCaptchaWordpressPlugin {
/**
* The current administration menu options.
*/
private $captcha_options;
/**
* The SVGCaptcha instance.
*/
private $svgCaptcha;
/**
* SVGCaptcha data (SVG string)
*/
private $svg_output;
/**
* SVGCaptcha answer;
*/
private $captcha_answer;
/**
* Disable that if you use this plugin on your own server.
* Own my site for example, I need to style the form for the horizontal bootstrap 3 form
*/
const CUSTOM_FORM_STYLE = true;
/**
* Should we consider the case of the captcha?
*/
private $case_sensitive = true;
/**
* Start up.
*/
public function __construct() {
session_start();
if (!class_exists('SVGCaptcha')) {
include_once("src/SVGCaptcha.php"); // include the captcha lib
}
// Define the default settings:
// Settings are divided in sections. Each section
// consists of fields. Eeach session field consists of a key and two values:
// 0: The default value,
// 1: all possible values (Can be an array, if no default value exists/makes sense this element must be null)
// 2: and the field title
// 3: and which callback to use. The callback then receives the whole field array as arg.
$this->dsettings = Array(
'general_settings' => array(
'description' => 'SVGCaptcha basic settings',
'sd' => array('captcha_difficulty' => array('easy', array('easy', 'medium', 'hard'), "Captcha difficulty", 'select_callback'), // The SVGCaptcha difficulty
'captcha_length' => array(4, 5, "Length of the captcha", 'text_callback'), // The captcha length in chars.
'captcha_border' => array(4, 'border: 1px solid 0f0;', "CSS code for the captcha border", 'text_callback'), // The captcha border. CSS property.
'captcha_width' => array(300, null, 'The width of the captcha in pixels', 'text_callback'),
'captcha_height' => array(130, null, 'The height of the captcha in pixels', 'text_callback'),
'enable_captcha_on_comments' => array(True, null, "Whether to enable the captcha on comment form", 'checkbox_callback'),
'enable_captcha_on_login' => array(True, null, "Whether to enable the captcha on the login form", 'checkbox_callback'),
'captcha_case_sensitive' => array(False, null, 'If the captcha is case sensitive (AbCd != ABcD)', 'checkbox_callback'),
'captcha_preview' => array(True, null, "<hr><h3><strong>Preview</strong> for the current settings.</h3><hr>" . $this->svgc_reload_link(), 'captcha_preview_callback'))),
'custom_captcha_settings' => array(
'description' => 'Use a custom captcha instead of a predefined (This overwrites the difficulty level set above)',
'sd' => array(
'custom_captcha' => array(False, null, "<strong>Enable</strong> user defined captchas by the settings below. The easy/medium/hard difficulty levels are disabled!", 'checkbox_callback'), // If set to true, SVGCaptcha will use a user defined captcha.
// This values are the parameters for the user defined captcha image.
'cg_glyph_offsetting' => array(False, null, "If the captcha should use glyph offsetting as a obfuscation technique", 'checkbox_callback'),
'cg_glyph_fragments' => array(False, null, "If the captcha should use glyph fragments to distort the image", 'checkbox_callback'),
'cg_transformations' => array(False, null, "If affine transformationsn should be enabled", 'checkbox_callback'),
'cg_approx_shapes' => array(False, null, "Whether to approximate shapes", 'checkbox_callback'),
'cg_change_degree' => array(False, null, "If the captcha library should change the degree of splines.", 'checkbox_callback'),
'cg_split_curve' => array(False, null, "Whether to split curves as a distortion technique", 'checkbox_callback'),
'cg_shapeify' => array(False, null, "Inject randomly generated shapes into the captcha", 'checkbox_callback')))
);
// Lableify all descriptions :/
foreach ($this->dsettings as $key => $value) {
foreach ($value["sd"] as $id => $option) {
$this->dsettings[$key]["sd"][$id][2] = '<label for="' . $id . '">' . $option[2] . '</label>';
}
}
$this->captcha_options = get_option('svgc_options');
// If the options are not set, set them to the default values.
if (empty($this->captcha_options)) {
foreach ($this->dsettings as $key => $value) {
foreach ($value['sd'] as $skey => $svalue) {
if ($svalue[0] != False) {
$options[$skey] = $svalue[0];
}
}
}
$this->captcha_options = $options;
}
$this->case_sensitive = $this->captcha_options["captcha_case_sensitive"];
$this->hook2wp();
}
private function hook2wp() {
// Enqueue and register ajax script for reload captcha capability
wp_register_script('captcha-reload', plugin_dir_url(__FILE__) . 'js/reload_captcha.js', array('jquery'));
wp_enqueue_script('captcha-reload');
// code to declare the URL to the file handling the AJAX request
wp_localize_script('captcha-reload', 'myAjaxObject', array('ajaxurl' => admin_url('admin-ajax.php')));
// Add ajax reload link handler
add_action('wp_ajax_nopriv_svgc_captcha_reload', array($this, 'svgc_captcha_reload'));
add_action('wp_ajax_svgc_captcha_reload', array($this, 'svgc_captcha_reload'));
if ($this->captcha_options['enable_captcha_on_comments']) {
// Add captcha to comment form
add_filter('comment_form_defaults', array($this, 'svgc_comment_form_defaults')); // Add a filter to verify if the captcha in the comment section was correct.
add_filter('preprocess_comment', array($this, 'svgc_validate_comment_captcha'));
}
if ($this->captcha_options['enable_captcha_on_login']) {
// Ad custom captcha field to login form
add_action('login_form', array($this, 'svgc_login_form_defaults'));
add_filter('authenticate', array($this, 'svgc_validate_login_captcha'), 30, 3); // Validate captcha in login form.
}
add_action('admin_menu', array($this, 'add_plugin_page'));
add_action('admin_init', array($this, 'page_init'));
// upon deinstallation
register_uninstall_hook(__FILE__, array($this, 'on_uninstall'));
// upon activation
register_activation_hook(__FILE__, array($this, 'on_activation'));
}
public function on_uninstall() {
if (!current_user_can('activate_plugins'))
return;
check_admin_referer('bulk-plugins');
// Important: Check if the file is the one
// that was registered during the uninstall hook.
if (__FILE__ != WP_UNINSTALL_PLUGIN)
return;
if (!delete_option('svgc_options')) {
wp_die("Couldn't uninstall plugin");
}
}
public function on_activation() {
if (!current_user_can('activate_plugins'))
return;
$plugin = isset($_REQUEST['plugin']) ? $_REQUEST['plugin'] : '';
check_admin_referer("activate-plugin_{$plugin}");
// If there is such a option, delete it.
if (get_option('svgc_options', False) != False) {
delete_option('svgc_options');
}
}
/**
* Create the captcha and store the encrypted solution in a hidden field.
* Alternatives:
* Use a session variable (Bad: Uses files = slow). Maybe the best.
* Use add_option() [Using a database] (Bad: Needs to be written to = slow). Maybe usable.
* Use a encrypted hidden field. Bad, unsecure.
* Use a cookie. (Bad: Can't make it working with wordpress.)
*
* @param type $default
* @return string
*/
public function svgc_comment_form_defaults($default) {
if (!is_admin()) {
$this->svgc_get_captcha();
if (self::CUSTOM_FORM_STYLE === True) {
$default['fields']['email'] .=
'<div class="form-group">
<label class="col-sm-2 control-label" for="svgc_answer">' . __('Captcha', 'SVGCaptcha') . '<span class="required"> *</span></label>
<div class="col-sm-10">
<input id="svgc_answer" class="form-control" name="svgc_answer" size="30" type="text" />
<div id="SVGCaptchaContainer" style="padding: 10px 0px;">' . $this->svg_output . '</div>
' . $this->svgc_reload_link() . '
</div>
</div>';
} else {
$default['fields']['email'] .=
'<p class="comment-form-captcha"><label for="svgc_answer">' . __('Captcha', 'SVGCaptcha') . '</label>
<span class="required">*</span>
<input id="svgc_answer" name="svgc_answer" size="30" type="text" />
<div id="SVGCaptchaContainer" style="padding: 10px 0px;">' . $this->svg_output . '</div>
' . $this->svgc_reload_link();
}
}
return $default;
}
public function svgc_validate_comment_captcha($commentdata) {
if (!is_admin()) { /* Admins excluded. They should't be prevented from spamming... */
if (empty($_POST['svgc_answer']))
wp_die(__('Error: You need to enter the captcha.', 'SVGCaptcha'));
$answer = strip_tags($_POST['svgc_answer']);
if (!$this->svgc_check($answer))/* Case insensitive comparing */
wp_die(__('Error: Your supplied captcha is incorrect.', 'SVGCaptcha'));
}
return $commentdata;
}
public function svgc_login_form_defaults() {
if (!is_admin()) {
$this->svgc_get_captcha();
//Get and set any values already sent
$user_captcha = ( isset($_POST['svgc_answer']) ) ? $_POST['svgc_answer'] : '';
?>
<div class="form-group">
<div id="SVGCaptchaContainer" style="padding: 10px 0px;"><?php echo $this->svg_output ?></div>
<?php echo $this->svgc_reload_link(); ?>
<label for="svgc_answer" class="col-sm-2 control-label"><?php _e('Captcha', 'SVGCaptcha') ?><span class="required"> *</span></label>
<input type="text" name="svgc_answer" id="svgc_answer" class="form-control" value="<?php echo esc_attr(stripslashes($user_captcha)); ?>" size="25" />
</div>
<?php
}
}
public function svgc_validate_login_captcha($user, $username, $password) {
if (!is_admin()) { /* Whenever a admin tries to login -.- */
if (empty($_POST['svgc_answer'])) {
return new WP_Error('invalid_captcha', __("You need to enter a captcha in order to login.", 'SVGCaptcha'));
} else {
$answer = strip_tags($_POST['svgc_answer']);
if (!$this->svgc_check($answer, $solution)) {/* Case insensitive comparing */
return new WP_Error('invalid_captcha', __("Your supplied captcha is incorrect.", 'SVGCaptcha'));
} else {
return $user;
}
}
}
}
/**
* Returns html that provides a capability to reload the current captcha via ajax.
*
* https://codex.wordpress.org/AJAX_in_Plugins
*/
public function svgc_reload_link() {
return '<a id="svgc-reload" style="cursor: pointer">reload captcha</a>';
}
public function svgc_captcha_reload() {
if ($_REQUEST["reload"] == "reload") {
$this->svgc_get_captcha();
echo $this->svg_output;
}
die();
}
/**
* Checks whether the user provided answer is correct.
*/
public function svgc_check($answer) {
$this->captcha_answer = $_SESSION['svgc_solution'];
if ($this->case_sensitive) {
return (strcmp($answer, $this->captcha_answer) == 0) ? True : False;
} else {
return (strcasecmp($answer, $this->captcha_answer) == 0) ? True : False;
}
}
/**
* Choses a random captcha from the pool and returns the corresponding image path.
* Sets global variable $captcha_value to the value (The solution the user has to enter)
* of the captcha.
*/
public function svgc_get_captcha() {
// and immediately create a instance determined by the specified settings (if given) or else by the default variables.
$lu = array('easy' => SVGCaptcha::EASY, 'medium' => SVGCaptcha::MEDIUM, 'hard' => SVGCaptcha::HARD);
// Check if we have a custom specified captcha or a predefined one (easy/medium/hard)
if ($this->captcha_options["custom_captcha"] == True) {
$custom_settings = array(
'glyph_offsetting' => array('apply' => False, 'h' => 1, 'v' => 0.5, 'mh' => 8), // Needs to be anabled by default
'glyph_fragments' => array('apply' => False, 'r_num_frag' => range(0, 6), 'frag_factor' => 2),
'transformations' => array('apply' => False, 'rotate' => True, 'skew' => True, 'scale' => True, 'shear' => False, 'translate' => True),
'approx_shapes' => array('apply' => False, 'p' => 3, 'r_al_num_lines' => range(10, 30)),
'change_degree' => array('apply' => False, 'p' => 5),
'split_curve' => array('apply' => False, 'p' => 5),
'shapeify' => array('apply' => False, 'r_num_shapes' => range(0, 6), 'r_num_gp' => range(4, 10))
);
foreach ($custom_settings as $key => $value) {
$custom_settings[$key]["apply"] = $this->captcha_options["cg_" . $key];
}
$this->svgCaptcha = SVGCaptcha::getInstance(
$this->captcha_options['captcha_length'], $width = $this->captcha_options['captcha_width'], $height = $this->captcha_options['captcha_height'], $difficulty = $custom_settings
);
} else {
$this->svgCaptcha = SVGCaptcha::getInstance(
$this->captcha_options['captcha_length'], $width = $this->captcha_options['captcha_width'], $height = $this->captcha_options['captcha_height'], $difficulty = $lu[$this->captcha_options['captcha_difficulty']]
);
}
list($this->captcha_answer, $this->svg_output) = $this->svgCaptcha->getSVGCaptcha();
$this->captcha_answer = ($this->captcha_options['captcha_case_sensitive'] == True) ? $this->captcha_answer : strtolower($this->captcha_answer);
$_SESSION['svgc_solution'] = $this->captcha_answer;
}
/**
* Get random pseudo bytes for encryption.
*/
public function svgc_random_hex_bytes($length = 32) {
$cstrong = False;
$bytes = openssl_random_pseudo_bytes($length, $cstrong);
if ($cstrong == False)
return False;
else
return $bytes;
}
/**
* Add options page
*/
public function add_plugin_page() {
// This page will be under "Settings"
$hook_suffix = add_options_page(
__('SVG-Captcha settings', 'SVGCaptcha'), __('SVG-Captcha', 'SVGCaptcha'), 'manage_options', 'svgc_submenu', array($this, 'create_admin_page')
);
// Add javascript to the admin page
add_action("load-" . $hook_suffix, array($this, "svgc_load_submenu_js"));
}
public function svgc_load_submenu_js() {
add_action("admin_enqueue_scripts", array($this, 'svgc_captcha_reload_admin'));
add_action("admin_enqueue_scripts", array($this, 'svgc_custom_captcha_toggle_admin'));
}
public function svgc_captcha_reload_admin() {
wp_register_script('captcha-reload-admin', plugin_dir_url(__FILE__) . 'js/reload_captcha_admin.js', array('jquery'));
wp_enqueue_script('captcha-reload-admin');
}
public function svgc_custom_captcha_toggle_admin() {
wp_register_script('toggle-custom-captcha', plugin_dir_url(__FILE__) . 'js/toggle-custom-captcha.js', array('jquery'));
wp_enqueue_script('toggle-custom-captcha');
}
/**
* Options page callback
*/
public function create_admin_page() {
// Set class property
if (get_option('svgc_options', False) != False) {
$this->captcha_options = get_option('svgc_options');
}
?>
<div class="wrap">
<?php screen_icon(); ?>
<h2>SVGCaptcha settings</h2>
<form method="post" action="options.php">
<?php
// This prints out all hidden setting fields
submit_button();
settings_fields('svgc_options_group');
do_settings_sections('svgc_submenu');
submit_button();
?>
</form>
</div>
<?php
}
/**
* Register and add settings.
*/
public function page_init() {
register_setting(
'svgc_options_group', // Option group
'svgc_options', // Option name
array($this, 'sanitize') // Sanitize
);
// Add settings sections
foreach ($this->dsettings as $key => $value) {
add_settings_section(
$key, // ID
$value['description'], // Title
array($this, 'print_section_info'), // Callback
'svgc_submenu' // Page
);
}
// Add settings fields
foreach ($this->dsettings as $section => $value) {
foreach ($value['sd'] as $key => $value) {
add_settings_field(
$key, // ID
$value[2], // Title
array($this, $value[3]), // Callback
'svgc_submenu', // Page
$section, // Section
array_merge((array) $key, $value)
);
}
}
}
/**
* Sanitize each setting field as needed
*
* @param array $input Contains all settings fields as array keys
*/
public function sanitize($input) {
$new_input = array();
foreach ($input as $key => $value) {
$new_input[$key] = sanitize_text_field($value);
}
return $new_input;
}
public function text_callback($d) {
printf(
'<input type="text" id="%2$s" name="svgc_options[%2$s]" value="%1$s" />', isset($this->captcha_options[$d[0]]) ? esc_attr($this->captcha_options[$d[0]]) : esc_attr($d[1]), esc_attr($d[0])
);
}
/*
* d[0] = the key of the field.
* d[1] = the default value for the field.
* d[2] = field title
*/
public function select_callback($d) {
if (!is_array($d[2]) || $d[2] == null)
wp_die(__('Invalid value for all possible values', 'SVGCaptcha'));
// Build select options
foreach ($d[2] as $option) {
$select_html .= '<option value="' . $option . '" ' . selected($this->captcha_options[$d[0]], $option, false) . ' >' . strtoupper(substr($option, 0, 1)) . substr($option, 1, strlen(option));
}
printf(
'<select id="%1$s" name="svgc_options[%1$s]">%2$s</select>', esc_attr($d[0]), $select_html
);
}
public function checkbox_callback($d) {
$checked = isset($this->captcha_options[$d[0]]) ? checked($this->captcha_options[$d[0]], True, False) : checked($this->by_key($this->dsettings, $d[0]), True, False);
printf(
'<input type="checkbox" id="%1$s" name="svgc_options[%1$s]" value="1" ' . $checked . ' />', esc_attr($d[0])
);
}
public function captcha_preview_callback($d) {
$this->svgc_get_captcha();
print '<figure id="SVGCaptchaPreviewContainer"' . $this->svg_output . '<figcaption>The solution for the generated captcha is <strong style="color:red">' . $this->captcha_answer . '</strong></figcaption></div>';
}
public function custom_captcha_preview_callback($d) {
$this->svgc_get_captcha();
print '<figure id="SVGCaptchaPreviewContainer"' . $this->svg_output . '<figcaption>The solution for the generated captcha is <strong style="color:red">' . $this->captcha_answer . '</strong></figcaption></div>';
}
/**
* Print the Section text
*/
public function print_section_info() {
print 'Enter your settings below: ';
}
public function by_key($arr, $key) {
if (isset($arr[$key])) {
return $arr[$key];
} else
if (is_array($arr)) {
foreach ($arr as $value) {
$this->by_key($value, $key);
}
}
}
/**
* Encrypt data using AES_256 with CBC mode. Prepends IV on ciphertext.
*
*/
public function svgc_encrypt($plaintext) {
if (false == ($key = get_option('ccpatcha_encryption_key')))
wp_die(__('Encryption error: could not retrieve encryption key from options database.', 'SVGCaptcha'));
$key = base64_decode($key); /* Get binary key */
if (32 != ($key_size = strlen($key)))
wp_die(__('Encryption error: Invalid keysize.', 'SVGCaptcha'));
# Create random IV to use with CBC mode.
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $plaintext, MCRYPT_MODE_CBC, $iv);
# Prepend the IV on the ciphertext for decryption (Must not be confidential).
$ciphertext = $iv . $ciphertext;
# Encode such that it can be represented as astring.
return base64_encode($ciphertext);
}
/**
* Decrypt using AES_256 with the IV prepended on base64_encoded ciphertext.
*/
public function svgc_decrypt($ciphertext) {
if (false == ($key = get_option('ccpatcha_encryption_key')))
wp_die(__('Decryption error: could not retrieve encryption key from options database.', 'SVGCaptcha'));
$key = base64_decode($key); /* Get binary key */
$ciphertext = base64_decode($ciphertext);
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$iv = substr($ciphertext, 0, $iv_size);
$ciphertext = substr($ciphertext, $iv_size);
$plaintext = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $ciphertext, MCRYPT_MODE_CBC, $iv);
return $plaintext;
}
}
$dummy = new SVGCaptchaWordpressPlugin();