From: Hypolite Petovan Date: Tue, 19 Jan 2021 03:53:06 +0000 (-0500) Subject: Move all two-factor authentication classes in Security\TwoFactor X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=3e257d42660136efff37adc6ef8e4c68eb5cc4c5;p=friendica.git Move all two-factor authentication classes in Security\TwoFactor --- diff --git a/src/Model/TwoFactor/AppSpecificPassword.php b/src/Model/TwoFactor/AppSpecificPassword.php deleted file mode 100644 index 1e8d56f759..0000000000 --- a/src/Model/TwoFactor/AppSpecificPassword.php +++ /dev/null @@ -1,153 +0,0 @@ -. - * - */ - -namespace Friendica\Model\TwoFactor; - -use Friendica\Database\DBA; -use Friendica\Model\User; -use Friendica\Util\DateTimeFormat; -use Friendica\Util\Temporal; -use PragmaRX\Random\Random; - -/** - * Manages users' two-factor recovery hashed_passwords in the 2fa_app_specific_passwords table - */ -class AppSpecificPassword -{ - public static function countForUser($uid) - { - return DBA::count('2fa_app_specific_password', ['uid' => $uid]); - } - - public static function checkDuplicateForUser($uid, $description) - { - return DBA::exists('2fa_app_specific_password', ['uid' => $uid, 'description' => $description]); - } - - /** - * Checks the provided hashed_password is available to use for login by the provided user - * - * @param int $uid User ID - * @param string $plaintextPassword - * @return bool - * @throws \Exception - */ - public static function authenticateUser($uid, $plaintextPassword) - { - $appSpecificPasswords = self::getListForUser($uid); - - $return = false; - - foreach ($appSpecificPasswords as $appSpecificPassword) { - if (password_verify($plaintextPassword, $appSpecificPassword['hashed_password'])) { - $fields = ['last_used' => DateTimeFormat::utcNow()]; - if (password_needs_rehash($appSpecificPassword['hashed_password'], PASSWORD_DEFAULT)) { - $fields['hashed_password'] = User::hashPassword($plaintextPassword); - } - - self::update($appSpecificPassword['id'], $fields); - - $return |= true; - } - } - - return $return; - } - - /** - * Returns a complete list of all recovery hashed_passwords for the provided user, including the used status - * - * @param int $uid User ID - * @return array - * @throws \Exception - */ - public static function getListForUser($uid) - { - $appSpecificPasswordsStmt = DBA::select('2fa_app_specific_password', ['id', 'description', 'hashed_password', 'last_used'], ['uid' => $uid]); - - $appSpecificPasswords = DBA::toArray($appSpecificPasswordsStmt); - - array_walk($appSpecificPasswords, function (&$value) { - $value['ago'] = Temporal::getRelativeDate($value['last_used']); - }); - - return $appSpecificPasswords; - } - - /** - * Generates a new app specific password for the provided user and hashes it in the database. - * - * @param int $uid User ID - * @param string $description Password description - * @return array The new app-specific password data structure with the plaintext password added - * @throws \Exception - */ - public static function generateForUser(int $uid, $description) - { - $Random = (new Random())->size(40); - - $plaintextPassword = $Random->get(); - - $generated = DateTimeFormat::utcNow(); - - $fields = [ - 'uid' => $uid, - 'description' => $description, - 'hashed_password' => User::hashPassword($plaintextPassword), - 'generated' => $generated, - ]; - - DBA::insert('2fa_app_specific_password', $fields); - - $fields['id'] = DBA::lastInsertId(); - $fields['plaintext_password'] = $plaintextPassword; - - return $fields; - } - - private static function update($appSpecificPasswordId, $fields) - { - return DBA::update('2fa_app_specific_password', $fields, ['id' => $appSpecificPasswordId]); - } - - /** - * Deletes all the recovery hashed_passwords for the provided user. - * - * @param int $uid User ID - * @return bool - * @throws \Exception - */ - public static function deleteAllForUser(int $uid) - { - return DBA::delete('2fa_app_specific_password', ['uid' => $uid]); - } - - /** - * @param int $uid - * @param int $app_specific_password_id - * @return bool - * @throws \Exception - */ - public static function deleteForUser(int $uid, int $app_specific_password_id) - { - return DBA::delete('2fa_app_specific_password', ['id' => $app_specific_password_id, 'uid' => $uid]); - } -} diff --git a/src/Model/TwoFactor/RecoveryCode.php b/src/Model/TwoFactor/RecoveryCode.php deleted file mode 100644 index 2c3b71ad41..0000000000 --- a/src/Model/TwoFactor/RecoveryCode.php +++ /dev/null @@ -1,141 +0,0 @@ -. - * - */ - -namespace Friendica\Model\TwoFactor; - -use Friendica\Database\DBA; -use Friendica\Util\DateTimeFormat; -use PragmaRX\Random\Random; -use PragmaRX\Recovery\Recovery; - -/** - * Manages users' two-factor recovery codes in the 2fa_recovery_codes table - */ -class RecoveryCode -{ - /** - * Returns the number of code the provided users can still use to replace a TOTP code - * - * @param int $uid User ID - * @return int - * @throws \Exception - */ - public static function countValidForUser($uid) - { - return DBA::count('2fa_recovery_codes', ['uid' => $uid, 'used' => null]); - } - - /** - * Checks the provided code is available to use for login by the provided user - * - * @param int $uid User ID - * @param string $code - * @return bool - * @throws \Exception - */ - public static function existsForUser($uid, $code) - { - return DBA::exists('2fa_recovery_codes', ['uid' => $uid, 'code' => $code, 'used' => null]); - } - - /** - * Returns a complete list of all recovery codes for the provided user, including the used status - * - * @param int $uid User ID - * @return array - * @throws \Exception - */ - public static function getListForUser($uid) - { - $codesStmt = DBA::select('2fa_recovery_codes', ['code', 'used'], ['uid' => $uid]); - - return DBA::toArray($codesStmt); - } - - /** - * Marks the provided code as used for the provided user. - * Returns false if the code doesn't exist for the user or it has been used already. - * - * @param int $uid User ID - * @param string $code - * @return bool - * @throws \Exception - */ - public static function markUsedForUser($uid, $code) - { - DBA::update('2fa_recovery_codes', ['used' => DateTimeFormat::utcNow()], ['uid' => $uid, 'code' => $code, 'used' => null]); - - return DBA::affectedRows() > 0; - } - - /** - * Generates a fresh set of recovery codes for the provided user. - * Generates 12 codes constituted of 2 blocks of 6 characters separated by a dash. - * - * @param int $uid User ID - * @throws \Exception - */ - public static function generateForUser($uid) - { - $Random = (new Random())->pattern('[a-z0-9]'); - - $RecoveryGenerator = new Recovery($Random); - - $codes = $RecoveryGenerator - ->setCount(12) - ->setBlocks(2) - ->setChars(6) - ->lowercase(true) - ->toArray(); - - $generated = DateTimeFormat::utcNow(); - foreach ($codes as $code) { - DBA::insert('2fa_recovery_codes', [ - 'uid' => $uid, - 'code' => $code, - 'generated' => $generated - ]); - } - } - - /** - * Deletes all the recovery codes for the provided user. - * - * @param int $uid User ID - * @throws \Exception - */ - public static function deleteForUser($uid) - { - DBA::delete('2fa_recovery_codes', ['uid' => $uid]); - } - - /** - * Replaces the existing recovery codes for the provided user by a freshly generated set. - * - * @param int $uid User ID - * @throws \Exception - */ - public static function regenerateForUser($uid) - { - self::deleteForUser($uid); - self::generateForUser($uid); - } -} diff --git a/src/Model/User.php b/src/Model/User.php index dbace74e5d..41e612ac80 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -34,7 +34,7 @@ use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\DI; -use Friendica\Model\TwoFactor\AppSpecificPassword; +use Friendica\Security\TwoFactor\Model\AppSpecificPassword; use Friendica\Network\HTTPException; use Friendica\Object\Image; use Friendica\Util\Crypto; diff --git a/src/Module/Security/TwoFactor/Recovery.php b/src/Module/Security/TwoFactor/Recovery.php index 7af1b6ac01..384d36e0d3 100644 --- a/src/Module/Security/TwoFactor/Recovery.php +++ b/src/Module/Security/TwoFactor/Recovery.php @@ -25,7 +25,7 @@ use Friendica\BaseModule; use Friendica\Core\Renderer; use Friendica\Core\Session; use Friendica\DI; -use Friendica\Model\TwoFactor\RecoveryCode; +use Friendica\Security\TwoFactor\Model\RecoveryCode; /** * // Page 1a: Recovery code verification diff --git a/src/Module/Settings/TwoFactor/AppSpecific.php b/src/Module/Settings/TwoFactor/AppSpecific.php index db094a8855..8c11af0295 100644 --- a/src/Module/Settings/TwoFactor/AppSpecific.php +++ b/src/Module/Settings/TwoFactor/AppSpecific.php @@ -23,7 +23,7 @@ namespace Friendica\Module\Settings\TwoFactor; use Friendica\Core\Renderer; use Friendica\DI; -use Friendica\Model\TwoFactor\AppSpecificPassword; +use Friendica\Security\TwoFactor\Model\AppSpecificPassword; use Friendica\Module\BaseSettings; use Friendica\Module\Security\Login; diff --git a/src/Module/Settings/TwoFactor/Index.php b/src/Module/Settings/TwoFactor/Index.php index 8cc04787f5..bd3840485f 100644 --- a/src/Module/Settings/TwoFactor/Index.php +++ b/src/Module/Settings/TwoFactor/Index.php @@ -24,8 +24,8 @@ namespace Friendica\Module\Settings\TwoFactor; use Friendica\Core\Renderer; use Friendica\Core\Session; use Friendica\DI; -use Friendica\Model\TwoFactor\AppSpecificPassword; -use Friendica\Model\TwoFactor\RecoveryCode; +use Friendica\Security\TwoFactor\Model\AppSpecificPassword; +use Friendica\Security\TwoFactor\Model\RecoveryCode; use Friendica\Model\User; use Friendica\Module\BaseSettings; use Friendica\Module\Security\Login; diff --git a/src/Module/Settings/TwoFactor/Recovery.php b/src/Module/Settings/TwoFactor/Recovery.php index 7b0d28534f..6ee52bd036 100644 --- a/src/Module/Settings/TwoFactor/Recovery.php +++ b/src/Module/Settings/TwoFactor/Recovery.php @@ -23,7 +23,7 @@ namespace Friendica\Module\Settings\TwoFactor; use Friendica\Core\Renderer; use Friendica\DI; -use Friendica\Model\TwoFactor\RecoveryCode; +use Friendica\Security\TwoFactor\Model\RecoveryCode; use Friendica\Module\BaseSettings; use Friendica\Module\Security\Login; diff --git a/src/Security/TwoFactor/Model/AppSpecificPassword.php b/src/Security/TwoFactor/Model/AppSpecificPassword.php new file mode 100644 index 0000000000..1524e25920 --- /dev/null +++ b/src/Security/TwoFactor/Model/AppSpecificPassword.php @@ -0,0 +1,153 @@ +. + * + */ + +namespace Friendica\Security\TwoFactor\Model; + +use Friendica\Database\DBA; +use Friendica\Model\User; +use Friendica\Util\DateTimeFormat; +use Friendica\Util\Temporal; +use PragmaRX\Random\Random; + +/** + * Manages users' two-factor recovery hashed_passwords in the 2fa_app_specific_passwords table + */ +class AppSpecificPassword +{ + public static function countForUser($uid) + { + return DBA::count('2fa_app_specific_password', ['uid' => $uid]); + } + + public static function checkDuplicateForUser($uid, $description) + { + return DBA::exists('2fa_app_specific_password', ['uid' => $uid, 'description' => $description]); + } + + /** + * Checks the provided hashed_password is available to use for login by the provided user + * + * @param int $uid User ID + * @param string $plaintextPassword + * @return bool + * @throws \Exception + */ + public static function authenticateUser($uid, $plaintextPassword) + { + $appSpecificPasswords = self::getListForUser($uid); + + $return = false; + + foreach ($appSpecificPasswords as $appSpecificPassword) { + if (password_verify($plaintextPassword, $appSpecificPassword['hashed_password'])) { + $fields = ['last_used' => DateTimeFormat::utcNow()]; + if (password_needs_rehash($appSpecificPassword['hashed_password'], PASSWORD_DEFAULT)) { + $fields['hashed_password'] = User::hashPassword($plaintextPassword); + } + + self::update($appSpecificPassword['id'], $fields); + + $return |= true; + } + } + + return $return; + } + + /** + * Returns a complete list of all recovery hashed_passwords for the provided user, including the used status + * + * @param int $uid User ID + * @return array + * @throws \Exception + */ + public static function getListForUser($uid) + { + $appSpecificPasswordsStmt = DBA::select('2fa_app_specific_password', ['id', 'description', 'hashed_password', 'last_used'], ['uid' => $uid]); + + $appSpecificPasswords = DBA::toArray($appSpecificPasswordsStmt); + + array_walk($appSpecificPasswords, function (&$value) { + $value['ago'] = Temporal::getRelativeDate($value['last_used']); + }); + + return $appSpecificPasswords; + } + + /** + * Generates a new app specific password for the provided user and hashes it in the database. + * + * @param int $uid User ID + * @param string $description Password description + * @return array The new app-specific password data structure with the plaintext password added + * @throws \Exception + */ + public static function generateForUser(int $uid, $description) + { + $Random = (new Random())->size(40); + + $plaintextPassword = $Random->get(); + + $generated = DateTimeFormat::utcNow(); + + $fields = [ + 'uid' => $uid, + 'description' => $description, + 'hashed_password' => User::hashPassword($plaintextPassword), + 'generated' => $generated, + ]; + + DBA::insert('2fa_app_specific_password', $fields); + + $fields['id'] = DBA::lastInsertId(); + $fields['plaintext_password'] = $plaintextPassword; + + return $fields; + } + + private static function update($appSpecificPasswordId, $fields) + { + return DBA::update('2fa_app_specific_password', $fields, ['id' => $appSpecificPasswordId]); + } + + /** + * Deletes all the recovery hashed_passwords for the provided user. + * + * @param int $uid User ID + * @return bool + * @throws \Exception + */ + public static function deleteAllForUser(int $uid) + { + return DBA::delete('2fa_app_specific_password', ['uid' => $uid]); + } + + /** + * @param int $uid + * @param int $app_specific_password_id + * @return bool + * @throws \Exception + */ + public static function deleteForUser(int $uid, int $app_specific_password_id) + { + return DBA::delete('2fa_app_specific_password', ['id' => $app_specific_password_id, 'uid' => $uid]); + } +} diff --git a/src/Security/TwoFactor/Model/RecoveryCode.php b/src/Security/TwoFactor/Model/RecoveryCode.php new file mode 100644 index 0000000000..d6a1a6e951 --- /dev/null +++ b/src/Security/TwoFactor/Model/RecoveryCode.php @@ -0,0 +1,141 @@ +. + * + */ + +namespace Friendica\Security\TwoFactor\Model; + +use Friendica\Database\DBA; +use Friendica\Util\DateTimeFormat; +use PragmaRX\Random\Random; +use PragmaRX\Recovery\Recovery; + +/** + * Manages users' two-factor recovery codes in the 2fa_recovery_codes table + */ +class RecoveryCode +{ + /** + * Returns the number of code the provided users can still use to replace a TOTP code + * + * @param int $uid User ID + * @return int + * @throws \Exception + */ + public static function countValidForUser($uid) + { + return DBA::count('2fa_recovery_codes', ['uid' => $uid, 'used' => null]); + } + + /** + * Checks the provided code is available to use for login by the provided user + * + * @param int $uid User ID + * @param string $code + * @return bool + * @throws \Exception + */ + public static function existsForUser($uid, $code) + { + return DBA::exists('2fa_recovery_codes', ['uid' => $uid, 'code' => $code, 'used' => null]); + } + + /** + * Returns a complete list of all recovery codes for the provided user, including the used status + * + * @param int $uid User ID + * @return array + * @throws \Exception + */ + public static function getListForUser($uid) + { + $codesStmt = DBA::select('2fa_recovery_codes', ['code', 'used'], ['uid' => $uid]); + + return DBA::toArray($codesStmt); + } + + /** + * Marks the provided code as used for the provided user. + * Returns false if the code doesn't exist for the user or it has been used already. + * + * @param int $uid User ID + * @param string $code + * @return bool + * @throws \Exception + */ + public static function markUsedForUser($uid, $code) + { + DBA::update('2fa_recovery_codes', ['used' => DateTimeFormat::utcNow()], ['uid' => $uid, 'code' => $code, 'used' => null]); + + return DBA::affectedRows() > 0; + } + + /** + * Generates a fresh set of recovery codes for the provided user. + * Generates 12 codes constituted of 2 blocks of 6 characters separated by a dash. + * + * @param int $uid User ID + * @throws \Exception + */ + public static function generateForUser($uid) + { + $Random = (new Random())->pattern('[a-z0-9]'); + + $RecoveryGenerator = new Recovery($Random); + + $codes = $RecoveryGenerator + ->setCount(12) + ->setBlocks(2) + ->setChars(6) + ->lowercase(true) + ->toArray(); + + $generated = DateTimeFormat::utcNow(); + foreach ($codes as $code) { + DBA::insert('2fa_recovery_codes', [ + 'uid' => $uid, + 'code' => $code, + 'generated' => $generated + ]); + } + } + + /** + * Deletes all the recovery codes for the provided user. + * + * @param int $uid User ID + * @throws \Exception + */ + public static function deleteForUser($uid) + { + DBA::delete('2fa_recovery_codes', ['uid' => $uid]); + } + + /** + * Replaces the existing recovery codes for the provided user by a freshly generated set. + * + * @param int $uid User ID + * @throws \Exception + */ + public static function regenerateForUser($uid) + { + self::deleteForUser($uid); + self::generateForUser($uid); + } +}