| 1 |
<?php |
| 2 |
if (!defined('WEBTOTEM_INIT') || WEBTOTEM_INIT !== true) { |
| 3 |
if (!headers_sent()) { |
| 4 |
header('HTTP/1.1 403 Forbidden'); |
| 5 |
} |
| 6 |
die("Protected By WebTotem!"); |
| 7 |
} |
| 8 |
|
| 9 |
require_once 'Captcha.php'; |
| 10 |
require_once 'BFProtection.php'; |
| 11 |
require_once 'GoogleAuthenticator.php'; |
| 12 |
|
| 13 |
/** |
| 14 |
* WebTotem Login class for Wordpress. |
| 15 |
*/ |
| 16 |
class WebTotemLogin { |
| 17 |
|
| 18 |
const RECOVERY_CODE_SIZE = 8; |
| 19 |
const RECOVERY_CODE_COUNT = 5; |
| 20 |
|
| 21 |
/** |
| 22 |
* Generates a new set of recovery codes and saves them to $user if provided. |
| 23 |
* |
| 24 |
* @param int $count |
| 25 |
* @return array |
| 26 |
*/ |
| 27 |
public static function generate_recovery_codes($count = self::RECOVERY_CODE_COUNT) { |
| 28 |
$codes = array(); |
| 29 |
for ($i = 0; $i < $count; $i++) { |
| 30 |
$codes[] = self::random_bytes(self::RECOVERY_CODE_SIZE); |
| 31 |
} |
| 32 |
|
| 33 |
return $codes; |
| 34 |
} |
| 35 |
|
| 36 |
/** |
| 37 |
* Save user's 2fa data. |
| 38 |
* |
| 39 |
* @param int $user_id |
| 40 |
* User ID. |
| 41 |
* |
| 42 |
* @return bool |
| 43 |
* Returns TRUE after save settings. |
| 44 |
*/ |
| 45 |
public static function saveData( int $user_id, $codes, $secret ) { |
| 46 |
|
| 47 |
$data = json_decode(WebTotemOption::getOption('two_factor_data'), true) ?: []; |
| 48 |
$data[$user_id]['recovery'] = $codes; |
| 49 |
$data[$user_id]['secret'] = $secret; |
| 50 |
WebTotemOption::setOptions(['two_factor_data' => json_encode($data)]); |
| 51 |
|
| 52 |
return TRUE; |
| 53 |
} |
| 54 |
|
| 55 |
/** |
| 56 |
* Delete user's 2fa data. |
| 57 |
* |
| 58 |
* @param int $user_id |
| 59 |
* User ID. |
| 60 |
* |
| 61 |
* @return bool |
| 62 |
* Returns TRUE after save settings. |
| 63 |
*/ |
| 64 |
public static function delete( int $user_id ) { |
| 65 |
$data = json_decode(WebTotemOption::getOption('two_factor_data'), true) ?: []; |
| 66 |
|
| 67 |
if(isset($data[$user_id])) { |
| 68 |
unset($data[$user_id]); |
| 69 |
} |
| 70 |
WebTotemOption::setOptions(['two_factor_data' => json_encode($data)]); |
| 71 |
|
| 72 |
return TRUE; |
| 73 |
} |
| 74 |
|
| 75 |
/** |
| 76 |
* Get user's 2fa data. |
| 77 |
* |
| 78 |
* @param int $user_id |
| 79 |
* User ID. |
| 80 |
* |
| 81 |
* @return mixed |
| 82 |
* Returns saved data by option name. |
| 83 |
*/ |
| 84 |
public static function getData($user_id) { |
| 85 |
|
| 86 |
$data = json_decode(WebTotemOption::getOption('two_factor_data'), true) ?: []; |
| 87 |
|
| 88 |
if(array_key_exists($user_id, $data)){ |
| 89 |
return $data[$user_id]; |
| 90 |
} |
| 91 |
|
| 92 |
return false; |
| 93 |
} |
| 94 |
|
| 95 |
/** |
| 96 |
* has user 2FA activated. |
| 97 |
* |
| 98 |
* @param WP_User $user |
| 99 |
* User. |
| 100 |
* |
| 101 |
* @return bool |
| 102 |
*/ |
| 103 |
public static function hasUser2faActivated($user){ |
| 104 |
if(self::getData($user->ID)){ |
| 105 |
return true; |
| 106 |
} else { |
| 107 |
return false; |
| 108 |
} |
| 109 |
} |
| 110 |
|
| 111 |
|
| 112 |
/** |
| 113 |
* Check 2FA code by user. |
| 114 |
* |
| 115 |
* @param WP_User $user |
| 116 |
* User. |
| 117 |
* |
| 118 |
* @return bool |
| 119 |
*/ |
| 120 |
public static function check2faCode($user, $code){ |
| 121 |
$data = self::getData($user->ID); |
| 122 |
$g = new WebTotemGoogleAuthenticator(); |
| 123 |
$code = trim($code); |
| 124 |
|
| 125 |
if(strlen($code) === 6 and $g->checkCode($data['secret'], $code)) { |
| 126 |
return true; |
| 127 |
} else if (strlen($code) >= 16) { |
| 128 |
$code = str_replace(' ', '', $code); |
| 129 |
$recovery = explode(',', $data['recovery']); |
| 130 |
$is_verify = false; |
| 131 |
foreach ($recovery as $key => $recoveryCode){ |
| 132 |
if($recoveryCode === $code){ |
| 133 |
$is_verify = true; |
| 134 |
break; |
| 135 |
} |
| 136 |
} |
| 137 |
if($is_verify){ |
| 138 |
// Delete this code from the database. |
| 139 |
unset($recovery[$key]); |
| 140 |
$recovery = implode(',', $recovery); |
| 141 |
self::saveData($user->ID, $recovery, $data['secret']); |
| 142 |
|
| 143 |
return true; |
| 144 |
} |
| 145 |
} |
| 146 |
|
| 147 |
return false; |
| 148 |
} |
| 149 |
|
| 150 |
/** |
| 151 |
* Get recovery data. |
| 152 |
* |
| 153 |
* @param WP_User $user |
| 154 |
* User. |
| 155 |
* |
| 156 |
* @return array |
| 157 |
* Returns saved data by option name. |
| 158 |
*/ |
| 159 |
public static function getRecoveryData( $user ){ |
| 160 |
$recovery = self::generate_recovery_codes(); |
| 161 |
|
| 162 |
$fileContents = sprintf(__('Two-Factor Authentication Recovery Codes. %s (%s)', 'wtotem'), home_url(), $user->user_login) . "\r\n"; |
| 163 |
$fileContents .= "\r\n" . __('Each line is a single recovery code, with optional spaces for readability. Your recovery codes are:', 'wtotem') . "\r\n\r\n"; |
| 164 |
$recoveryBlocks = []; |
| 165 |
foreach ($recovery as $c) { |
| 166 |
$hex = bin2hex( $c ); |
| 167 |
$blocks = str_split( $hex, 4 ); |
| 168 |
$blocks = implode( ' ', $blocks ); |
| 169 |
$fileContents .= $blocks . "\r\n"; |
| 170 |
$recoveryBlocks[] = $blocks; |
| 171 |
} |
| 172 |
|
| 173 |
$fileContents = str_replace("\n", "\\n", str_replace("\r", "\\r", addslashes($fileContents))); |
| 174 |
|
| 175 |
return [ |
| 176 |
'fileName' => WEBTOTEM_SITE_DOMAIN . '_' . $user->user_login . '_recovery_codes.txt', |
| 177 |
'fileContents' => $fileContents, |
| 178 |
'recovery' => implode(',', array_map(function($c) { return bin2hex($c); }, $recovery)), |
| 179 |
'blocks' => $recoveryBlocks, |
| 180 |
]; |
| 181 |
|
| 182 |
} |
| 183 |
|
| 184 |
/** |
| 185 |
* @throws Exception |
| 186 |
*/ |
| 187 |
public static function random_bytes($length) { |
| 188 |
$length = (int) $length; |
| 189 |
if (function_exists('random_bytes')) { |
| 190 |
$rand = random_bytes($length); |
| 191 |
if (is_string($rand)) { |
| 192 |
return $rand; |
| 193 |
} |
| 194 |
} |
| 195 |
|
| 196 |
$return = ''; |
| 197 |
for ($i = 0; $i < $length; $i++) { |
| 198 |
$return .= chr(mt_rand(0, 255)); |
| 199 |
} |
| 200 |
return $return; |
| 201 |
} |
| 202 |
|
| 203 |
/** |
| 204 |
* Check to any two-factor activated. |
| 205 |
* |
| 206 |
* @return bool returns true if at least one activation. |
| 207 |
*/ |
| 208 |
public static function anyTwoFactorActivated($user = null) { |
| 209 |
$data = json_decode(WebTotemOption::getOption('two_factor_data'), true) ?: []; |
| 210 |
if($data){ |
| 211 |
return true; |
| 212 |
} |
| 213 |
return false; |
| 214 |
} |
| 215 |
|
| 216 |
/** |
| 217 |
* Get two factor authenticator data. |
| 218 |
* |
| 219 |
* @return array |
| 220 |
* Returns google authenticator data. |
| 221 |
*/ |
| 222 |
public static function getTwoFactorData($user = null) { |
| 223 |
|
| 224 |
if(!$user) { $user = wp_get_current_user(); }; |
| 225 |
|
| 226 |
if($data = self::getData($user->ID)) { |
| 227 |
|
| 228 |
return [ |
| 229 |
'isActivated' => true, |
| 230 |
'recovery' => explode(',', $data['recovery']), |
| 231 |
]; |
| 232 |
} |
| 233 |
|
| 234 |
$data = self::getRecoveryData($user); |
| 235 |
$g = new WebTotemGoogleAuthenticator(); |
| 236 |
|
| 237 |
$host = WebTotemOption::getMainHost(); |
| 238 |
$data['secret'] = $g->generateSecret(); |
| 239 |
$data['qr_url'] = $g->getURL( $user->user_login, $host['name'], $data['secret'] ); |
| 240 |
|
| 241 |
$data['isActivated'] = false; |
| 242 |
|
| 243 |
return $data; |
| 244 |
|
| 245 |
} |
| 246 |
|
| 247 |
|
| 248 |
/** |
| 249 |
* Checks whether two-factor authorization is activated on the site. |
| 250 |
* |
| 251 |
* @return bool. |
| 252 |
*/ |
| 253 |
public static function isTwoFactorEnabled($user = null) { |
| 254 |
return WebTotemOption::getPluginSettings('two_factor'); |
| 255 |
} |
| 256 |
|
| 257 |
} |