PluginProbe
Google Authenticator / 0.39
Google Authenticator v0.39
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.39, at google-authenticator.php

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