PluginProbe
Google Authenticator / 0.44
Google Authenticator v0.44
trunk 0.20 0.30 0.35 0.36 0.37 0.38 0.39 0.40 0.41 0.42 0.43 0.44 0.45 0.46 0.47 0.48 0.50 0.51 0.52 0.53 0.54 0.55 0.56
google-authenticator / google-authenticator.php

google-authenticator.php in Google Authenticator 0.44, at google-authenticator.php

499 lines 18.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: Google Authenticator
4 Plugin URI: http://henrik.schack.dk/google-authenticator-for-wordpress
5 Description: Two-Factor Authentication for WordPress using the Android/iPhone/Blackberry app as One Time Password generator.
6 Author: Henrik Schack
7 Version: 0.44
8 Author URI: http://henrik.schack.dk/
9 Compatibility: WordPress 3.5
10 Text Domain: google-authenticator
11 Domain Path: /lang
12
13 ----------------------------------------------------------------------------
14
15 Thanks to Bryan Ruiz for his Base32 encode/decode class, found at php.net.
16 Thanks to Tobias B�thge for his major code rewrite and German translation.
17 Thanks to Pascal de Bruijn for his relaxed mode idea.
18 Thanks to Daniel Werl for his usability tips.
19 Thanks to Dion Hulse for his bugfixes.
20 Thanks to Aldo Latino for his Italian translation.
21 Thanks to Kaijia Feng for his Simplified Chinese translation.
22
23 ----------------------------------------------------------------------------
24
25 Copyright 2013 Henrik Schack (email : henrik@schack.dk)
26
27 This program is free software; you can redistribute it and/or modify
28 it under the terms of the GNU General Public License as published by
29 the Free Software Foundation; either version 2 of the License, or
30 (at your option) any later version.
31
32 This program is distributed in the hope that it will be useful,
33 but WITHOUT ANY WARRANTY; without even the implied warranty of
34 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 GNU General Public License for more details.
36
37 You should have received a copy of the GNU General Public License
38 along with this program; if not, write to the Free Software
39 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
40 */
41
42 class GoogleAuthenticator {
43
44 static $instance; // to store a reference to the plugin, allows other plugins to remove actions
45
46 /**
47 * Constructor, entry point of the plugin
48 */
49 function __construct() {
50 self::$instance = $this;
51 add_action( 'init', array( $this, 'init' ) );
52 }
53
54 /**
55 * Initialization, Hooks, and localization
56 */
57 function init() {
58 require_once( 'base32.php' );
59
60 add_action( 'login_form', array( $this, 'loginform' ) );
61 add_action( 'login_footer', array( $this, 'loginfooter' ) );
62 add_filter( 'authenticate', array( $this, 'check_otp' ), 50, 3 );
63
64 if ( defined( 'DOING_AJAX' ) && DOING_AJAX )
65 add_action( 'wp_ajax_GoogleAuthenticator_action', array( $this, 'ajax_callback' ) );
66
67 add_action( 'personal_options_update', array( $this, 'personal_options_update' ) );
68 add_action( 'profile_personal_options', array( $this, 'profile_personal_options' ) );
69 add_action( 'edit_user_profile', array( $this, 'edit_user_profile' ) );
70 add_action( 'edit_user_profile_update', array( $this, 'edit_user_profile_update' ) );
71
72 load_plugin_textdomain( 'google-authenticator', false, basename( dirname( __FILE__ ) ) . '/lang' );
73 }
74
75 /**
76 * Check the verification code entered by the user.
77 */
78 function verify( $secretkey, $thistry, $relaxedmode ) {
79
80 // Did the user enter 6 digits ?
81 if ( strlen( $thistry ) != 6) {
82 return false;
83 } else {
84 $thistry = intval ( $thistry );
85 }
86
87 // If user is running in relaxed mode, we allow more time drifting
88 // �4 min, as opposed to � 30 seconds in normal mode.
89 if ( $relaxedmode == 'enabled' ) {
90 $firstcount = -8;
91 $lastcount = 8;
92 } else {
93 $firstcount = -1;
94 $lastcount = 1;
95 }
96
97 $tm = floor( time() / 30 );
98
99 $secretkey=Base32::decode($secretkey);
100 // Keys from 30 seconds before and after are valid aswell.
101 for ($i=$firstcount; $i<=$lastcount; $i++) {
102 // Pack time into binary string
103 $time=chr(0).chr(0).chr(0).chr(0).pack('N*',$tm+$i);
104 // Hash it with users secret key
105 $hm = hash_hmac( 'SHA1', $time, $secretkey, true );
106 // Use last nipple of result as index/offset
107 $offset = ord(substr($hm,-1)) & 0x0F;
108 // grab 4 bytes of the result
109 $hashpart=substr($hm,$offset,4);
110 // Unpak binary value
111 $value=unpack("N",$hashpart);
112 $value=$value[1];
113 // Only 32 bits
114 $value = $value & 0x7FFFFFFF;
115 $value = $value % 1000000;
116 if ( $value == $thistry ) {
117 return true;
118 }
119 }
120 return false;
121 }
122
123 /**
124 * Create a new random secret for the Google Authenticator app.
125 * 16 characters, randomly chosen from the allowed Base32 characters
126 * equals 10 bytes = 80 bits, as 256^10 = 32^16 = 2^80
127 */
128 function create_secret() {
129 $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; // allowed characters in Base32
130 $secret = '';
131 for ( $i = 0; $i < 16; $i++ ) {
132 $secret .= substr( $chars, wp_rand( 0, strlen( $chars ) - 1 ), 1 );
133 }
134 return $secret;
135 }
136
137
138 /**
139 * Add verification code field to login form.
140 */
141 function loginform() {
142 echo "\t<p>\n";
143 echo "\t\t<label title=\"".__('If you don\'t have Google Authenticator enabled for your WordPress account, leave this field empty.','google-authenticator')."\">".__('Google Authenticator code','google-authenticator')."<span id=\"google-auth-info\"></span><br />\n";
144 echo "\t\t<input type=\"text\" name=\"googleotp\" id=\"user_email\" class=\"input\" value=\"\" size=\"20\" /></label>\n";
145 echo "\t</p>\n";
146 }
147
148 /**
149 * Disable autocomplete on Google Authenticator code input field.
150 */
151 function loginfooter() {
152 echo "\n<script type=\"text/javascript\">\n";
153 echo "\ttry{\n";
154 echo "\t\tdocument.getElementById('user_email').setAttribute('autocomplete','off');\n";
155 echo "\t} catch(e){}\n";
156 echo "</script>\n";
157 }
158
159 /**
160 * Login form handling.
161 * Check Google Authenticator verification code, if user has been setup to do so.
162 * @param wordpressuser
163 * @return user/loginstatus
164 */
165 function check_otp( $user, $username = '', $password = '' ) {
166 // Store result of loginprocess, so far.
167 $userstate = $user;
168
169 // Get information on user, we need this in case an app password has been enabled,
170 // since the $user var only contain an error at this point in the login flow.
171 $user = get_userdatabylogin( $username );
172
173 // Does the user have the Google Authenticator enabled ?
174 if ( trim(get_user_option( 'googleauthenticator_enabled', $user->ID ) ) == 'enabled' ) {
175
176 // Get the users secret
177 $GA_secret = trim( get_user_option( 'googleauthenticator_secret', $user->ID ) );
178
179 // Figure out if user is using relaxed mode ?
180 $GA_relaxedmode = trim( get_user_option( 'googleauthenticator_relaxedmode', $user->ID ) );
181
182 // Get the verification code entered by the user trying to login
183 $otp = trim( $_POST[ 'googleotp' ] );
184
185 // Valid code ?
186 if ( $this->verify( $GA_secret, $otp, $GA_relaxedmode ) ) {
187 return $userstate;
188 } else {
189 // No, lets see if an app password is enabled, and this is an XMLRPC / APP login ?
190 if ( trim( get_user_option( 'googleauthenticator_pwdenabled', $user->ID ) ) == 'enabled' && ( defined('XMLRPC_REQUEST') || defined('APP_REQUEST') ) ) {
191 $GA_passwords = json_decode( get_user_option( 'googleauthenticator_passwords', $user->ID ) );
192 $passwordsha1 = trim($GA_passwords->{'password'} );
193 $usersha1 = sha1( strtoupper( str_replace( ' ', '', $password ) ) );
194 if ( $passwordsha1 == $usersha1 ) {
195 return new WP_User( $user->ID );
196 } else {
197 // Wrong XMLRPC/APP password !
198 return new WP_Error( 'invalid_google_authenticator_password', __( '<strong>ERROR</strong>: The Google Authenticator password is incorrect.', 'google-authenticator' ) );
199 }
200 } else {
201 return new WP_Error( 'invalid_google_authenticator_token', __( '<strong>ERROR</strong>: The Google Authenticator code is incorrect or has expired.', 'google-authenticator' ) );
202 }
203 }
204 }
205 // Google Authenticator isn't enabled for this account,
206 // just resume normal authentication.
207 return $userstate;
208 }
209
210
211 /**
212 * Extend personal profile page with Google Authenticator settings.
213 */
214 function profile_personal_options() {
215 global $user_id, $is_profile_page;
216
217 // If editing of Google Authenticator settings has been disabled, just return
218 $GA_hidefromuser = trim( get_user_option( 'googleauthenticator_hidefromuser', $user_id ) );
219 if ( $GA_hidefromuser == 'enabled') return;
220
221 $GA_secret = trim( get_user_option( 'googleauthenticator_secret', $user_id ) );
222 $GA_enabled = trim( get_user_option( 'googleauthenticator_enabled', $user_id ) );
223 $GA_relaxedmode = trim( get_user_option( 'googleauthenticator_relaxedmode', $user_id ) );
224 $GA_description = trim( get_user_option( 'googleauthenticator_description', $user_id ) );
225 $GA_pwdenabled = trim( get_user_option( 'googleauthenticator_pwdenabled', $user_id ) );
226 $GA_password = trim( get_user_option( 'googleauthenticator_passwords', $user_id ) );
227
228 // We dont store the generated app password in cleartext so there is no point in trying
229 // to show the user anything except from the fact that a password exists.
230 if ( $GA_password != '' ) {
231 $GA_password = "XXXX XXXX XXXX XXXX";
232 }
233
234 // In case the user has no secret ready (new install), we create one.
235 if ( '' == $GA_secret ) {
236 $GA_secret = $this->create_secret();
237 }
238
239 // Use "WordPress Blog" as default description
240 if ( '' == $GA_description ) {
241 $GA_description = __( 'WordPressBlog', 'google-authenticator' );
242 }
243
244 echo "<h3>".__( 'Google Authenticator Settings', 'google-authenticator' )."</h3>\n";
245
246 echo "<table class=\"form-table\">\n";
247 echo "<tbody>\n";
248 echo "<tr>\n";
249 echo "<th scope=\"row\">".__( 'Active', 'google-authenticator' )."</th>\n";
250 echo "<td>\n";
251 echo "<input name=\"GA_enabled\" id=\"GA_enabled\" class=\"tog\" type=\"checkbox\"" . checked( $GA_enabled, 'enabled', false ) . "/>\n";
252 echo "</td>\n";
253 echo "</tr>\n";
254
255 // Create URL for the Google charts QR code generator.
256 $chl = urlencode( "otpauth://totp/{$GA_description}?secret={$GA_secret}" );
257 $qrcodeurl = "https://chart.googleapis.com/chart?cht=qr&amp;chs=300x300&amp;chld=H|0&amp;chl={$chl}";
258
259 if ( $is_profile_page || IS_PROFILE_PAGE ) {
260 echo "<tr>\n";
261 echo "<th scope=\"row\">".__( 'Relaxed mode', 'google-authenticator' )."</th>\n";
262 echo "<td>\n";
263 echo "<input name=\"GA_relaxedmode\" id=\"GA_relaxedmode\" class=\"tog\" type=\"checkbox\"" . checked( $GA_relaxedmode, 'enabled', false ) . "/><span class=\"description\">".__(' Relaxed mode allows for more time drifting on your phone clock (&#177;4 min).','google-authenticator')."</span>\n";
264 echo "</td>\n";
265 echo "</tr>\n";
266
267 echo "<tr>\n";
268 echo "<th><label for=\"GA_description\">".__('Description','google-authenticator')."</label></th>\n";
269 echo "<td><input name=\"GA_description\" id=\"GA_description\" value=\"{$GA_description}\" type=\"text\" size=\"25\" /><span class=\"description\">".__(' Description that you\'ll see in the Google Authenticator app on your phone.','google-authenticator')."</span><br /></td>\n";
270 echo "</tr>\n";
271
272 echo "<tr>\n";
273 echo "<th><label for=\"GA_secret\">".__('Secret','google-authenticator')."</label></th>\n";
274 echo "<td>\n";
275 echo "<input name=\"GA_secret\" id=\"GA_secret\" value=\"{$GA_secret}\" readonly=\"readonly\" type=\"text\" size=\"25\" />";
276 echo "<input name=\"GA_newsecret\" id=\"GA_newsecret\" value=\"".__("Create new secret",'google-authenticator')."\" type=\"button\" class=\"button\" />";
277 echo "<input name=\"show_qr\" id=\"show_qr\" value=\"".__("Show/Hide QR code",'google-authenticator')."\" type=\"button\" class=\"button\" onclick=\"jQuery('#GA_QR_INFO').toggle('slow');\" />";
278 echo "</td>\n";
279 echo "</tr>\n";
280
281 echo "<tr>\n";
282 echo "<th></th>\n";
283 echo "<td><div id=\"GA_QR_INFO\" style=\"display: none\" >";
284 echo "<img id=\"GA_QRCODE\" src=\"{$qrcodeurl}\" alt=\"QR Code\"/>";
285 echo '<span class="description"><br/> ' . __( 'Scan this with the Google Authenticator app.', 'google-authenticator' ) . '</span>';
286 echo "</div></td>\n";
287 echo "</tr>\n";
288
289 echo "<tr>\n";
290 echo "<th scope=\"row\">".__( 'Enable App password', 'google-authenticator' )."</th>\n";
291 echo "<td>\n";
292 echo "<input name=\"GA_pwdenabled\" id=\"GA_pwdenabled\" class=\"tog\" type=\"checkbox\"" . checked( $GA_pwdenabled, 'enabled', false ) . "/><span class=\"description\">".__(' Enabling an App password will decrease your overall login security.','google-authenticator')."</span>\n";
293 echo "</td>\n";
294 echo "</tr>\n";
295
296 echo "<tr>\n";
297 echo "<th></th>\n";
298 echo "<td>\n";
299 echo "<input name=\"GA_password\" id=\"GA_password\" readonly=\"readonly\" value=\"".$GA_password."\" type=\"text\" size=\"25\" />";
300 echo "<input name=\"GA_createpassword\" id=\"GA_createpassword\" value=\"".__("Create new password",'google-authenticator')."\" type=\"button\" class=\"button\" />";
301 echo "<span class=\"description\" id=\"GA_passworddesc\">".__(' Password is not stored in cleartext, this is your only chance to see it.','google-authenticator')."</span>\n";
302 echo "</td>\n";
303 echo "</tr>\n";
304 }
305
306
307 echo "</tbody></table>\n";
308 echo "<script type=\"text/javascript\">\n";
309 echo "var GAnonce='".wp_create_nonce('GoogleAuthenticatoraction')."';\n";
310 echo <<<ENDOFJS
311 var pwdata;
312 jQuery('#GA_newsecret').bind('click', function() {
313 var data=new Object();
314 data['action'] = 'GoogleAuthenticator_action';
315 data['nonce'] = GAnonce;
316 jQuery.post(ajaxurl, data, function(response) {
317 jQuery('#GA_secret').val(response['new-secret']);
318 chl=escape("otpauth://totp/"+jQuery('#GA_description').val()+"?secret="+jQuery('#GA_secret').val());
319 qrcodeurl="https://chart.googleapis.com/chart?cht=qr&chs=300x300&chld=H|0&chl="+chl;
320 jQuery('#GA_QRCODE').attr('src',qrcodeurl);
321 jQuery('#GA_QR_INFO').show('slow');
322 });
323 });
324
325 jQuery('#GA_description').bind('focus blur change keyup', function() {
326 chl=escape("otpauth://totp/"+jQuery('#GA_description').val()+"?secret="+jQuery('#GA_secret').val());
327 qrcodeurl="https://chart.googleapis.com/chart?cht=qr&chs=300x300&chld=H|0&chl="+chl;
328 jQuery('#GA_QRCODE').attr('src',qrcodeurl);
329 });
330
331 jQuery('#GA_createpassword').bind('click',function() {
332 var data=new Object();
333 data['action'] = 'GoogleAuthenticator_action';
334 data['nonce'] = GAnonce;
335 data['save'] = 1;
336 jQuery.post(ajaxurl, data, function(response) {
337 jQuery('#GA_password').val(response['new-secret'].match(new RegExp(".{0,4}","g")).join(' '));
338 jQuery('#GA_passworddesc').show();
339 });
340 });
341
342 jQuery('#GA_enabled').bind('change',function() {
343 GoogleAuthenticator_apppasswordcontrol();
344 });
345
346 jQuery(document).ready(function() {
347 jQuery('#GA_passworddesc').hide();
348 GoogleAuthenticator_apppasswordcontrol();
349 });
350
351 function GoogleAuthenticator_apppasswordcontrol() {
352 if (jQuery('#GA_enabled').is(':checked')) {
353 jQuery('#GA_pwdenabled').removeAttr('disabled');
354 jQuery('#GA_createpassword').removeAttr('disabled');
355 } else {
356 jQuery('#GA_pwdenabled').removeAttr('checked')
357 jQuery('#GA_pwdenabled').attr('disabled', true);
358 jQuery('#GA_createpassword').attr('disabled', true);
359 }
360 }
361 </script>
362 ENDOFJS;
363
364 }
365
366 /**
367 * Form handling of Google Authenticator options added to personal profile page (user editing his own profile)
368 */
369 function personal_options_update() {
370 global $user_id;
371
372 // If editing of Google Authenticator settings has been disabled, just return
373 $GA_hidefromuser = trim( get_user_option( 'googleauthenticator_hidefromuser', $user_id ) );
374 if ( $GA_hidefromuser == 'enabled') return;
375
376
377 $GA_enabled = ! empty( $_POST['GA_enabled'] );
378 $GA_description = trim( $_POST['GA_description'] );
379 $GA_relaxedmode = ! empty( $_POST['GA_relaxedmode'] );
380 $GA_secret = trim( $_POST['GA_secret'] );
381 $GA_pwdenabled = ! empty( $_POST['GA_pwdenabled'] );
382 $GA_password = str_replace(' ', '', trim( $_POST['GA_password'] ) );
383
384 if ( ! $GA_enabled ) {
385 $GA_enabled = 'disabled';
386 } else {
387 $GA_enabled = 'enabled';
388 }
389
390 if ( ! $GA_relaxedmode ) {
391 $GA_relaxedmode = 'disabled';
392 } else {
393 $GA_relaxedmode = 'enabled';
394 }
395
396
397 if ( ! $GA_pwdenabled ) {
398 $GA_pwdenabled = 'disabled';
399 } else {
400 $GA_pwdenabled = 'enabled';
401 }
402
403 // Only store password if a new one has been generated.
404 if (strtoupper($GA_password) != 'XXXXXXXXXXXXXXXX' ) {
405 // Store the password in a format that can be expanded easily later on if needed.
406 $GA_password = array( 'appname' => 'Default', 'password' => sha1( $GA_password ) );
407 update_user_option( $user_id, 'googleauthenticator_passwords', json_encode( $GA_password ), true );
408 }
409
410 update_user_option( $user_id, 'googleauthenticator_enabled', $GA_enabled, true );
411 update_user_option( $user_id, 'googleauthenticator_description', $GA_description, true );
412 update_user_option( $user_id, 'googleauthenticator_relaxedmode', $GA_relaxedmode, true );
413 update_user_option( $user_id, 'googleauthenticator_secret', $GA_secret, true );
414 update_user_option( $user_id, 'googleauthenticator_pwdenabled', $GA_pwdenabled, true );
415
416 }
417
418 /**
419 * Extend profile page with ability to enable/disable Google Authenticator authentication requirement.
420 * Used by an administrator when editing other users.
421 */
422 function edit_user_profile() {
423 global $user_id;
424 $GA_enabled = trim( get_user_option( 'googleauthenticator_enabled', $user_id ) );
425 $GA_hidefromuser = trim( get_user_option( 'googleauthenticator_hidefromuser', $user_id ) );
426 echo "<h3>".__('Google Authenticator Settings','google-authenticator')."</h3>\n";
427 echo "<table class=\"form-table\">\n";
428 echo "<tbody>\n";
429
430 echo "<tr>\n";
431 echo "<th scope=\"row\">".__('Hide settings from user','google-authenticator')."</th>\n";
432 echo "<td>\n";
433 echo "<div><input name=\"GA_hidefromuser\" id=\"GA_hidefromuser\" class=\"tog\" type=\"checkbox\"" . checked( $GA_hidefromuser, 'enabled', false ) . "/>\n";
434 echo "</td>\n";
435 echo "</tr>\n";
436
437 echo "<tr>\n";
438 echo "<th scope=\"row\">".__('Active','google-authenticator')."</th>\n";
439 echo "<td>\n";
440 echo "<div><input name=\"GA_enabled\" id=\"GA_enabled\" class=\"tog\" type=\"checkbox\"" . checked( $GA_enabled, 'enabled', false ) . "/>\n";
441 echo "</td>\n";
442 echo "</tr>\n";
443
444 echo "</tbody>\n";
445 echo "</table>\n";
446 }
447
448 /**
449 * Form handling of Google Authenticator options on edit profile page (admin user editing other user)
450 */
451 function edit_user_profile_update() {
452 global $user_id;
453
454 $GA_enabled = ! empty( $_POST['GA_enabled'] );
455 $GA_hidefromuser = ! empty( $_POST['GA_hidefromuser'] );
456
457 if ( ! $GA_enabled ) {
458 $GA_enabled = 'disabled';
459 } else {
460 $GA_enabled = 'enabled';
461 }
462
463 if ( ! $GA_hidefromuser ) {
464 $GA_hidefromuser = 'disabled';
465 } else {
466 $GA_hidefromuser = 'enabled';
467 }
468
469 update_user_option( $user_id, 'googleauthenticator_enabled', $GA_enabled, true );
470 update_user_option( $user_id, 'googleauthenticator_hidefromuser', $GA_hidefromuser, true );
471
472 }
473
474
475 /**
476 * AJAX callback function used to generate new secret
477 */
478 function ajax_callback() {
479 global $user_id;
480
481 // Some AJAX security
482 check_ajax_referer( 'GoogleAuthenticatoraction', 'nonce' );
483
484 // Create new secret, using the users password hash as input for further hashing
485 $secret = $this->create_secret();
486
487 $result = array( 'new-secret' => $secret );
488 header( 'Content-Type: application/json' );
489 echo json_encode( $result );
490
491 // die() is required to return a proper result
492 die();
493 }
494
495 } // end class
496
497 $google_authenticator = new GoogleAuthenticator;
498 ?>
499