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

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