diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index c0227fe5ea..3e7a42f0f1 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -196,7 +196,7 @@ App::post('/v1/account/sessions') Authorization::setRole('user:' . $profile->getId()); // Re-hash if not using recommended algo - if($profile->getAttribute('hash') !== Auth::DEFAULT_ALGO) { + if ($profile->getAttribute('hash') !== Auth::DEFAULT_ALGO) { $profile ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) ->setAttribute('hash', Auth::DEFAULT_ALGO) @@ -1262,8 +1262,7 @@ App::patch('/v1/account/password') ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) ->setAttribute('hash', Auth::DEFAULT_ALGO) ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) - ->setAttribute('passwordUpdate', \time()) - ); + ->setAttribute('passwordUpdate', \time())); $audits ->setResource('user/' . $user->getId()) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 70cfe30987..06f527ee90 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -335,10 +335,10 @@ App::post('/v1/teams/:teamId/memberships') 'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), 'hash' => Auth::DEFAULT_ALGO, 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, - /** - * Set the password update time to 0 for users created using - * team invite and OAuth to allow password updates without an - * old password + /** + * Set the password update time to 0 for users created using + * team invite and OAuth to allow password updates without an + * old password */ 'passwordUpdate' => 0, 'registration' => \time(), diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index a161a82d8c..8a11fb1157 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -29,7 +29,8 @@ use Utopia\Validator\Boolean; use MaxMind\Db\Reader; use Utopia\Validator\Integer; -function createUser(string $hash, mixed $hashOptions, string $userId, string $email, string $password, string $name, Database $dbForProject, Stats $usage, Event $events): Document { +function createUser(string $hash, mixed $hashOptions, string $userId, string $email, string $password, string $name, Database $dbForProject, Stats $usage, Event $events): Document +{ $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array $email = \strtolower($email); @@ -204,7 +205,7 @@ App::post('/v1/users/import/sha') ->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) { $options = '{}'; - if(!empty($passwordVersion)) { + if (!empty($passwordVersion)) { $options = '{"version":"' . $passwordVersion . '"}'; } @@ -269,26 +270,26 @@ App::post('/v1/users/import/scrypt') ->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) { $options = []; - if(!empty($passwordSalt)) { + if (!empty($passwordSalt)) { $options['salt'] = $passwordSalt; } - if(!empty($passwordCpu)) { + if (!empty($passwordCpu)) { $options['cost_cpu'] = $passwordCpu; } - if(!empty($passwordMemory)) { + if (!empty($passwordMemory)) { $options['cost_memory'] = $passwordMemory; } - if(!empty($passwordParallel)) { + if (!empty($passwordParallel)) { $options['cost_parallel'] = $passwordParallel; } - if(!empty($passwordLength)) { + if (!empty($passwordLength)) { $options['length'] = $passwordLength; } - + $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, $name, $dbForProject, $usage, $events); $response->setStatusCode(Response::STATUS_CODE_CREATED); diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 406720ad5d..850afadd0a 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -7,8 +7,7 @@ use Utopia\Database\Validator\Authorization; class Auth { - - const SUPPORTED_ALGOS = [ + public const SUPPORTED_ALGOS = [ 'argon2' => 'Argon2', 'bcrypt' => 'Bcrypt', 'md5' => 'Md5', @@ -19,8 +18,8 @@ class Auth 'plaintext' => '' // This is alias for DX purposes. It is translated to default algo ]; - const DEFAULT_ALGO = 'argon2'; - const DEFAULT_ALGO_OPTIONS = ['memory_cost' => 2048, 'time_cost' => 4, 'threads' => 3]; + public const DEFAULT_ALGO = 'argon2'; + public const DEFAULT_ALGO_OPTIONS = ['memory_cost' => 2048, 'time_cost' => 4, 'threads' => 3]; /** * User Roles. @@ -154,17 +153,17 @@ class Auth public static function passwordHash(string $string, string $algo, mixed $options = []) { // Plain text not supported, just an alias. Switch to recommended algo - if($algo === 'plaintext') { + if ($algo === 'plaintext') { $algo = Auth::DEFAULT_ALGO; $options = Auth::DEFAULT_ALGO_OPTIONS; } - if(!\array_key_exists($algo, Auth::SUPPORTED_ALGOS)) { + if (!\array_key_exists($algo, Auth::SUPPORTED_ALGOS)) { throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.'); } $className = Auth::SUPPORTED_ALGOS[$algo]; - $classPath = '\\Appwrite\\Auth\\Hash\\'.$className; + $classPath = '\\Appwrite\\Auth\\Hash\\' . $className; $hasher = new $classPath($options); $hash = $hasher->hash($string); @@ -184,13 +183,13 @@ class Auth public static function passwordVerify(string $plain, string $hash, string $algo, mixed $options = []) { // Plain text not supported, just an alias. Switch to recommended algo - if($algo === 'plaintext') { + if ($algo === 'plaintext') { $algo = Auth::DEFAULT_ALGO; $options = Auth::DEFAULT_ALGO_OPTIONS; } $className = Auth::SUPPORTED_ALGOS[$algo]; - $classPath = '\\Appwrite\\Auth\\Hash\\'.$className; + $classPath = '\\Appwrite\\Auth\\Hash\\' . $className; $hasher = new $classPath($options); $verify = $hasher->verify($plain, $hash); diff --git a/src/Appwrite/Auth/Hash.php b/src/Appwrite/Auth/Hash.php index f107fac41d..bf2ab04808 100644 --- a/src/Appwrite/Auth/Hash.php +++ b/src/Appwrite/Auth/Hash.php @@ -8,36 +8,39 @@ abstract class Hash * @var mixed $options Hashing-algo specific options */ protected mixed $options = []; - + /** * @param mixed $options Hashing-algo specific options */ - public function __construct(mixed $options = []) { + public function __construct(mixed $options = []) + { $this->setOptions($options); } /** * Set hashing algo options - * + * * @param mixed $options Hashing-algo specific options */ - public function setOptions(mixed $options): self { + public function setOptions(mixed $options): self + { $this->options = \array_merge([], $this->getDefaultOptions(), $options); return $this; } /** * Get hashing algo options - * + * * @return mixed $options Hashing-algo specific options */ - public function getOptions(): mixed { + public function getOptions(): mixed + { return $this->options; } /** * @param string $password Input password to hash - * + * * @return string hash */ abstract public function hash(string $password): string; @@ -45,14 +48,14 @@ abstract class Hash /** * @param string $password Input password to validate * @param string $hash Hash to verify password against - * + * * @return boolean true if password matches hash */ abstract public function verify(string $password, string $hash): bool; /** * Get default options for specific hashing algo - * + * * @return mixed options named array */ abstract public function getDefaultOptions(): mixed; diff --git a/src/Appwrite/Auth/Hash/Argon2.php b/src/Appwrite/Auth/Hash/Argon2.php index 26fe4fc97e..90fea0b871 100644 --- a/src/Appwrite/Auth/Hash/Argon2.php +++ b/src/Appwrite/Auth/Hash/Argon2.php @@ -9,36 +9,39 @@ use Appwrite\Auth\Hash; * int threads * int time_cost * int memory_cost - * + * * Refference: https://www.php.net/manual/en/function.password-hash.php#example-983 */ class Argon2 extends Hash { /** * @param string $password Input password to hash - * + * * @return string hash */ - public function hash(string $password): string { + public function hash(string $password): string + { return \password_hash($password, PASSWORD_ARGON2ID, $this->getOptions()); } /** * @param string $password Input password to validate * @param string $hash Hash to verify password against - * + * * @return boolean true if password matches hash */ - public function verify(string $password, string $hash): bool { + public function verify(string $password, string $hash): bool + { return \password_verify($password, $hash); } /** * Get default options for specific hashing algo - * + * * @return mixed options named array */ - public function getDefaultOptions(): mixed { + public function getDefaultOptions(): mixed + { return ['memory_cost' => 2048, 'time_cost' => 4, 'threads' => 3]; } -} \ No newline at end of file +} diff --git a/src/Appwrite/Auth/Hash/Bcrypt.php b/src/Appwrite/Auth/Hash/Bcrypt.php index 46d1841a3e..71cd437231 100644 --- a/src/Appwrite/Auth/Hash/Bcrypt.php +++ b/src/Appwrite/Auth/Hash/Bcrypt.php @@ -8,36 +8,39 @@ use Appwrite\Auth\Hash; * BCrypt accepted options: * int cost * string? salt; auto-generated if empty - * + * * Refference: https://www.php.net/manual/en/password.constants.php */ class Bcrypt extends Hash { /** * @param string $password Input password to hash - * + * * @return string hash */ - public function hash(string $password): string { + public function hash(string $password): string + { return \password_hash($password, PASSWORD_BCRYPT, $this->getOptions()); } /** * @param string $password Input password to validate * @param string $hash Hash to verify password against - * + * * @return boolean true if password matches hash */ - public function verify(string $password, string $hash): bool { + public function verify(string $password, string $hash): bool + { return \password_verify($password, $hash); } /** * Get default options for specific hashing algo - * + * * @return mixed options named array */ - public function getDefaultOptions(): mixed { + public function getDefaultOptions(): mixed + { return [ 'cost' => 8 ]; } -} \ No newline at end of file +} diff --git a/src/Appwrite/Auth/Hash/Md5.php b/src/Appwrite/Auth/Hash/Md5.php index f3d50162b2..366de5e969 100644 --- a/src/Appwrite/Auth/Hash/Md5.php +++ b/src/Appwrite/Auth/Hash/Md5.php @@ -6,36 +6,39 @@ use Appwrite\Auth\Hash; /* * MD5 does not accept any options. - * + * * Refference: https://www.php.net/manual/en/function.md5.php */ class Md5 extends Hash { /** * @param string $password Input password to hash - * + * * @return string hash */ - public function hash(string $password): string { + public function hash(string $password): string + { return \md5($password); } /** * @param string $password Input password to validate * @param string $hash Hash to verify password against - * + * * @return boolean true if password matches hash */ - public function verify(string $password, string $hash): bool { + public function verify(string $password, string $hash): bool + { return $this->hash($password) === $hash; } - + /** * Get default options for specific hashing algo - * + * * @return mixed options named array */ - public function getDefaultOptions(): mixed { + public function getDefaultOptions(): mixed + { return []; } -} \ No newline at end of file +} diff --git a/src/Appwrite/Auth/Hash/Phpass.php b/src/Appwrite/Auth/Hash/Phpass.php index 0e309468a7..4ffb9db82b 100644 --- a/src/Appwrite/Auth/Hash/Phpass.php +++ b/src/Appwrite/Auth/Hash/Phpass.php @@ -1,4 +1,5 @@ 8, 'portable_hashes' => false, 'random_state' => $randomState]; } - /** + /** * @param string $password Input password to hash - * + * * @return string hash */ - public function hash(string $password): string - { - $options = $this->getDefaultOptions(); + public function hash(string $password): string + { + $options = $this->getDefaultOptions(); - $random = ''; - if (CRYPT_BLOWFISH === 1 && !$options['portable_hashes']) { - $random = $this->get_random_bytes(16, $options); - $hash = crypt($password, $this->gensalt_blowfish($random, $options)); - if (strlen($hash) === 60) { - return $hash; - } - } - if (strlen($random) < 6) { - $random = $this->get_random_bytes(6, $options); - } - $hash = $this->crypt_private($password, $this->gensalt_private($random, $options)); - if (strlen($hash) === 34) { - return $hash; - } + $random = ''; + if (CRYPT_BLOWFISH === 1 && !$options['portable_hashes']) { + $random = $this->getRandomBytes(16, $options); + $hash = crypt($password, $this->gensaltBlowfish($random, $options)); + if (strlen($hash) === 60) { + return $hash; + } + } + if (strlen($random) < 6) { + $random = $this->getRandomBytes(6, $options); + } + $hash = $this->cryptPrivate($password, $this->gensaltPrivate($random, $options)); + if (strlen($hash) === 34) { + return $hash; + } - /** - * Returning '*' on error is safe here, but would _not_ be safe - * in a crypt(3)-like function used _both_ for generating new - * hashes and for validating passwords against existing hashes. - */ - return '*'; - } + /** + * Returning '*' on error is safe here, but would _not_ be safe + * in a crypt(3)-like function used _both_ for generating new + * hashes and for validating passwords against existing hashes. + */ + return '*'; + } /** * @param string $password Input password to validate * @param string $hash Hash to verify password against - * + * * @return boolean true if password matches hash */ public function verify(string $password, string $hash): bool - { - $hash = $this->crypt_private($password, $hash); - if ($hash[0] === '*') { - $hash = crypt($password, $hash); - } + { + $hash = $this->cryptPrivate($password, $hash); + if ($hash[0] === '*') { + $hash = crypt($password, $hash); + } - /** - * This is not constant-time. In order to keep the code simple, - * for timing safety we currently rely on the salts being - * unpredictable, which they are at least in the non-fallback - * cases (that is, when we use /dev/urandom and bcrypt). - */ - return $hash === $hash; - } + /** + * This is not constant-time. In order to keep the code simple, + * for timing safety we currently rely on the salts being + * unpredictable, which they are at least in the non-fallback + * cases (that is, when we use /dev/urandom and bcrypt). + */ + return $hash === $hash; + } - /** - * @param int $count - * - * @return String $output - * @since 0.1.0 - * @throws Exception Thows an Exception if the $count parameter is not a positive integer. - */ - protected function get_random_bytes($count, $options) - { - if (!is_int($count) || $count < 1) { - throw new \Exception('Argument count must be a positive integer'); - } - $output = ''; - if (@is_readable('/dev/urandom') && ($fh = @fopen('/dev/urandom', 'rb'))) { - $output = fread($fh, $count); - fclose($fh); - } + /** + * @param int $count + * + * @return String $output + * @since 0.1.0 + * @throws Exception Thows an Exception if the $count parameter is not a positive integer. + */ + protected function getRandomBytes($count, $options) + { + if (!is_int($count) || $count < 1) { + throw new \Exception('Argument count must be a positive integer'); + } + $output = ''; + if (@is_readable('/dev/urandom') && ($fh = @fopen('/dev/urandom', 'rb'))) { + $output = fread($fh, $count); + fclose($fh); + } - if (strlen($output) < $count) { - $output = ''; + if (strlen($output) < $count) { + $output = ''; - for ($i = 0; $i < $count; $i += 16) { - $options['iteration_count_log2'] = md5(microtime() . $options['iteration_count_log2']); - $output .= md5($options['iteration_count_log2'], TRUE); - } + for ($i = 0; $i < $count; $i += 16) { + $options['iteration_count_log2'] = md5(microtime() . $options['iteration_count_log2']); + $output .= md5($options['iteration_count_log2'], true); + } - $output = substr($output, 0, $count); - } + $output = substr($output, 0, $count); + } - return $output; - } + return $output; + } - /** - * @param String $input - * @param int $count - * - * @return String $output - * @since 0.1.0 - * @throws Exception Thows an Exception if the $count parameter is not a positive integer. - */ - protected function encode64($input, $count) - { - if (!is_int($count) || $count < 1) { - throw new \Exception('Argument count must be a positive integer'); - } - $output = ''; - $i = 0; - do { - $value = ord($input[$i++]); - $output .= $this->itoa64[$value & 0x3f]; - if ($i < $count) { - $value |= ord($input[$i]) << 8; - } - $output .= $this->itoa64[($value >> 6) & 0x3f]; - if ($i++ >= $count) { - break; - } - if ($i < $count) { - $value |= ord($input[$i]) << 16; - } - $output .= $this->itoa64[($value >> 12) & 0x3f]; - if ($i++ >= $count) { - break; - } - $output .= $this->itoa64[($value >> 18) & 0x3f]; - } while ($i < $count); + /** + * @param String $input + * @param int $count + * + * @return String $output + * @since 0.1.0 + * @throws Exception Thows an Exception if the $count parameter is not a positive integer. + */ + protected function encode64($input, $count) + { + if (!is_int($count) || $count < 1) { + throw new \Exception('Argument count must be a positive integer'); + } + $output = ''; + $i = 0; + do { + $value = ord($input[$i++]); + $output .= $this->itoa64[$value & 0x3f]; + if ($i < $count) { + $value |= ord($input[$i]) << 8; + } + $output .= $this->itoa64[($value >> 6) & 0x3f]; + if ($i++ >= $count) { + break; + } + if ($i < $count) { + $value |= ord($input[$i]) << 16; + } + $output .= $this->itoa64[($value >> 12) & 0x3f]; + if ($i++ >= $count) { + break; + } + $output .= $this->itoa64[($value >> 18) & 0x3f]; + } while ($i < $count); - return $output; - } + return $output; + } - /** - * @param String $input - * - * @return String $output - * @since 0.1.0 - */ - private function gensalt_private($input, $options) - { - $output = '$P$'; - $output .= $this->itoa64[min($options['iteration_count_log2'] + ((PHP_VERSION >= '5') ? 5 : 3), 30)]; - $output .= $this->encode64($input, 6); + /** + * @param String $input + * + * @return String $output + * @since 0.1.0 + */ + private function gensaltPrivate($input, $options) + { + $output = '$P$'; + $output .= $this->itoa64[min($options['iteration_count_log2'] + ((PHP_VERSION >= '5') ? 5 : 3), 30)]; + $output .= $this->encode64($input, 6); - return $output; - } + return $output; + } - /** - * @param String $password - * @param String $setting - * - * @return String $output - * @since 0.1.0 - */ - private function crypt_private($password, $setting) - { - $output = '*0'; - if (substr($setting, 0, 2) === $output) { - $output = '*1'; - } - $id = substr($setting, 0, 3); - // We use "$P$", phpBB3 uses "$H$" for the same thing - if ($id !== '$P$' && $id !== '$H$') { - return $output; - } - $count_log2 = strpos($this->itoa64, $setting[3]); - if ($count_log2 < 7 || $count_log2 > 30) { - return $output; - } - $count = 1 << $count_log2; - $salt = substr($setting, 4, 8); - if (strlen($salt) !== 8) { - return $output; - } - /** - * We were kind of forced to use MD5 here since it's the only - * cryptographic primitive that was available in all versions of PHP - * in use. To implement our own low-level crypto in PHP - * would have result in much worse performance and - * consequently in lower iteration counts and hashes that are - * quicker to crack (by non-PHP code). - */ - $hash = md5($salt . $password, TRUE); - do { - $hash = md5($hash . $password, TRUE); - } while (--$count); - $output = substr($setting, 0, 12); - $output .= $this->encode64($hash, 16); + /** + * @param String $password + * @param String $setting + * + * @return String $output + * @since 0.1.0 + */ + private function cryptPrivate($password, $setting) + { + $output = '*0'; + if (substr($setting, 0, 2) === $output) { + $output = '*1'; + } + $id = substr($setting, 0, 3); + // We use "$P$", phpBB3 uses "$H$" for the same thing + if ($id !== '$P$' && $id !== '$H$') { + return $output; + } + $count_log2 = strpos($this->itoa64, $setting[3]); + if ($count_log2 < 7 || $count_log2 > 30) { + return $output; + } + $count = 1 << $count_log2; + $salt = substr($setting, 4, 8); + if (strlen($salt) !== 8) { + return $output; + } + /** + * We were kind of forced to use MD5 here since it's the only + * cryptographic primitive that was available in all versions of PHP + * in use. To implement our own low-level crypto in PHP + * would have result in much worse performance and + * consequently in lower iteration counts and hashes that are + * quicker to crack (by non-PHP code). + */ + $hash = md5($salt . $password, true); + do { + $hash = md5($hash . $password, true); + } while (--$count); + $output = substr($setting, 0, 12); + $output .= $this->encode64($hash, 16); - return $output; - } + return $output; + } - /** - * @param String $input - * - * @return String $output - * @since 0.1.0 - */ - private function gensalt_blowfish($input, $options) - { - /** - * This one needs to use a different order of characters and a - * different encoding scheme from the one in encode64() above. - * We care because the last character in our encoded string will - * only represent 2 bits. While two known implementations of - * bcrypt will happily accept and correct a salt string which - * has the 4 unused bits set to non-zero, we do not want to take - * chances and we also do not want to waste an additional byte - * of entropy. - */ - $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - $output = '$2a$'; - $output .= chr(ord('0') + $options['iteration_count_log2'] / 10); - $output .= chr(ord('0') + $options['iteration_count_log2'] % 10); - $output .= '$'; - $i = 0; - do { - $c1 = ord($input[$i++]); - $output .= $itoa64[$c1 >> 2]; - $c1 = ($c1 & 0x03) << 4; - if ($i >= 16) { - $output .= $itoa64[$c1]; - break; - } - $c2 = ord($input[$i++]); - $c1 |= $c2 >> 4; - $output .= $itoa64[$c1]; - $c1 = ($c2 & 0x0f) << 2; - $c2 = ord($input[$i++]); - $c1 |= $c2 >> 6; - $output .= $itoa64[$c1]; - $output .= $itoa64[$c2 & 0x3f]; - } while (1); + /** + * @param String $input + * + * @return String $output + * @since 0.1.0 + */ + private function gensaltBlowfish($input, $options) + { + /** + * This one needs to use a different order of characters and a + * different encoding scheme from the one in encode64() above. + * We care because the last character in our encoded string will + * only represent 2 bits. While two known implementations of + * bcrypt will happily accept and correct a salt string which + * has the 4 unused bits set to non-zero, we do not want to take + * chances and we also do not want to waste an additional byte + * of entropy. + */ + $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + $output = '$2a$'; + $output .= chr(ord('0') + $options['iteration_count_log2'] / 10); + $output .= chr(ord('0') + $options['iteration_count_log2'] % 10); + $output .= '$'; + $i = 0; + do { + $c1 = ord($input[$i++]); + $output .= $itoa64[$c1 >> 2]; + $c1 = ($c1 & 0x03) << 4; + if ($i >= 16) { + $output .= $itoa64[$c1]; + break; + } + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 4; + $output .= $itoa64[$c1]; + $c1 = ($c2 & 0x0f) << 2; + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 6; + $output .= $itoa64[$c1]; + $output .= $itoa64[$c2 & 0x3f]; + } while (1); - return $output; - } -} \ No newline at end of file + return $output; + } +} diff --git a/src/Appwrite/Auth/Hash/Scrypt.php b/src/Appwrite/Auth/Hash/Scrypt.php index 471bfa1ce7..c29a486915 100644 --- a/src/Appwrite/Auth/Hash/Scrypt.php +++ b/src/Appwrite/Auth/Hash/Scrypt.php @@ -11,17 +11,18 @@ use Appwrite\Auth\Hash; * int cost_memory * int cost_parallel * int length - * + * * Refference: https://github.com/DomBlack/php-scrypt/blob/master/scrypt.php#L112-L116 */ class Scrypt extends Hash { /** * @param string $password Input password to hash - * + * * @return string hash */ - public function hash(string $password): string { + public function hash(string $password): string + { $options = $this->getOptions(); return \scrypt($password, $options['salt'] ?? null, $options['cost_cpu'], $options['cost_memory'], $options['cost_parallel'], $options['length']); @@ -30,19 +31,21 @@ class Scrypt extends Hash /** * @param string $password Input password to validate * @param string $hash Hash to verify password against - * + * * @return boolean true if password matches hash */ - public function verify(string $password, string $hash): bool { + public function verify(string $password, string $hash): bool + { return $hash === $this->hash($password); } /** * Get default options for specific hashing algo - * + * * @return mixed options named array */ - public function getDefaultOptions(): mixed { + public function getDefaultOptions(): mixed + { return [ 'cost_cpu' => 8, 'cost_memory' => 14, 'cost_parallel' => 1, 'length' => 64 ]; } -} \ No newline at end of file +} diff --git a/src/Appwrite/Auth/Hash/Scryptmodified.php b/src/Appwrite/Auth/Hash/Scryptmodified.php index c360434406..a9d04b33d4 100644 --- a/src/Appwrite/Auth/Hash/Scryptmodified.php +++ b/src/Appwrite/Auth/Hash/Scryptmodified.php @@ -6,21 +6,22 @@ use Appwrite\Auth\Hash; /* * This is SCrypt hash with some additional steps added by Google. - * + * * string salt * string salt_separator * strin signer_key - * + * * Refference: https://github.com/DomBlack/php-scrypt/blob/master/scrypt.php#L112-L116 */ class Scryptmodified extends Hash { /** * @param string $password Input password to hash - * + * * @return string hash */ - public function hash(string $password): string { + public function hash(string $password): string + { $options = $this->getOptions(); $derivedKeyBytes = $this->generateDerivedKey($password); @@ -34,34 +35,38 @@ class Scryptmodified extends Hash /** * @param string $password Input password to validate * @param string $hash Hash to verify password against - * + * * @return boolean true if password matches hash */ - public function verify(string $password, string $hash): bool { + public function verify(string $password, string $hash): bool + { return $this->hash($password) === $hash; } /** * Get default options for specific hashing algo - * + * * @return mixed options named array */ - public function getDefaultOptions(): mixed { + public function getDefaultOptions(): mixed + { return [ ]; } - private function generateDerivedKey(string $password) { + private function generateDerivedKey(string $password) + { $options = $this->getOptions(); $saltBytes = \base64_decode($options['salt']); $saltSeparatorBytes = \base64_decode($options['salt_separator']); $derivedKey = \scrypt(\utf8_encode($password), $saltBytes . $saltSeparatorBytes, 16384, 8, 1, 64, true); - + return $derivedKey; } - private function hashKeys($signerKeyBytes, $derivedKeyBytes): string { + private function hashKeys($signerKeyBytes, $derivedKeyBytes): string + { $key = \substr($derivedKeyBytes, 0, 32); $iv = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; @@ -70,4 +75,4 @@ class Scryptmodified extends Hash return $hash; } -} \ No newline at end of file +} diff --git a/src/Appwrite/Auth/Hash/Sha.php b/src/Appwrite/Auth/Hash/Sha.php index b9373aa847..1c96057737 100644 --- a/src/Appwrite/Auth/Hash/Sha.php +++ b/src/Appwrite/Auth/Hash/Sha.php @@ -10,17 +10,18 @@ use Appwrite\Auth\Hash; * - Version 1: sha1 * - Version 2: sha224, sha256, sha384, sha512/224, sha512/256, sha512 * - Version 3: sha3-224, sha3-256, sha3-384, sha3-512 - * + * * Refference: https://www.php.net/manual/en/function.hash-algos.php */ class Sha extends Hash { /** * @param string $password Input password to hash - * + * * @return string hash */ - public function hash(string $password): string { + public function hash(string $password): string + { $algo = $this->getOptions()['version']; return \hash($algo, $password); @@ -29,19 +30,21 @@ class Sha extends Hash /** * @param string $password Input password to validate * @param string $hash Hash to verify password against - * + * * @return boolean true if password matches hash */ - public function verify(string $password, string $hash): bool { + public function verify(string $password, string $hash): bool + { return $this->hash($password) === $hash; } - + /** * Get default options for specific hashing algo - * + * * @return mixed options named array */ - public function getDefaultOptions(): mixed { + public function getDefaultOptions(): mixed + { return [ 'version' => 'sha3-512' ]; } -} \ No newline at end of file +} diff --git a/src/Appwrite/Utopia/Response/Model.php b/src/Appwrite/Utopia/Response/Model.php index a5c65ac4c5..b9b3953b26 100644 --- a/src/Appwrite/Utopia/Response/Model.php +++ b/src/Appwrite/Utopia/Response/Model.php @@ -96,7 +96,7 @@ abstract class Model */ protected function removeRule(string $key): self { - if(isset($this->rules[$key])) { + if (isset($this->rules[$key])) { unset($this->rules[$key]); } diff --git a/src/Appwrite/Utopia/Response/Model/Account.php b/src/Appwrite/Utopia/Response/Model/Account.php index 52b57b7cac..bfcd7ab7d8 100644 --- a/src/Appwrite/Utopia/Response/Model/Account.php +++ b/src/Appwrite/Utopia/Response/Model/Account.php @@ -9,7 +9,7 @@ class Account extends User public function __construct() { parent::__construct(); - + $this ->removeRule('password') ->removeRule('hash') @@ -21,7 +21,7 @@ class Account extends User * * @return string */ - public function getName():string + public function getName(): string { return 'Account'; } @@ -31,7 +31,7 @@ class Account extends User * * @return string */ - public function getType():string + public function getType(): string { return Response::MODEL_ACCOUNT; } diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 8f463154db..368789bc46 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -174,7 +174,7 @@ trait UsersBase /** * Tries to login into all accounts created with hashed password. Ensures hash veifying logic. - * + * * @depends testCreateUser */ public function testCreateUserSessionHashed(array $data): void diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index f4bdb658d9..7931a6165b 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -148,14 +148,15 @@ class AuthTest extends TestCase $salt = '56dFqW+kswqktw=='; $saltSeparator = 'Bw=='; $signerKey = 'XyEKE9RcTDeLEsL/RjwPDBv/RqDl8fb3gpYEOQaPihbxf1ZAtSOHCjuAAa7Q3oHpCYhXSN9tizHgVOwn6krflQ=='; - + $options = [ 'salt' => $salt, 'salt_separator' => $saltSeparator, 'signer_key' => $signerKey ]; $generatedHash = Auth::passwordHash($plain, 'scrypt_mod', $options); $this->assertEquals(true, Auth::passwordVerify($plain, $generatedHash, 'scrypt_mod', $options)); $this->assertEquals(true, Auth::passwordVerify($plain, $hash, 'scrypt_mod', $options)); } - public function testUnknownAlgo() { + public function testUnknownAlgo() + { $this->expectExceptionMessage('Hashing algorithm \'md8\' is not supported.'); // Bcrypt - Cost 5