1: | <?php |
2: | |
3: | |
4: | |
5: | |
6: | |
7: | |
8: | namespace Aurora\Modules\TwoFactorAuth; |
9: | |
10: | use Aurora\Modules\Core\Models\User; |
11: | use Aurora\Modules\TwoFactorAuth\Models\UsedDevice; |
12: | use Aurora\Modules\TwoFactorAuth\Models\WebAuthnKey; |
13: | use Aurora\System\Api; |
14: | use PragmaRX\Recovery\Recovery; |
15: | |
16: | require_once('Classes/WebAuthn/WebAuthn.php'); |
17: | |
18: | |
19: | |
20: | |
21: | |
22: | |
23: | |
24: | |
25: | class Module extends \Aurora\System\Module\AbstractModule |
26: | { |
27: | public static $VerifyState = false; |
28: | |
29: | private $oWebAuthn = null; |
30: | |
31: | |
32: | |
33: | |
34: | protected $oUsedDevicesManager = null; |
35: | |
36: | public function init() |
37: | { |
38: | \Aurora\System\Router::getInstance()->registerArray( |
39: | self::GetName(), |
40: | [ |
41: | 'assetlinks' => [$this, 'EntryAssetlinks'], |
42: | 'verify-security-key' => [$this, 'EntryVerifySecurityKey'], |
43: | ] |
44: | ); |
45: | |
46: | $this->subscribeEvent('Core::Authenticate::after', array($this, 'onAfterAuthenticate')); |
47: | $this->subscribeEvent('Core::Logout::before', array($this, 'onBeforeLogout')); |
48: | $this->subscribeEvent('Core::DeleteUser::after', array($this, 'onAfterDeleteUser')); |
49: | |
50: | $this->oWebAuthn = new \WebAuthn\WebAuthn( |
51: | 'WebAuthn Library', |
52: | $this->oHttp->GetHost(), |
53: | [ |
54: | 'android-key', |
55: | 'android-safetynet', |
56: | 'apple', |
57: | 'fido-u2f', |
58: | 'none', |
59: | 'packed', |
60: | 'tpm' |
61: | ], |
62: | false, |
63: | array_merge($this->getConfig('FacetIds', []), [$this->oHttp->GetScheme().'://'.$this->oHttp->GetHost(true, false)]) |
64: | ); |
65: | } |
66: | |
67: | |
68: | |
69: | |
70: | |
71: | public function getUsedDevicesManager() |
72: | { |
73: | if ($this->oUsedDevicesManager === null) { |
74: | $this->oUsedDevicesManager = new Managers\UsedDevices\Manager($this); |
75: | } |
76: | |
77: | return $this->oUsedDevicesManager; |
78: | } |
79: | |
80: | |
81: | |
82: | |
83: | |
84: | |
85: | public function GetSettings() |
86: | { |
87: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous); |
88: | |
89: | $bAllowUsedDevices = $this->getConfig('AllowUsedDevices', false); |
90: | $aSettings = [ |
91: | 'AllowBackupCodes' => $this->getConfig('AllowBackupCodes', false), |
92: | 'AllowSecurityKeys' => $this->getConfig('AllowSecurityKeys', false), |
93: | 'AllowAuthenticatorApp' => $this->getConfig('AllowAuthenticatorApp', true), |
94: | 'AllowUsedDevices' => $bAllowUsedDevices, |
95: | 'TrustDevicesForDays' => $bAllowUsedDevices ? $this->getConfig('TrustDevicesForDays', 0) : 0, |
96: | ]; |
97: | |
98: | $oUser = Api::getAuthenticatedUser(); |
99: | if (!empty($oUser) && $oUser->isNormalOrTenant()) { |
100: | $bShowRecommendationToConfigure = $this->getConfig('ShowRecommendationToConfigure', false); |
101: | if ($bShowRecommendationToConfigure) { |
102: | $bShowRecommendationToConfigure = $oUser->{$this->GetName().'::ShowRecommendationToConfigure'}; |
103: | } |
104: | |
105: | $bAuthenticatorAppEnabled = $this->getConfig('AllowAuthenticatorApp', true) && $oUser->{$this->GetName().'::Secret'} ? true : false; |
106: | $aWebAuthKeysInfo = $this->getConfig('AllowSecurityKeys', false) ? $this->_getWebAuthKeysInfo($oUser) : []; |
107: | $iBackupCodesCount = 0; |
108: | if ($bAuthenticatorAppEnabled || count($aWebAuthKeysInfo) > 0) { |
109: | $sBackupCodes = \Aurora\System\Utils::DecryptValue($oUser->{$this->GetName().'::BackupCodes'}); |
110: | $aBackupCodes = empty($sBackupCodes) ? [] : json_decode($sBackupCodes); |
111: | $aNotUsedBackupCodes = array_filter($aBackupCodes, function ($sCode) { |
112: | return !empty($sCode); |
113: | }); |
114: | $iBackupCodesCount = count($aNotUsedBackupCodes); |
115: | } |
116: | |
117: | $aSettings = array_merge($aSettings, [ |
118: | 'ShowRecommendationToConfigure' => $bShowRecommendationToConfigure, |
119: | 'WebAuthKeysInfo' => $aWebAuthKeysInfo, |
120: | 'AuthenticatorAppEnabled' => $bAuthenticatorAppEnabled, |
121: | 'BackupCodesCount' => $iBackupCodesCount, |
122: | ]); |
123: | } |
124: | |
125: | return $aSettings; |
126: | } |
127: | |
128: | public function UpdateSettings($ShowRecommendationToConfigure) |
129: | { |
130: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
131: | |
132: | if ($this->getConfig('ShowRecommendationToConfigure', false)) { |
133: | $oUser = Api::getAuthenticatedUser(); |
134: | if (!empty($oUser) && $oUser->isNormalOrTenant()) { |
135: | $oUser->setExtendedProp($this->GetName() . '::ShowRecommendationToConfigure', $ShowRecommendationToConfigure); |
136: | return $oUser->save(); |
137: | } |
138: | } |
139: | return false; |
140: | } |
141: | |
142: | |
143: | |
144: | |
145: | |
146: | |
147: | |
148: | public function GetUserSettings($UserId) |
149: | { |
150: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin); |
151: | |
152: | if ($this->getConfig('AllowAuthenticatorApp', true)) { |
153: | $oUser = Api::getUserById($UserId); |
154: | if ($oUser instanceof User && $oUser->isNormalOrTenant()) { |
155: | Api::checkUserAccess($oUser); |
156: | $iWebAuthnKeyCount = WebAuthnKey::where('UserId', $oUser->Id)->count(); |
157: | return [ |
158: | 'TwoFactorAuthEnabled' => !empty($oUser->{$this->GetName().'::Secret'}) || $iWebAuthnKeyCount > 0 |
159: | ]; |
160: | } |
161: | } |
162: | |
163: | return null; |
164: | } |
165: | |
166: | public function onAfterDeleteUser($aArgs, &$mResult) |
167: | { |
168: | if ($mResult) { |
169: | UsedDevice::where('UserId', $aArgs['UserId'])->delete(); |
170: | } |
171: | } |
172: | |
173: | |
174: | |
175: | |
176: | |
177: | |
178: | |
179: | public function DisableUserTwoFactorAuth($UserId) |
180: | { |
181: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin); |
182: | |
183: | if (!$this->getConfig('AllowAuthenticatorApp', true)) { |
184: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
185: | } |
186: | |
187: | $oUser = Api::getUserById($UserId); |
188: | if ($oUser instanceof User && $oUser->isNormalOrTenant()) { |
189: | Api::checkUserAccess($oUser); |
190: | |
191: | $oUser->setExtendedProp($this->GetName().'::Secret', ''); |
192: | $oUser->setExtendedProp($this->GetName().'::IsEncryptedSecret', false); |
193: | |
194: | $oUser->setExtendedProp($this->GetName().'::Challenge', ''); |
195: | $aWebAuthnKeys = WebAuthnKey::where('UserId', $oUser->Id)->get(); |
196: | |
197: | $bResult = true; |
198: | foreach ($aWebAuthnKeys as $oWebAuthnKey) { |
199: | $bResult = $bResult && $oWebAuthnKey->delete(); |
200: | } |
201: | |
202: | $oUser->setExtendedProp($this->GetName().'::BackupCodes', ''); |
203: | $oUser->setExtendedProp($this->GetName().'::BackupCodesTimestamp', ''); |
204: | $bResult = $bResult && \Aurora\Modules\Core\Module::Decorator()->UpdateUserObject($oUser); |
205: | |
206: | $bResult = $bResult && $this->getUsedDevicesManager()->revokeTrustFromAllDevices($oUser); |
207: | |
208: | return $bResult; |
209: | } |
210: | |
211: | |
212: | return false; |
213: | } |
214: | |
215: | |
216: | |
217: | |
218: | |
219: | |
220: | |
221: | public function RegisterAuthenticatorAppBegin($Password) |
222: | { |
223: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
224: | |
225: | if (!$this->getConfig('AllowAuthenticatorApp', true)) { |
226: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
227: | } |
228: | |
229: | $oUser = Api::getAuthenticatedUser(); |
230: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
231: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
232: | } |
233: | |
234: | if (empty($Password)) { |
235: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter); |
236: | } |
237: | |
238: | if (!Api::GetModuleDecorator('Core')->VerifyPassword($Password)) { |
239: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
240: | } |
241: | |
242: | $oGoogle = new \PHPGangsta_GoogleAuthenticator(); |
243: | $sSecret = ''; |
244: | if ($oUser->{$this->GetName().'::Secret'}) { |
245: | $sSecret = $oUser->{$this->GetName().'::Secret'}; |
246: | if ($oUser->{$this->GetName().'::IsEncryptedSecret'}) { |
247: | $sSecret = \Aurora\System\Utils::DecryptValue($sSecret); |
248: | } |
249: | } else { |
250: | $sSecret = $oGoogle->createSecret(); |
251: | ; |
252: | } |
253: | $sQRCodeName = $oUser->PublicId . "(" . $_SERVER['SERVER_NAME'] . ")"; |
254: | |
255: | return [ |
256: | 'Secret' => $sSecret, |
257: | 'QRcode' => $oGoogle->getQRCodeGoogleUrl($sQRCodeName, $sSecret), |
258: | 'Enabled' => $oUser->{$this->GetName().'::Secret'} ? true : false |
259: | ]; |
260: | } |
261: | |
262: | |
263: | |
264: | |
265: | |
266: | |
267: | |
268: | |
269: | |
270: | |
271: | public function RegisterAuthenticatorAppFinish($Password, $Code, $Secret) |
272: | { |
273: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
274: | |
275: | if (!$this->getConfig('AllowAuthenticatorApp', true)) { |
276: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
277: | } |
278: | |
279: | $oUser = Api::getAuthenticatedUser(); |
280: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
281: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
282: | } |
283: | |
284: | if (empty($Password) || empty($Code) || empty($Secret)) { |
285: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter); |
286: | } |
287: | |
288: | if (!Api::GetModuleDecorator('Core')->VerifyPassword($Password)) { |
289: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
290: | } |
291: | |
292: | $bResult = false; |
293: | $iClockTolerance = $this->getConfig('ClockTolerance', 2); |
294: | $oGoogle = new \PHPGangsta_GoogleAuthenticator(); |
295: | |
296: | $oStatus = $oGoogle->verifyCode($Secret, $Code, $iClockTolerance); |
297: | if ($oStatus === true) { |
298: | $oUser->setExtendedProp($this->GetName().'::Secret', \Aurora\System\Utils::EncryptValue($Secret)); |
299: | $oUser->setExtendedProp($this->GetName().'::IsEncryptedSecret', true); |
300: | \Aurora\Modules\Core\Module::Decorator()->UpdateUserObject($oUser); |
301: | $bResult = true; |
302: | } |
303: | |
304: | return $bResult; |
305: | } |
306: | |
307: | |
308: | |
309: | |
310: | |
311: | |
312: | |
313: | public function DisableAuthenticatorApp($Password) |
314: | { |
315: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
316: | |
317: | if (!$this->getConfig('AllowAuthenticatorApp', true)) { |
318: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
319: | } |
320: | |
321: | $oUser = Api::getAuthenticatedUser(); |
322: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
323: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
324: | } |
325: | |
326: | if (empty($Password)) { |
327: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter); |
328: | } |
329: | |
330: | if (!Api::GetModuleDecorator('Core')->VerifyPassword($Password)) { |
331: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
332: | } |
333: | |
334: | $oUser->setExtendedProp($this->GetName().'::Secret', ""); |
335: | $oUser->setExtendedProp($this->GetName().'::IsEncryptedSecret', false); |
336: | $bResult = \Aurora\Modules\Core\Module::Decorator()->UpdateUserObject($oUser); |
337: | $this->_removeAllDataWhenAllSecondFactorsDisabled($oUser); |
338: | |
339: | return $bResult; |
340: | } |
341: | |
342: | |
343: | |
344: | |
345: | |
346: | |
347: | |
348: | |
349: | |
350: | |
351: | public function VerifyAuthenticatorAppCode($Code, $Login, $Password) |
352: | { |
353: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous); |
354: | |
355: | if (!$this->getConfig('AllowAuthenticatorApp', true)) { |
356: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
357: | } |
358: | |
359: | if (empty($Code) || empty($Login) || empty($Password)) { |
360: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter); |
361: | } |
362: | |
363: | self::$VerifyState = true; |
364: | $mAuthenticateResult = \Aurora\Modules\Core\Module::Decorator()->Authenticate($Login, $Password); |
365: | self::$VerifyState = false; |
366: | if (!$mAuthenticateResult || !is_array($mAuthenticateResult) || !isset($mAuthenticateResult['token'])) { |
367: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AuthError); |
368: | } |
369: | |
370: | $oUser = Api::getUserById((int) $mAuthenticateResult['id']); |
371: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
372: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
373: | } |
374: | |
375: | $mResult = false; |
376: | if ($oUser->{$this->GetName().'::Secret'}) { |
377: | $sSecret = $oUser->{$this->GetName().'::Secret'}; |
378: | if ($oUser->{$this->GetName().'::IsEncryptedSecret'}) { |
379: | $sSecret = \Aurora\System\Utils::DecryptValue($sSecret); |
380: | } |
381: | $oGoogle = new \PHPGangsta_GoogleAuthenticator(); |
382: | $iClockTolerance = $this->getConfig('ClockTolerance', 2); |
383: | $oStatus = $oGoogle->verifyCode($sSecret, $Code, $iClockTolerance); |
384: | if ($oStatus) { |
385: | $mResult = \Aurora\Modules\Core\Module::Decorator()->SetAuthDataAndGetAuthToken($mAuthenticateResult); |
386: | } |
387: | } else { |
388: | throw new \Aurora\System\Exceptions\BaseException(Enums\ErrorCodes::SecretNotSet); |
389: | } |
390: | |
391: | return $mResult; |
392: | } |
393: | |
394: | |
395: | |
396: | |
397: | |
398: | |
399: | |
400: | public function GetBackupCodes($Password) |
401: | { |
402: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
403: | |
404: | if (!$this->getConfig('AllowBackupCodes', false)) { |
405: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
406: | } |
407: | |
408: | $oUser = Api::getAuthenticatedUser(); |
409: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
410: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
411: | } |
412: | |
413: | if (empty($Password)) { |
414: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter); |
415: | } |
416: | |
417: | if (!Api::GetModuleDecorator('Core')->VerifyPassword($Password)) { |
418: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
419: | } |
420: | |
421: | $sBackupCodes = \Aurora\System\Utils::DecryptValue($oUser->{$this->GetName().'::BackupCodes'}); |
422: | return [ |
423: | 'Datetime' => $oUser->{$this->GetName().'::BackupCodesTimestamp'}, |
424: | 'Codes' => empty($sBackupCodes) ? [] : json_decode($sBackupCodes) |
425: | ]; |
426: | } |
427: | |
428: | |
429: | |
430: | |
431: | |
432: | |
433: | |
434: | public function GenerateBackupCodes($Password) |
435: | { |
436: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
437: | |
438: | if (!$this->getConfig('AllowBackupCodes', false)) { |
439: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
440: | } |
441: | |
442: | $oUser = Api::getAuthenticatedUser(); |
443: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
444: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
445: | } |
446: | |
447: | if (empty($Password)) { |
448: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter); |
449: | } |
450: | |
451: | if (!Api::GetModuleDecorator('Core')->VerifyPassword($Password)) { |
452: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
453: | } |
454: | |
455: | $oRecovery = new Recovery(); |
456: | $aCodes = $oRecovery |
457: | ->setCount(10) |
458: | ->setBlocks(2) |
459: | ->setChars(4) |
460: | ->setBlockSeparator(' ') |
461: | ->uppercase() |
462: | ->toArray(); |
463: | |
464: | $oUser->setExtendedProp($this->GetName().'::BackupCodes', \Aurora\System\Utils::EncryptValue(json_encode($aCodes))); |
465: | $oUser->setExtendedProp($this->GetName().'::BackupCodesTimestamp', time()); |
466: | \Aurora\Modules\Core\Module::Decorator()->UpdateUserObject($oUser); |
467: | |
468: | return [ |
469: | 'Datetime' => $oUser->{$this->GetName().'::BackupCodesTimestamp'}, |
470: | 'Codes' => $aCodes, |
471: | ]; |
472: | } |
473: | |
474: | public function VerifyBackupCode($BackupCode, $Login, $Password) |
475: | { |
476: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous); |
477: | |
478: | if (!$this->getConfig('AllowBackupCodes', false)) { |
479: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
480: | } |
481: | |
482: | if (empty($BackupCode) || empty($Login) || empty($Password)) { |
483: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter); |
484: | } |
485: | |
486: | self::$VerifyState = true; |
487: | $mAuthenticateResult = \Aurora\Modules\Core\Module::Decorator()->Authenticate($Login, $Password); |
488: | self::$VerifyState = false; |
489: | if (!$mAuthenticateResult || !is_array($mAuthenticateResult) || !isset($mAuthenticateResult['token'])) { |
490: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AuthError); |
491: | } |
492: | |
493: | $oUser = Api::getUserById((int) $mAuthenticateResult['id']); |
494: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
495: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
496: | } |
497: | |
498: | $mResult = false; |
499: | $sBackupCodes = \Aurora\System\Utils::DecryptValue($oUser->{$this->GetName().'::BackupCodes'}); |
500: | $aBackupCodes = empty($sBackupCodes) ? [] : json_decode($sBackupCodes); |
501: | $sTrimmed = preg_replace('/\s+/', '', $BackupCode); |
502: | $sPrepared = substr_replace($sTrimmed, ' ', 4, 0); |
503: | $index = array_search($sPrepared, $aBackupCodes); |
504: | if ($index !== false) { |
505: | $aBackupCodes[$index] = ''; |
506: | $oUser->setExtendedProp($this->GetName().'::BackupCodes', \Aurora\System\Utils::EncryptValue(json_encode($aBackupCodes))); |
507: | \Aurora\Modules\Core\Module::Decorator()->UpdateUserObject($oUser); |
508: | $mResult = \Aurora\Modules\Core\Module::Decorator()->SetAuthDataAndGetAuthToken($mAuthenticateResult); |
509: | } |
510: | return $mResult; |
511: | } |
512: | |
513: | |
514: | |
515: | |
516: | |
517: | |
518: | |
519: | public function onAfterAuthenticate($aArgs, &$mResult) |
520: | { |
521: | if (!self::$VerifyState && $mResult && is_array($mResult) && isset($mResult['token'])) { |
522: | $oUser = Api::getUserById((int) $mResult['id']); |
523: | if ($oUser instanceof User) { |
524: | $bHasSecurityKey = false; |
525: | if ($this->getConfig('AllowSecurityKeys', false)) { |
526: | $iWebAuthnKeyCount = WebAuthnKey::where('UserId', $oUser->Id)->count(); |
527: | $bHasSecurityKey = $iWebAuthnKeyCount > 0; |
528: | } |
529: | |
530: | $bHasAuthenticatorApp = false; |
531: | if ($this->getConfig('AllowAuthenticatorApp', true)) { |
532: | $bHasAuthenticatorApp = !!(!empty($oUser->{$this->GetName().'::Secret'})); |
533: | } |
534: | |
535: | $bDeviceTrusted = ($bHasAuthenticatorApp || $bHasAuthenticatorApp) ? $this->getUsedDevicesManager()->checkDeviceAfterAuthenticate($oUser) : false; |
536: | |
537: | if (($bHasSecurityKey || $bHasAuthenticatorApp) && !$bDeviceTrusted) { |
538: | $mResult = [ |
539: | 'TwoFactorAuth' => [ |
540: | 'HasAuthenticatorApp' => $bHasAuthenticatorApp, |
541: | 'HasSecurityKey' => $bHasSecurityKey, |
542: | 'HasBackupCodes' => $this->getConfig('AllowBackupCodes', false) && !empty($oUser->{$this->GetName().'::BackupCodes'}) |
543: | ] |
544: | ]; |
545: | } |
546: | } |
547: | } |
548: | } |
549: | |
550: | |
551: | |
552: | |
553: | |
554: | |
555: | |
556: | public function RegisterSecurityKeyBegin($Password) |
557: | { |
558: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
559: | |
560: | if (!$this->getConfig('AllowSecurityKeys', false)) { |
561: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
562: | } |
563: | |
564: | $oUser = Api::getAuthenticatedUser(); |
565: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
566: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
567: | } |
568: | |
569: | if (empty($Password)) { |
570: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter); |
571: | } |
572: | |
573: | if (!Api::GetModuleDecorator('Core')->VerifyPassword($Password)) { |
574: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
575: | } |
576: | |
577: | $oCreateArgs = $this->oWebAuthn->getCreateArgs( |
578: | \base64_encode($oUser->UUID), |
579: | $oUser->PublicId, |
580: | $oUser->PublicId, |
581: | 90, |
582: | false, |
583: | 'discouraged', |
584: | true, |
585: | [] |
586: | ); |
587: | |
588: | $oCreateArgs->publicKey->user->id = \base64_encode($oCreateArgs->publicKey->user->id->getBinaryString()); |
589: | $oCreateArgs->publicKey->challenge = \base64_encode($oCreateArgs->publicKey->challenge->getBinaryString()); |
590: | $oUser->setExtendedProp($this->GetName().'::Challenge', $oCreateArgs->publicKey->challenge); |
591: | $oUser->save(); |
592: | |
593: | return $oCreateArgs; |
594: | } |
595: | |
596: | |
597: | |
598: | |
599: | |
600: | |
601: | |
602: | |
603: | |
604: | public function RegisterSecurityKeyFinish($Attestation, $Password) |
605: | { |
606: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
607: | |
608: | if (!$this->getConfig('AllowSecurityKeys', false)) { |
609: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
610: | } |
611: | |
612: | $oUser = Api::getAuthenticatedUser(); |
613: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
614: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
615: | } |
616: | |
617: | if (empty($Password) || empty($Attestation)) { |
618: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter); |
619: | } |
620: | |
621: | if (!Api::GetModuleDecorator('Core')->VerifyPassword($Password)) { |
622: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
623: | } |
624: | |
625: | $data = $this->oWebAuthn->processCreate( |
626: | \base64_decode($Attestation['clientDataJSON']), |
627: | \base64_decode($Attestation['attestationObject']), |
628: | \base64_decode($oUser->{$this->GetName().'::Challenge'}), |
629: | false |
630: | ); |
631: | $data->credentialId = \base64_encode($data->credentialId); |
632: | $data->AAGUID = \base64_encode($data->AAGUID); |
633: | |
634: | $sEncodedSecurityKeyData = \json_encode($data); |
635: | if ($sEncodedSecurityKeyData === false) { |
636: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::UnknownError, null, json_last_error_msg()); |
637: | } else { |
638: | $oWebAuthnKey = new WebAuthnKey(); |
639: | $oWebAuthnKey->UserId = $oUser->Id; |
640: | $oWebAuthnKey->KeyData = $sEncodedSecurityKeyData; |
641: | $oWebAuthnKey->CreationDateTime = time(); |
642: | |
643: | if ($oWebAuthnKey->save()) { |
644: | return $oWebAuthnKey->Id; |
645: | } |
646: | } |
647: | |
648: | return false; |
649: | } |
650: | |
651: | |
652: | |
653: | |
654: | |
655: | |
656: | |
657: | |
658: | public function VerifySecurityKeyBegin($Login, $Password) |
659: | { |
660: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous); |
661: | |
662: | if (!$this->getConfig('AllowSecurityKeys', false)) { |
663: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
664: | } |
665: | |
666: | self::$VerifyState = true; |
667: | $mAuthenticateResult = \Aurora\Modules\Core\Module::Decorator()->Authenticate($Login, $Password); |
668: | self::$VerifyState = false; |
669: | if (!$mAuthenticateResult || !is_array($mAuthenticateResult) || !isset($mAuthenticateResult['token'])) { |
670: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AuthError); |
671: | } |
672: | |
673: | $oUser = Api::getUserById((int) $mAuthenticateResult['id']); |
674: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
675: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
676: | } |
677: | |
678: | $mGetArgs = false; |
679: | $aIds = []; |
680: | $aWebAuthnKeys = WebAuthnKey::where('UserId', $oUser->Id)->get(); |
681: | |
682: | foreach ($aWebAuthnKeys as $oWebAuthnKey) { |
683: | $oKeyData = \json_decode($oWebAuthnKey->KeyData); |
684: | $aIds[] = \base64_decode($oKeyData->credentialId); |
685: | } |
686: | |
687: | if (count($aIds) > 0) { |
688: | $mGetArgs = $this->oWebAuthn->getGetArgs( |
689: | $aIds, |
690: | 90 |
691: | ); |
692: | $mGetArgs->publicKey->challenge = \base64_encode($mGetArgs->publicKey->challenge->getBinaryString()); |
693: | if (is_array($mGetArgs->publicKey->allowCredentials)) { |
694: | foreach ($mGetArgs->publicKey->allowCredentials as $key => $val) { |
695: | $val->id = \base64_encode($val->id->getBinaryString()); |
696: | $mGetArgs->publicKey->allowCredentials[$key] = $val; |
697: | } |
698: | } |
699: | |
700: | $oUser->setExtendedProp($this->GetName().'::Challenge', $mGetArgs->publicKey->challenge); |
701: | $oUser->save(); |
702: | } |
703: | |
704: | return $mGetArgs; |
705: | } |
706: | |
707: | |
708: | |
709: | |
710: | |
711: | |
712: | |
713: | |
714: | |
715: | |
716: | public function VerifySecurityKeyFinish($Login, $Password, $Attestation) |
717: | { |
718: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous); |
719: | |
720: | if (!$this->getConfig('AllowSecurityKeys', false)) { |
721: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
722: | } |
723: | |
724: | self::$VerifyState = true; |
725: | $mAuthenticateResult = \Aurora\Modules\Core\Module::Decorator()->Authenticate($Login, $Password); |
726: | self::$VerifyState = false; |
727: | if (!$mAuthenticateResult || !is_array($mAuthenticateResult) || !isset($mAuthenticateResult['token'])) { |
728: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AuthError); |
729: | } |
730: | |
731: | $oUser = Api::getUserById((int) $mAuthenticateResult['id']); |
732: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
733: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
734: | } |
735: | |
736: | $mResult = true; |
737: | $clientDataJSON = base64_decode($Attestation['clientDataJSON']); |
738: | $authenticatorData = base64_decode($Attestation['authenticatorData']); |
739: | $signature = base64_decode($Attestation['signature']); |
740: | $id = base64_decode($Attestation['id']); |
741: | $credentialPublicKey = null; |
742: | |
743: | $challenge = \base64_decode($oUser->{$this->GetName().'::Challenge'}); |
744: | |
745: | $aWebAuthnKeys = WebAuthnKey::where('UserId', $oUser->Id)->get(); |
746: | |
747: | $oWebAuthnKey = null; |
748: | foreach ($aWebAuthnKeys as $oWebAuthnKey) { |
749: | $oKeyData = \json_decode($oWebAuthnKey->KeyData); |
750: | if (\base64_decode($oKeyData->credentialId) === $id) { |
751: | $credentialPublicKey = $oKeyData->credentialPublicKey; |
752: | break; |
753: | } |
754: | } |
755: | |
756: | if ($credentialPublicKey !== null) { |
757: | try { |
758: | |
759: | $this->oWebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $credentialPublicKey, $challenge, null, false); |
760: | $mResult = \Aurora\Modules\Core\Module::Decorator()->SetAuthDataAndGetAuthToken($mAuthenticateResult); |
761: | if (isset($oWebAuthnKey)) { |
762: | $oWebAuthnKey->LastUsageDateTime = time(); |
763: | $oWebAuthnKey->save(); |
764: | } |
765: | } catch (\Exception $oEx) { |
766: | $mResult = false; |
767: | throw new \Aurora\System\Exceptions\ApiException(999, $oEx, $oEx->getMessage()); |
768: | } |
769: | } |
770: | |
771: | return $mResult; |
772: | } |
773: | |
774: | |
775: | |
776: | |
777: | |
778: | |
779: | |
780: | |
781: | |
782: | public function UpdateSecurityKeyName($KeyId, $NewName, $Password) |
783: | { |
784: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
785: | |
786: | if (!$this->getConfig('AllowSecurityKeys', false)) { |
787: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
788: | } |
789: | |
790: | $oUser = Api::getAuthenticatedUser(); |
791: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
792: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
793: | } |
794: | |
795: | if (empty($Password) || empty($KeyId) || empty($NewName)) { |
796: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter); |
797: | } |
798: | |
799: | if (!Api::GetModuleDecorator('Core')->VerifyPassword($Password)) { |
800: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
801: | } |
802: | |
803: | $mResult = false; |
804: | $oWebAuthnKey = WebAuthnKey::where('UserId', $oUser->Id) |
805: | ->where('Id', $KeyId) |
806: | ->first(); |
807: | |
808: | if ($oWebAuthnKey instanceof WebAuthnKey) { |
809: | $oWebAuthnKey->Name = $NewName; |
810: | $mResult = $oWebAuthnKey->save(); |
811: | } |
812: | return $mResult; |
813: | } |
814: | |
815: | |
816: | |
817: | |
818: | |
819: | |
820: | |
821: | |
822: | public function DeleteSecurityKey($KeyId, $Password) |
823: | { |
824: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
825: | |
826: | if (!$this->getConfig('AllowSecurityKeys', false)) { |
827: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
828: | } |
829: | |
830: | $oUser = Api::getAuthenticatedUser(); |
831: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
832: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
833: | } |
834: | |
835: | if (empty($Password) || empty($KeyId)) { |
836: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter); |
837: | } |
838: | |
839: | if (!Api::GetModuleDecorator('Core')->VerifyPassword($Password)) { |
840: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
841: | } |
842: | |
843: | $mResult = false; |
844: | $oWebAuthnKey = WebAuthnKey::where('UserId', $oUser->Id) |
845: | ->where('Id', $KeyId) |
846: | ->first(); |
847: | if ($oWebAuthnKey instanceof WebAuthnKey) { |
848: | $mResult = $oWebAuthnKey->delete(); |
849: | $this->_removeAllDataWhenAllSecondFactorsDisabled($oUser); |
850: | } |
851: | return $mResult; |
852: | } |
853: | |
854: | |
855: | |
856: | |
857: | |
858: | |
859: | |
860: | public function VerifyPassword($Password) |
861: | { |
862: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
863: | |
864: | if (empty($Password)) { |
865: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter); |
866: | } |
867: | |
868: | return Api::GetModuleDecorator('Core')->VerifyPassword($Password); |
869: | } |
870: | |
871: | public function EntryVerifySecurityKey() |
872: | { |
873: | $oModuleManager = Api::GetModuleManager(); |
874: | $sTheme = $oModuleManager->getModuleConfigValue('CoreWebclient', 'Theme'); |
875: | |
876: | $oHttp = \MailSo\Base\Http::SingletonInstance(); |
877: | $sLogin = $oHttp->GetQuery('login', ''); |
878: | $sPassword = $oHttp->GetQuery('password', ''); |
879: | $sPackageName = $oHttp->GetQuery('package_name', ''); |
880: | if (empty($sLogin) || empty($sPassword)) { |
881: | return ''; |
882: | } |
883: | |
884: | $oGetArgs = false; |
885: | $sError = false; |
886: | try { |
887: | $oGetArgs = self::Decorator()->VerifySecurityKeyBegin($sLogin, $sPassword); |
888: | } catch (\Exception $oEx) { |
889: | $sError = $oEx->getCode() . ': ' . $oEx->getMessage(); |
890: | } |
891: | $sResult = \file_get_contents($this->GetPath().'/templates/EntryVerifySecurityKey.html'); |
892: | $sResult = \strtr($sResult, array( |
893: | '{{GetArgs}}' => \Aurora\System\Managers\Response::GetJsonFromObject(null, $oGetArgs), |
894: | '{{PackageName}}' => $sPackageName, |
895: | '{{Error}}' => $sError, |
896: | '{{Description}}' => $this->i18N('HINT_INSERT_TOUCH_SECURITY_KEY'), |
897: | '{{Theme}}' => $sTheme, |
898: | )); |
899: | \Aurora\Modules\CoreWebclient\Module::Decorator()->SetHtmlOutputHeaders(); |
900: | @header('Cache-Control: no-cache', true); |
901: | return $sResult; |
902: | } |
903: | |
904: | public function EntryAssetlinks() |
905: | { |
906: | @header('Content-Type: application/json; charset=utf-8'); |
907: | @header('Cache-Control: no-cache', true); |
908: | |
909: | $sPath = __DIR__ . '/assets/assetlinks.json'; |
910: | $sDistPath = __DIR__ . '/assets/assetlinks.dist.json'; |
911: | |
912: | if (file_exists($sPath)) { |
913: | $sFileContent = file_get_contents($sPath); |
914: | } elseif (file_exists($sDistPath)) { |
915: | $sFileContent = file_get_contents($sDistPath); |
916: | } else { |
917: | $sFileContent = "[]"; |
918: | } |
919: | |
920: | echo $sFileContent; |
921: | } |
922: | |
923: | public function TrustDevice($DeviceId, $DeviceName) |
924: | { |
925: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous); |
926: | |
927: | if (!$this->getConfig('AllowUsedDevices', false)) { |
928: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
929: | } |
930: | |
931: | if (!Api::validateAuthToken()) { |
932: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AuthError); |
933: | } |
934: | |
935: | $oUser = Api::getAuthenticatedUser(Api::getAuthToken()); |
936: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
937: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
938: | } |
939: | |
940: | return $this->getUsedDevicesManager()->trustDevice($oUser->Id, $DeviceId, $DeviceName); |
941: | } |
942: | |
943: | public function SaveDevice($DeviceId, $DeviceName) |
944: | { |
945: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
946: | |
947: | if ($this->getConfig('AllowUsedDevices', false)) { |
948: | $oUser = Api::getAuthenticatedUser(); |
949: | return $this->getUsedDevicesManager()->saveDevice($oUser->Id, $DeviceId, $DeviceName, Api::getAuthToken()); |
950: | } else { |
951: | return false; |
952: | } |
953: | } |
954: | |
955: | public function GetUsedDevices() |
956: | { |
957: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
958: | |
959: | if (!$this->getConfig('AllowUsedDevices', false)) { |
960: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
961: | } |
962: | |
963: | $oUser = Api::getAuthenticatedUser(); |
964: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
965: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
966: | } |
967: | |
968: | $usedDevices = $this->getUsedDevicesManager()->getAllDevices($oUser->Id)->toArray(); |
969: | foreach ($usedDevices as &$aResult) { |
970: | $aResult['Authenticated'] = false; |
971: | if (Api::GetSettings()->GetValue('StoreAuthTokenInDB', false) && !empty($aResult['AuthToken']) && !empty(Api::UserSession()->Get($aResult['AuthToken']))) { |
972: | $aResult['Authenticated'] = true; |
973: | } |
974: | unset($aResult['AuthToken']); |
975: | } |
976: | |
977: | return $usedDevices; |
978: | } |
979: | |
980: | public function RevokeTrustFromAllDevices() |
981: | { |
982: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
983: | |
984: | if (!$this->getConfig('AllowUsedDevices', false)) { |
985: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
986: | } |
987: | |
988: | if (!$this->getUsedDevicesManager()->isTrustedDevicesEnabled()) { |
989: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
990: | } |
991: | |
992: | $oUser = Api::getAuthenticatedUser(); |
993: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
994: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
995: | } |
996: | |
997: | return $this->getUsedDevicesManager()->revokeTrustFromAllDevices($oUser); |
998: | } |
999: | |
1000: | public function onBeforeLogout($aArgs, &$mResult) |
1001: | { |
1002: | $oUser = Api::getAuthenticatedUser(); |
1003: | if ($oUser instanceof User && $oUser->isNormalOrTenant()) { |
1004: | $oUsedDevice = $this->getUsedDevicesManager()->getDeviceByAuthToken($oUser->Id, Api::getAuthToken()); |
1005: | if ($oUsedDevice) { |
1006: | $oUsedDevice->AuthToken = ''; |
1007: | $oUsedDevice->save(); |
1008: | } |
1009: | } |
1010: | } |
1011: | |
1012: | public function LogoutFromDevice($DeviceId) |
1013: | { |
1014: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
1015: | |
1016: | if (!$this->getConfig('AllowUsedDevices', false)) { |
1017: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
1018: | } |
1019: | |
1020: | $oUser = Api::getAuthenticatedUser(); |
1021: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant()) { |
1022: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
1023: | } |
1024: | |
1025: | if (empty($DeviceId)) { |
1026: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter); |
1027: | } |
1028: | |
1029: | $oUsedDevice = $this->getUsedDevicesManager()->getDevice($oUser->Id, $DeviceId); |
1030: | if ($oUsedDevice && !empty($oUsedDevice->AuthToken)) { |
1031: | Api::UserSession()->Delete($oUsedDevice->AuthToken); |
1032: | $oUsedDevice->AuthToken = ''; |
1033: | $oUsedDevice->TrustTillDateTime = $oUsedDevice->CreationDateTime; |
1034: | $oUsedDevice->save(); |
1035: | } |
1036: | return true; |
1037: | } |
1038: | |
1039: | public function RemoveDevice($DeviceId) |
1040: | { |
1041: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
1042: | $oUser = Api::getAuthenticatedUser(); |
1043: | if (!$this->getConfig('AllowUsedDevices', false) && !$oUser->isAdmin()) { |
1044: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
1045: | } |
1046: | |
1047: | if (!($oUser instanceof User) || !$oUser->isNormalOrTenant() && !$oUser->isAdmin()) { |
1048: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied); |
1049: | } |
1050: | |
1051: | if (empty($DeviceId)) { |
1052: | throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter); |
1053: | } |
1054: | |
1055: | $oUsedDevice = $oUser->isAdmin() ? $this->getUsedDevicesManager()->getDeviceByDeviceId($DeviceId) : $this->getUsedDevicesManager()->getDevice($oUser->Id, $DeviceId); |
1056: | if ($oUsedDevice) { |
1057: | Api::UserSession()->Delete($oUsedDevice->AuthToken); |
1058: | $oUsedDevice->delete(); |
1059: | } |
1060: | return true; |
1061: | } |
1062: | |
1063: | protected function _getWebAuthKeysInfo($oUser) |
1064: | { |
1065: | $aWebAuthKeysInfo = []; |
1066: | |
1067: | if ($oUser instanceof User && $oUser->isNormalOrTenant()) { |
1068: | $aWebAuthnKeys = WebAuthnKey::where('UserId', $oUser->Id)->get(); |
1069: | foreach ($aWebAuthnKeys as $oWebAuthnKey) { |
1070: | $aWebAuthKeysInfo[] = [ |
1071: | $oWebAuthnKey->Id, |
1072: | $oWebAuthnKey->Name |
1073: | ]; |
1074: | } |
1075: | } |
1076: | |
1077: | return $aWebAuthKeysInfo; |
1078: | } |
1079: | |
1080: | protected function _removeAllDataWhenAllSecondFactorsDisabled($oUser) |
1081: | { |
1082: | $iWebAuthnKeyCount = WebAuthnKey::where('UserId', $oUser->Id)->count(); |
1083: | if (empty($oUser->{$this->GetName().'::Secret'}) && $iWebAuthnKeyCount === 0) { |
1084: | $oUser->setExtendedProp($this->GetName().'::BackupCodes', ''); |
1085: | $oUser->setExtendedProp($this->GetName().'::BackupCodesTimestamp', ''); |
1086: | \Aurora\Modules\Core\Module::Decorator()->UpdateUserObject($oUser); |
1087: | |
1088: | $this->getUsedDevicesManager()->revokeTrustFromAllDevices($oUser); |
1089: | } |
1090: | } |
1091: | } |
1092: | |