Changeset 2701770
- Timestamp:
- 03/30/2022 09:46:30 AM (4 years ago)
- Location:
- vonage-2fa
- Files:
-
- 18 added
- 2 edited
-
assets/banner-772x250.png (added)
-
assets/screenshot-1.png (added)
-
assets/screenshot-2.png (added)
-
assets/screenshot-3.png (added)
-
assets/screenshot-4.png (added)
-
tags/1.0.0 (added)
-
tags/1.0.0/LICENSE (added)
-
tags/1.0.0/README.md (added)
-
tags/1.0.0/assets (added)
-
tags/1.0.0/assets/admin-menu.png (added)
-
tags/1.0.0/assets/logo-large.png (added)
-
tags/1.0.0/assets/logo.png (added)
-
tags/1.0.0/assets/logo.svg (added)
-
tags/1.0.0/assets/vonage-api-screenshot.png (added)
-
tags/1.0.0/assets/vonage-user-settings.png (added)
-
tags/1.0.0/assets/vonagexwordpress.png (added)
-
tags/1.0.0/readme.txt (added)
-
tags/1.0.0/vonage2fa.php (added)
-
trunk/readme.txt (modified) (5 diffs)
-
trunk/vonage2fa.php (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
-
vonage-2fa/trunk/readme.txt
r2701042 r2701770 3 3 Requires at least: 5.7 4 4 Tested up to: 5.9.2 5 Stable tag: 0.0.15 Stable tag: 1.0.0 6 6 Requires PHP: 8.0 7 7 License: Apache 2.0 … … 10 10 This plugin enables your WordPress site to use Two-Factor Authentication using Vonage's Verify API service. 11 11 12 == Screenshots == 13 14 1. The Vonage WordPress 2FA Plugin Admin Menu 15 2. This is where you can get your API keys from the Vonage Dashboard 16 3. Vonage 2FA Settings in the User profile screen 17 3. New PIN entry login form for 2FA 18 12 19 == Description == 13 20 14 21 # Vonage Two-Factor Authentication Plugin for WordPress 15 16 17 22 18 23 This plugin enables your WordPress site to use Two-Factor Authentication … … 27 32 1. Install this plugin in your WordPress plugins directory. 28 33 2. Active the plugin under your administrator account. 29 3. Your 2FA admin menu should now be accessible from the WordPress menu: 30 31  34 3. Your 2FA admin menu should now be accessible from the WordPress menu 32 35 33 36 4. Populate your API Key and Secret in order to connect to Vonage's [Verify API]() that 34 this plugin uses. You can get these credentials from your [Vonage Dashboard](https://dashboard.nexmo.com/), located here: 35 36  37 this plugin uses. You can get these credentials from your [Vonage Dashboard](https://dashboard.nexmo.com/). 37 38 38 39 5. Each user of your WordPress site can now enable 2 Factor Authentication from their 39 user settings. A new section is available in the Profile screen, which will look like this: 40 41  40 user settings. A new section is available in the Profile screen. 42 41 43 42 > Please note that this phone number must contain the *full* international dialling code … … 45 44 46 45 6. For users with 2FA enabled, the first login attempt will ask for the PIN sent out 47 to the phone number entered: 48 49  46 to the phone number entered. 50 47 51 48 7. Once the user has entered a valid PIN, login will finish. … … 61 58 or by email you can reach me at jim.seconde at vonage.com. Alternatively, you 62 59 can email the Developer Relations team at devrel at vonage.com. 60 -
vonage-2fa/trunk/vonage2fa.php
r2701042 r2701770 6 6 Description: Use Vonage's APIs for 2FA 7 7 Author: James Seconde 8 Version: 0.0.18 Version: 1.0.0 9 9 Author URI: https://developer.vonage.com/ 10 10 */ 11 11 12 const PLUGIN_VERSION ='1.0.0'; 12 13 const RESPONSE_PIN_OK = "0"; 13 14 const RESPONSE_PIN_INVALID = "16"; … … 16 17 const RESPONSE_REQUEST_INSUFFICIENT_FUNDS = '9'; 17 18 19 global $wp_version; 20 define( "VONAGE_USER_AGENT_STRING", 'vonage-wordpress/' . $wp_version . '/' . PLUGIN_VERSION ); 21 18 22 function vonage_2fa_setup_menu() { 19 add_menu_page(20 'TestPlugin Page',21 'Vonage 2FA',22 'manage_options',23 'vonage_2fa_plugin',24 'vonage_2fa_load_admin_settings',25 'data:image/svg+xml;base64,' . base64_encode('<svg version="1.0" xmlns="http://www.w3.org/2000/svg"23 add_menu_page( 24 'Vonage 2FA Plugin Page', 25 'Vonage 2FA', 26 'manage_options', 27 'vonage_2fa_plugin', 28 'vonage_2fa_load_admin_settings', 29 'data:image/svg+xml;base64,' . base64_encode('<svg version="1.0" xmlns="http://www.w3.org/2000/svg" 26 30 width="300.000000pt" height="261.000000pt" viewBox="0 0 300.000000 261.000000" 27 31 preserveAspectRatio="xMidYMid meet"> … … 37 41 </svg> 38 42 ') 39 );43 ); 40 44 } 41 45 42 46 function vonage_2fa_register_settings() { 43 register_setting( 'vonage_api_settings_options', 'vonage_api_settings_options', 'vonage_api_settings_options_validate' );44 add_settings_section( 'api_credentials', 'Vonage API Credentials', 'vonage_2fa_plugin_text_helper', 'vonage_2fa_plugin' );45 46 add_settings_field( 'api_credentials_key', 'API Key', 'vonage_2fa_api_credentials_key', 'vonage_2fa_plugin', 'api_credentials' );47 add_settings_field( 'api_credentials_secret', 'API Secret', 'vonage_2fa_api_credentials_secret', 'vonage_2fa_plugin', 'api_credentials' );47 register_setting( 'vonage_api_settings_options', 'vonage_api_settings_options', 'vonage_api_settings_options_validate' ); 48 add_settings_section( 'api_credentials', 'Vonage API Credentials', 'vonage_2fa_plugin_text_helper', 'vonage_2fa_plugin' ); 49 50 add_settings_field( 'api_credentials_key', 'API Key', 'vonage_2fa_api_credentials_key', 'vonage_2fa_plugin', 'api_credentials' ); 51 add_settings_field( 'api_credentials_secret', 'API Secret', 'vonage_2fa_api_credentials_secret', 'vonage_2fa_plugin', 'api_credentials' ); 48 52 } 49 53 50 54 function vonage_2fa_plugin_text_helper() { 51 echo '<p>You will need your master API Key/Secret credentials from your Vonage Dashboard.</p>';55 echo '<p>You will need your master API Key/Secret credentials from your Vonage Dashboard.</p>'; 52 56 } 53 57 54 58 function vonage_2fa_api_credentials_key() { 55 $options = get_option( 'vonage_api_settings_options' );56 echo "<input id='api_credentials_key' name='vonage_api_settings_options[api_credentials_key]' type='text' value='" . esc_attr( $options['api_credentials_key'] ) . "' />";59 $options = get_option( 'vonage_api_settings_options' ); 60 echo "<input id='api_credentials_key' name='vonage_api_settings_options[api_credentials_key]' type='text' value='" . esc_attr( $options['api_credentials_key'] ) . "' />"; 57 61 } 58 62 59 63 function vonage_2fa_api_credentials_secret() { 60 $options = get_option( 'vonage_api_settings_options' );61 echo "<input id='api_credentials_secret' name='vonage_api_settings_options[api_credentials_secret]' type='text' value='" . esc_attr( $options['api_credentials_secret'] ) . "' />";64 $options = get_option( 'vonage_api_settings_options' ); 65 echo "<input id='api_credentials_secret' name='vonage_api_settings_options[api_credentials_secret]' type='text' value='" . esc_attr( $options['api_credentials_secret'] ) . "' />"; 62 66 } 63 67 64 68 function vonage_2fa_load_admin_settings() { 65 echo "69 echo " 66 70 <img src='" . plugin_dir_url(__FILE__ ) . "assets/logo-large.png' alt='Vonage logo'> 67 71 <h1>Built in 2FA</h1> … … 69 73 <div> 70 74 <form action='options.php' method='post'>"; 71 settings_fields( 'vonage_api_settings_options');72 do_settings_sections('vonage_2fa_plugin');73 submit_button();74 echo "75 settings_fields( 'vonage_api_settings_options'); 76 do_settings_sections('vonage_2fa_plugin'); 77 submit_button(); 78 echo " 75 79 </form> 76 80 </div> … … 79 83 80 84 function vonage_2fa_user_settings( $user ) { 81 $mobileValue = get_the_author_meta( 'vonage_2fa_user_mobile_number_data', $user->ID );82 $enabled = get_the_author_meta( 'vonage_2fa_user_enabled_data', $user->ID );83 $checkedString = $enabled === '1' ? 'checked' : '';84 85 echo "85 $mobileValue = get_the_author_meta( 'vonage_2fa_user_mobile_number_data', $user->ID ); 86 $enabled = get_the_author_meta( 'vonage_2fa_user_enabled_data', $user->ID ); 87 $checkedString = $enabled === '1' ? 'checked' : ''; 88 89 echo " 86 90 <br /> 87 91 <h3>Vonage Two-Factor Authentication Settings</h3> … … 108 112 function vonage_2fa_mobile_number_taken( $user_id, $mobile ) { 109 113 110 if ( !$mobile ) {111 return false;112 }113 114 $users = get_users(115 [116 'meta_key' => 'vonage_2fa_user_mobile_number',117 'meta_value' => $mobile,118 'number' => 1119 ]120 );121 122 return 0 < count( $users ) && $user_id !== $users[0]->ID;114 if ( !$mobile ) { 115 return false; 116 } 117 118 $users = get_users( 119 [ 120 'meta_key' => 'vonage_2fa_user_mobile_number', 121 'meta_value' => $mobile, 122 'number' => 1 123 ] 124 ); 125 126 return 0 < count( $users ) && $user_id !== $users[0]->ID; 123 127 } 124 128 125 129 function vonage_2fa_valid_mobile( $mobile ) { 126 $validMatch = preg_match('/^\+(?:[0-9]?){6,14}[0-9]$/', $mobile);127 $validTrim = trim($mobile) !== "";128 129 return $validMatch && $validTrim;130 $validMatch = preg_match('/^\+(?:[0-9]?){6,14}[0-9]$/', $mobile); 131 $validTrim = trim($mobile) !== ""; 132 133 return $validMatch && $validTrim; 130 134 } 131 135 132 136 function vonage_2fa_form_settings_validation( &$errors, $update, &$user ) { 133 $mobile = filter_var( $_POST['vonage_2fa_user_mobile_number'], FILTER_SANITIZE_NUMBER_INT );134 $enabled = filter_var( isset( $_POST['vonage_2fa_user_enabled']), FILTER_SANITIZE_NUMBER_INT );135 136 if ($user && $enabled && !vonage_2fa_valid_mobile( $mobile )) {137 $errors->add( 'vonage_2fa_settings_update_error', 'Phone number provided is invalid, please make sure it includes international dialling code with plus sign.');138 update_user_meta($user->ID, 'vonage_2fa_user_mobile_number_data', "");139 }140 141 if ($user && $mobile && vonage_2fa_mobile_number_taken( $user->ID, $mobile )) {142 $errors->add( 'vonage_2fa_settings_update_error', 'Mobile number already in use.');143 update_user_meta($user->ID, 'vonage_2fa_user_mobile_number_data', "");144 }137 $mobile = filter_var( $_POST['vonage_2fa_user_mobile_number'], FILTER_SANITIZE_NUMBER_INT ); 138 $enabled = filter_var( isset( $_POST['vonage_2fa_user_enabled']), FILTER_SANITIZE_NUMBER_INT ); 139 140 if ($user && $enabled && !vonage_2fa_valid_mobile( $mobile )) { 141 $errors->add( 'vonage_2fa_settings_update_error', 'Phone number provided is invalid, please make sure it includes international dialling code with plus sign.'); 142 update_user_meta($user->ID, 'vonage_2fa_user_mobile_number_data', ""); 143 } 144 145 if ($user && $mobile && vonage_2fa_mobile_number_taken( $user->ID, $mobile )) { 146 $errors->add( 'vonage_2fa_settings_update_error', 'Mobile number already in use.'); 147 update_user_meta($user->ID, 'vonage_2fa_user_mobile_number_data', ""); 148 } 145 149 } 146 150 147 151 function vonage_2fa_save_settings( $user_id ) { 148 152 149 $mobile = filter_var( $_POST['vonage_2fa_user_mobile_number'], FILTER_SANITIZE_NUMBER_INT );150 $enabled = filter_var( isset( $_POST['vonage_2fa_user_enabled']), FILTER_SANITIZE_NUMBER_INT );151 152 if (!current_user_can( 'edit_user', $user_id ) ) {153 return false;154 }155 156 update_user_meta( $user_id, 'vonage_2fa_user_mobile_number_data', $mobile );157 update_user_meta( $user_id, 'vonage_2fa_user_enabled_data', $enabled );153 $mobile = filter_var( $_POST['vonage_2fa_user_mobile_number'], FILTER_SANITIZE_NUMBER_INT ); 154 $enabled = filter_var( isset( $_POST['vonage_2fa_user_enabled']), FILTER_SANITIZE_NUMBER_INT ); 155 156 if (!current_user_can( 'edit_user', $user_id ) ) { 157 return false; 158 } 159 160 update_user_meta( $user_id, 'vonage_2fa_user_mobile_number_data', $mobile ); 161 update_user_meta( $user_id, 'vonage_2fa_user_enabled_data', $enabled ); 158 162 } 159 163 160 164 function vonage_2fa_auth_intercept( $user, $username, $password ) { 161 if ( !session_id() ) { 162 session_start(); 163 } 164 165 $wpUser = get_user_by( 'login', $username ); 166 $enabled_2fa = get_user_meta( $wpUser->ID, 'vonage_2fa_user_enabled_data', true ); 167 168 if (!$enabled_2fa) { 169 return; 170 } 171 172 $options = get_option( 'vonage_api_settings_options' ); 173 $apiKey = $options['api_credentials_key']; 174 $apiSecret = $options['api_credentials_secret']; 175 176 $errors = []; 177 $redirect_to = sanitize_url( $_POST['redirect_to'] ) ?? admin_url(); 178 $remember_me = isset( $_POST['rememberme'] ) && $_POST['rememberme'] === 'forever'; 179 180 $savedRequestId = sanitize_text_field($_SESSION['vonage_2fa_request_id']); 181 $pin = isset( $_POST['vonage_2fa_pin'] ) ? sanitize_text_field( $_POST['vonage_2fa_pin'] ) : false; 182 $requestId = isset( $_POST['vonage_2fa_request_id'] ) ? sanitize_text_field( $_POST['vonage_2fa_request_id'] ) : false; 183 184 // You have submitted a PIN 185 if ( $requestId && $pin && $savedRequestId === $requestId ) { 186 187 $url = "https://api.nexmo.com/verify/check/json?&api_key=$apiKey&api_secret=$apiSecret&request_id=$requestId&code=$pin"; 188 $response = wp_remote_get( $url ); 189 $responseBody = json_decode( $response['body'], true ); 190 191 if ( $responseBody['status'] === RESPONSE_PIN_OK ) { 192 wp_set_auth_cookie($wpUser->ID, $remember_me); 193 wp_safe_redirect($redirect_to); 194 exit; 195 } 196 197 if ( $responseBody['status'] === RESPONSE_PIN_INVALID ) { 198 $errors[] = "Invalid PIN code"; 199 } 200 } 201 202 // Or you have a request ID saved that needs to be checked 203 if ( $savedRequestId ) { 204 $url = "https://api.nexmo.com/verify/search/json?&api_key=$apiKey&api_secret=$apiSecret&request_id=$savedRequestId"; 205 $response = wp_remote_get( $url ); 206 $responseBody = json_decode( $response['body'], true ); 207 208 if ( $responseBody['status'] === RESPONSE_VERIFICATION_PASSED ) { 209 wp_set_auth_cookie( $wpUser->ID, $remember_me ); 210 wp_safe_redirect( $redirect_to ); 211 exit; 212 } 213 214 // The saved request ID has expired or failed 215 $errors[] = 'Your verification has expired or there was an error logging in.'; 216 $_SESSION['vonage_2fa_request_id'] = ''; 217 } 218 219 // You are trying to log in for the first time or have requested a PIN or have an invalid exiting verify 220 if ($wpUser) { 221 vonage_2fa_verify_user($wpUser, $redirect_to, $remember_me, $errors); 222 } 223 224 return $user; 165 if ( !session_id() ) { 166 session_start(); 167 } 168 169 $wpUser = get_user_by( 'login', $username ); 170 $enabled_2fa = get_user_meta( $wpUser->ID, 'vonage_2fa_user_enabled_data', true ); 171 172 if (!$enabled_2fa) { 173 return; 174 } 175 176 $options = get_option( 'vonage_api_settings_options' ); 177 $apiKey = $options['api_credentials_key']; 178 $apiSecret = $options['api_credentials_secret']; 179 180 $errors = []; 181 $redirect_to = sanitize_url( $_POST['redirect_to'] ) ?? admin_url(); 182 $remember_me = isset( $_POST['rememberme'] ) && $_POST['rememberme'] === 'forever'; 183 184 $savedRequestId = sanitize_text_field($_SESSION['vonage_2fa_request_id']); 185 $pin = isset( $_POST['vonage_2fa_pin'] ) ? sanitize_text_field( $_POST['vonage_2fa_pin'] ) : false; 186 $requestId = isset( $_POST['vonage_2fa_request_id'] ) ? sanitize_text_field( $_POST['vonage_2fa_request_id'] ) : false; 187 188 // You have submitted a PIN 189 if ( $requestId && $pin && $savedRequestId === $requestId ) { 190 191 $url = "https://api.nexmo.com/verify/check/json?&api_key=$apiKey&api_secret=$apiSecret&request_id=$requestId&code=$pin"; 192 $response = wp_remote_get( $url, [ 193 'user-agent' => VONAGE_USER_AGENT_STRING 194 ] ); 195 $responseBody = json_decode( $response['body'], true ); 196 197 if ( $responseBody['status'] === RESPONSE_PIN_OK ) { 198 wp_set_auth_cookie($wpUser->ID, $remember_me); 199 wp_safe_redirect($redirect_to); 200 exit; 201 } 202 203 if ( $responseBody['status'] === RESPONSE_PIN_INVALID ) { 204 $errors[] = "Invalid PIN code"; 205 } 206 } 207 208 // Or you have a request ID saved that needs to be checked 209 if ( $savedRequestId ) { 210 $url = "https://api.nexmo.com/verify/search/json?&api_key=$apiKey&api_secret=$apiSecret&request_id=$savedRequestId"; 211 $response = wp_remote_get( $url, [ 212 'user-agent' => VONAGE_USER_AGENT_STRING 213 ] ); 214 $responseBody = json_decode( $response['body'], true ); 215 216 if ( $responseBody['status'] === RESPONSE_VERIFICATION_PASSED ) { 217 wp_set_auth_cookie( $wpUser->ID, $remember_me ); 218 wp_safe_redirect( $redirect_to ); 219 exit; 220 } 221 222 // The saved request ID has expired or failed 223 $errors[] = 'Your verification has expired or there was an error logging in.'; 224 $_SESSION['vonage_2fa_request_id'] = ''; 225 } 226 227 // You are trying to log in for the first time or have requested a PIN or have an invalid exiting verify 228 if ($wpUser) { 229 vonage_2fa_verify_user($wpUser, $redirect_to, $remember_me, $errors); 230 } 231 232 return $user; 225 233 } 226 234 227 235 function vonage_2fa_verify_user( $user, $redirect_to, $remember_me, $errors = [] ) { 228 $options = get_option( 'vonage_api_settings_options' ); 229 $apiKey = $options['api_credentials_key']; 230 $apiSecret = $options['api_credentials_secret']; 231 $phoneNumber = get_user_meta( $user->ID, 'vonage_2fa_user_mobile_number_data', true ); 232 233 // You are requesting a PIN with a phone number 234 $url = "https://api.nexmo.com/verify/json?&api_key=$apiKey&api_secret=$apiSecret&number=$phoneNumber&workflow_id=6&brand=Wordpress2FA"; 235 $response = wp_remote_post( $url ); 236 $responseBody = json_decode( $response['body'], true ); 237 238 // Attempt to send number has been rejected 239 if ( $responseBody['status'] === RESPONSE_REQUEST_FAILED ) { 240 $errors[] = $responseBody['error_text']; 241 } 242 243 if ( $responseBody['status'] === RESPONSE_REQUEST_INSUFFICIENT_FUNDS ) { 244 $errors[] = 'Your Vonage account does not have enough balance to perform this authorisation request. Please contact your website administrator'; 245 } 246 247 $requestId = $responseBody['request_id']; 248 $_SESSION['vonage_2fa_request_id'] = $requestId; 249 250 wp_logout(); 251 nocache_headers(); 252 header('Content-Type: ' . get_bloginfo( 'html_type' ) . '; charset=' . get_bloginfo( 'charset' ) ); 253 login_header('Vonage Two-Factor Authentication', '<p class="message">' . sprintf( 'Enter the PIN code sent to your 2FA phone number ending in <strong>%1$s</strong>' , substr($phoneNumber, -5) ) . '</p>'); 254 255 if ( !empty($errors) ) { ?> 236 $options = get_option( 'vonage_api_settings_options' ); 237 $apiKey = $options['api_credentials_key']; 238 $apiSecret = $options['api_credentials_secret']; 239 $phoneNumber = get_user_meta( $user->ID, 'vonage_2fa_user_mobile_number_data', true ); 240 241 // You are requesting a PIN with a phone number 242 $url = "https://api.nexmo.com/verify/json?&api_key=$apiKey&api_secret=$apiSecret&number=$phoneNumber&workflow_id=6&brand=Wordpress2FA"; 243 $response = wp_remote_post( $url, [ 244 'user-agent' => VONAGE_USER_AGENT_STRING 245 ] ); 246 $responseBody = json_decode( $response['body'], true ); 247 248 // Attempt to send number has been rejected 249 if ( $responseBody['status'] === RESPONSE_REQUEST_FAILED ) { 250 $errors[] = $responseBody['error_text']; 251 } 252 253 if ( $responseBody['status'] === RESPONSE_REQUEST_INSUFFICIENT_FUNDS ) { 254 $errors[] = 'Your Vonage account does not have enough balance to perform this authorisation request. Please contact your website administrator'; 255 } 256 257 $requestId = $responseBody['request_id']; 258 $_SESSION['vonage_2fa_request_id'] = $requestId; 259 260 wp_logout(); 261 nocache_headers(); 262 header('Content-Type: ' . get_bloginfo( 'html_type' ) . '; charset=' . get_bloginfo( 'charset' ) ); 263 login_header('Vonage Two-Factor Authentication', '<p class="message">' . sprintf( 'Enter the PIN code sent to your 2FA phone number ending in <strong>%1$s</strong>' , substr($phoneNumber, -5) ) . '</p>'); 264 265 if ( !empty($errors) ) { ?> 256 266 <div id="login_error"><?php echo esc_html( implode( '<br />', $errors ) ) ?></div> 257 <?php } ?>267 <?php } ?> 258 268 259 269 <form name="loginform" id="loginform" action="<?php echo esc_url( site_url( 'wp-login.php', 'login_post' ) ) ?>" method="post" autocomplete="off"> … … 270 280 <input type="hidden" name="redirect_to" value="<?php echo esc_attr( $redirect_to ) ?>" /> 271 281 272 <?php if ( $remember_me ) : ?>282 <?php if ( $remember_me ) : ?> 273 283 <input type="hidden" name="rememberme" value="forever" /> 274 <?php endif; ?>284 <?php endif; ?> 275 285 </p> 276 286 </form> 277 287 278 <?php279 280 login_footer( 'vonage_2fa_pin' );281 282 exit;288 <?php 289 290 login_footer( 'vonage_2fa_pin' ); 291 292 exit; 283 293 } 284 294
Note: See TracChangeset
for help on using the changeset viewer.