appwrite/src/Appwrite/Auth/Auth.php

414 lines
11 KiB
PHP
Raw Normal View History

2019-05-09 06:54:39 +00:00
<?php
namespace Appwrite\Auth;
2019-05-09 06:54:39 +00:00
use Appwrite\Auth\Hash\Argon2;
use Appwrite\Auth\Hash\Bcrypt;
use Appwrite\Auth\Hash\Md5;
use Appwrite\Auth\Hash\Phpass;
use Appwrite\Auth\Hash\Scrypt;
use Appwrite\Auth\Hash\Scryptmodified;
use Appwrite\Auth\Hash\Sha;
2021-09-30 10:32:24 +00:00
use Utopia\Database\Document;
2021-10-07 19:27:23 +00:00
use Utopia\Database\Validator\Authorization;
2019-05-09 06:54:39 +00:00
class Auth
{
2022-06-14 11:08:54 +00:00
public const SUPPORTED_ALGOS = [
'argon2',
'bcrypt',
'md5',
'sha',
'phpass',
'scrypt',
'scrypt_mod',
'plaintext'
2022-05-04 14:37:37 +00:00
];
2022-06-14 11:08:54 +00:00
public const DEFAULT_ALGO = 'argon2';
public const DEFAULT_ALGO_OPTIONS = ['memory_cost' => 2048, 'time_cost' => 4, 'threads' => 3];
2022-05-04 14:37:37 +00:00
2019-05-09 06:54:39 +00:00
/**
* User Roles.
2019-05-09 06:54:39 +00:00
*/
2022-06-02 13:03:37 +00:00
public const USER_ROLE_ALL = 'all';
public const USER_ROLE_GUEST = 'guest';
public const USER_ROLE_MEMBER = 'member';
public const USER_ROLE_ADMIN = 'admin';
public const USER_ROLE_DEVELOPER = 'developer';
public const USER_ROLE_OWNER = 'owner';
public const USER_ROLE_APP = 'app';
public const USER_ROLE_SYSTEM = 'system';
2019-05-09 06:54:39 +00:00
/**
* Token Types.
2019-05-09 06:54:39 +00:00
*/
2022-06-02 13:03:37 +00:00
public const TOKEN_TYPE_LOGIN = 1; // Deprecated
public const TOKEN_TYPE_VERIFICATION = 2;
public const TOKEN_TYPE_RECOVERY = 3;
public const TOKEN_TYPE_INVITE = 4;
public const TOKEN_TYPE_MAGIC_URL = 5;
2019-05-09 06:54:39 +00:00
2021-02-19 10:02:02 +00:00
/**
* Session Providers.
*/
2022-06-02 13:03:37 +00:00
public const SESSION_PROVIDER_EMAIL = 'email';
public const SESSION_PROVIDER_ANONYMOUS = 'anonymous';
public const SESSION_PROVIDER_MAGIC_URL = 'magic-url';
2021-02-19 10:02:02 +00:00
2019-05-09 06:54:39 +00:00
/**
* Token Expiration times.
2019-05-09 06:54:39 +00:00
*/
2022-06-02 13:03:37 +00:00
public const TOKEN_EXPIRATION_LOGIN_LONG = 31536000; /* 1 year */
public const TOKEN_EXPIRATION_LOGIN_SHORT = 3600; /* 1 hour */
public const TOKEN_EXPIRATION_RECOVERY = 3600; /* 1 hour */
public const TOKEN_EXPIRATION_CONFIRM = 3600 * 24 * 7; /* 7 days */
2019-05-09 06:54:39 +00:00
/**
* @var string
*/
public static $cookieName = 'a_session';
2019-05-09 06:54:39 +00:00
/**
* User Unique ID.
2019-05-09 06:54:39 +00:00
*
2020-11-18 22:08:54 +00:00
* @var string
2019-05-09 06:54:39 +00:00
*/
2020-11-18 22:08:54 +00:00
public static $unique = '';
2019-05-09 06:54:39 +00:00
/**
* User Secret Key.
2019-05-09 06:54:39 +00:00
*
* @var string
*/
public static $secret = '';
2019-05-09 06:54:39 +00:00
/**
* Set Cookie Name.
2019-05-09 06:54:39 +00:00
*
* @param $string
*
2019-05-09 06:54:39 +00:00
* @return string
*/
public static function setCookieName($string)
2019-05-09 06:54:39 +00:00
{
return self::$cookieName = $string;
}
/**
* Encode Session.
2019-05-09 06:54:39 +00:00
*
2020-11-18 22:08:54 +00:00
* @param string $id
2019-05-09 06:54:39 +00:00
* @param string $secret
*
2019-05-09 06:54:39 +00:00
* @return string
*/
public static function encodeSession($id, $secret)
2019-05-09 06:54:39 +00:00
{
return \base64_encode(\json_encode([
2019-05-09 06:54:39 +00:00
'id' => $id,
'secret' => $secret,
]));
}
/**
* Decode Session.
2019-05-09 06:54:39 +00:00
*
* @param string $session
*
2019-05-09 06:54:39 +00:00
* @return array
*
2019-05-09 06:54:39 +00:00
* @throws \Exception
*/
public static function decodeSession($session)
2019-05-09 06:54:39 +00:00
{
$session = \json_decode(\base64_decode($session), true);
$default = ['id' => null, 'secret' => ''];
2019-05-09 06:54:39 +00:00
if (!\is_array($session)) {
2019-05-09 06:54:39 +00:00
return $default;
}
return \array_merge($default, $session);
2019-05-09 06:54:39 +00:00
}
/**
* Encode.
2019-05-09 06:54:39 +00:00
*
* One-way encryption
*
* @param $string
*
2019-05-09 06:54:39 +00:00
* @return string
*/
public static function hash(string $string)
2019-05-09 06:54:39 +00:00
{
return \hash('sha256', $string);
2019-05-09 06:54:39 +00:00
}
/**
* Password Hash.
2019-05-09 06:54:39 +00:00
*
* One way string hashing for user passwords
*
* @param string $string
* @param string $algo hashing algorithm to use
* @param string $options algo-specific options
*
* @return bool|string|null
2019-05-09 06:54:39 +00:00
*/
public static function passwordHash(string $string, string $algo, mixed $options = [])
2019-05-09 06:54:39 +00:00
{
2022-05-05 11:53:27 +00:00
// Plain text not supported, just an alias. Switch to recommended algo
2022-06-14 11:08:54 +00:00
if ($algo === 'plaintext') {
2022-05-05 11:53:27 +00:00
$algo = Auth::DEFAULT_ALGO;
$options = Auth::DEFAULT_ALGO_OPTIONS;
}
if (!\in_array($algo, Auth::SUPPORTED_ALGOS)) {
2022-05-05 11:53:27 +00:00
throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.');
}
switch ($algo) {
case 'argon2':
$hasher = new Argon2($options);
return $hasher->hash($string);
break;
case 'bcrypt':
$hasher = new Bcrypt($options);
return $hasher->hash($string);
break;
case 'md5':
$hasher = new Md5($options);
return $hasher->hash($string);
break;
case 'sha':
$hasher = new Sha($options);
return $hasher->hash($string);
break;
case 'phpass':
$hasher = new Phpass($options);
return $hasher->hash($string);
break;
case 'scrypt':
$hasher = new Scrypt($options);
return $hasher->hash($string);
break;
case 'scrypt_mod':
$hasher = new Scryptmodified($options);
return $hasher->hash($string);
break;
default:
throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.');
}
2019-05-09 06:54:39 +00:00
}
/**
* Password verify.
2019-05-09 06:54:39 +00:00
*
* @param string $plain
* @param string $hash
* @param string $algo hashing algorithm used to hash
* @param string $options algo-specific options
*
2019-05-09 06:54:39 +00:00
* @return bool
*/
public static function passwordVerify(string $plain, string $hash, string $algo, mixed $options = [])
2019-05-09 06:54:39 +00:00
{
2022-05-05 11:53:27 +00:00
// Plain text not supported, just an alias. Switch to recommended algo
2022-06-14 11:08:54 +00:00
if ($algo === 'plaintext') {
2022-05-05 11:53:27 +00:00
$algo = Auth::DEFAULT_ALGO;
$options = Auth::DEFAULT_ALGO_OPTIONS;
}
if (!\in_array($algo, Auth::SUPPORTED_ALGOS)) {
throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.');
}
2022-05-05 11:53:27 +00:00
switch ($algo) {
case 'argon2':
$hasher = new Argon2($options);
return $hasher->verify($plain, $hash);
break;
case 'bcrypt':
$hasher = new Bcrypt($options);
return $hasher->verify($plain, $hash);
break;
case 'md5':
$hasher = new Md5($options);
return $hasher->verify($plain, $hash);
break;
case 'sha':
$hasher = new Sha($options);
return $hasher->verify($plain, $hash);
break;
case 'phpass':
$hasher = new Phpass($options);
return $hasher->verify($plain, $hash);
break;
case 'scrypt':
$hasher = new Scrypt($options);
return $hasher->verify($plain, $hash);
break;
case 'scrypt_mod':
$hasher = new Scryptmodified($options);
return $hasher->verify($plain, $hash);
break;
default:
throw new \Exception('Hashing algorithm \'' . $algo . '\' is not supported.');
}
2019-05-09 06:54:39 +00:00
}
/**
* Password Generator.
2019-05-09 06:54:39 +00:00
*
* Generate random password string
*
* @param int $length
*
2019-05-09 06:54:39 +00:00
* @return string
*
2019-05-09 06:54:39 +00:00
* @throws \Exception
*/
2022-05-23 14:54:50 +00:00
public static function passwordGenerator(int $length = 20): string
2019-05-09 06:54:39 +00:00
{
return \bin2hex(\random_bytes($length));
2019-05-09 06:54:39 +00:00
}
/**
* Token Generator.
2019-05-09 06:54:39 +00:00
*
* Generate random password string
*
* @param int $length
*
2019-05-09 06:54:39 +00:00
* @return string
*
2019-05-09 06:54:39 +00:00
* @throws \Exception
*/
2022-05-23 14:54:50 +00:00
public static function tokenGenerator(int $length = 128): string
2019-05-09 06:54:39 +00:00
{
return \bin2hex(\random_bytes($length));
2019-05-09 06:54:39 +00:00
}
/**
* Verify token and check that its not expired.
2019-05-09 06:54:39 +00:00
*
* @param array $tokens
* @param int $type
2019-05-09 06:54:39 +00:00
* @param string $secret
*
2019-12-28 16:37:39 +00:00
* @return bool|string
2019-05-09 06:54:39 +00:00
*/
public static function tokenVerify(array $tokens, int $type, string $secret)
2019-05-09 06:54:39 +00:00
{
foreach ($tokens as $token) { /** @var Document $token */
2022-05-23 14:54:50 +00:00
if (
$token->isSet('type') &&
$token->isSet('secret') &&
$token->isSet('expire') &&
$token->getAttribute('type') == $type &&
$token->getAttribute('secret') === self::hash($secret) &&
2022-05-23 14:54:50 +00:00
$token->getAttribute('expire') >= \time()
) {
return (string)$token->getId();
2019-05-09 06:54:39 +00:00
}
}
return false;
}
2021-02-19 12:12:47 +00:00
/**
* Verify session and check that its not expired.
*
* @param array $sessions
* @param string $secret
*
* @return bool|string
*/
public static function sessionVerify(array $sessions, string $secret)
{
foreach ($sessions as $session) { /** @var Document $session */
2022-05-23 14:54:50 +00:00
if (
$session->isSet('secret') &&
2021-02-19 12:12:47 +00:00
$session->isSet('expire') &&
$session->isSet('provider') &&
$session->getAttribute('secret') === self::hash($secret) &&
2022-05-23 14:54:50 +00:00
$session->getAttribute('expire') >= \time()
) {
2021-02-19 12:12:47 +00:00
return (string)$session->getId();
}
}
return false;
}
/**
* Is Privileged User?
2021-10-05 19:23:04 +00:00
*
* @param array $roles
2021-10-05 19:23:04 +00:00
*
* @return bool
*/
2021-03-01 21:04:53 +00:00
public static function isPrivilegedUser(array $roles): bool
{
2021-10-05 19:23:04 +00:00
if (
2022-05-23 14:54:50 +00:00
in_array('role:' . self::USER_ROLE_OWNER, $roles) ||
in_array('role:' . self::USER_ROLE_DEVELOPER, $roles) ||
in_array('role:' . self::USER_ROLE_ADMIN, $roles)
) {
return true;
}
return false;
}
/**
* Is App User?
2021-10-05 19:23:04 +00:00
*
* @param array $roles
2021-10-05 19:23:04 +00:00
*
* @return bool
*/
public static function isAppUser(array $roles): bool
{
2022-05-23 14:54:50 +00:00
if (in_array('role:' . self::USER_ROLE_APP, $roles)) {
return true;
}
return false;
}
/**
* Returns all roles for a user.
2021-10-05 19:23:04 +00:00
*
* @param Document $user
* @return array
*/
public static function getRoles(Document $user): array
{
2021-09-03 15:15:42 +00:00
$roles = [];
if (!self::isPrivilegedUser(Authorization::getRoles()) && !self::isAppUser(Authorization::getRoles())) {
2021-09-03 15:07:09 +00:00
if ($user->getId()) {
2022-05-23 14:54:50 +00:00
$roles[] = 'user:' . $user->getId();
$roles[] = 'role:' . Auth::USER_ROLE_MEMBER;
2021-09-03 15:07:09 +00:00
} else {
2022-05-23 14:54:50 +00:00
return ['role:' . Auth::USER_ROLE_GUEST];
2021-09-03 15:07:09 +00:00
}
}
foreach ($user->getAttribute('memberships', []) as $node) {
if (isset($node['teamId']) && isset($node['roles'])) {
$roles[] = 'team:' . $node['teamId'];
foreach ($node['roles'] as $nodeRole) { // Set all team roles
$roles[] = 'team:' . $node['teamId'] . '/' . $nodeRole;
}
}
}
return $roles;
}
}