Merge branch 'appwrite:master' into feat-etsy-auth

This commit is contained in:
Wess Cope
2022-07-14 11:05:27 -04:00
committed by GitHub
4319 changed files with 96155 additions and 45220 deletions
+70 -35
View File
@@ -10,38 +10,41 @@ class Auth
/**
* User Roles.
*/
const USER_ROLE_ALL = 'all';
const USER_ROLE_GUEST = 'guest';
const USER_ROLE_MEMBER = 'member';
const USER_ROLE_ADMIN = 'admin';
const USER_ROLE_DEVELOPER = 'developer';
const USER_ROLE_OWNER = 'owner';
const USER_ROLE_APP = 'app';
const USER_ROLE_SYSTEM = 'system';
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';
/**
* Token Types.
*/
const TOKEN_TYPE_LOGIN = 1; // Deprecated
const TOKEN_TYPE_VERIFICATION = 2;
const TOKEN_TYPE_RECOVERY = 3;
const TOKEN_TYPE_INVITE = 4;
const TOKEN_TYPE_MAGIC_URL = 5;
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;
public const TOKEN_TYPE_PHONE = 6;
/**
* Session Providers.
*/
const SESSION_PROVIDER_EMAIL = 'email';
const SESSION_PROVIDER_ANONYMOUS = 'anonymous';
const SESSION_PROVIDER_MAGIC_URL = 'magic-url';
public const SESSION_PROVIDER_EMAIL = 'email';
public const SESSION_PROVIDER_ANONYMOUS = 'anonymous';
public const SESSION_PROVIDER_MAGIC_URL = 'magic-url';
public const SESSION_PROVIDER_PHONE = 'phone';
/**
* Token Expiration times.
*/
const TOKEN_EXPIRATION_LOGIN_LONG = 31536000; /* 1 year */
const TOKEN_EXPIRATION_LOGIN_SHORT = 3600; /* 1 hour */
const TOKEN_EXPIRATION_RECOVERY = 3600; /* 1 hour */
const TOKEN_EXPIRATION_CONFIRM = 3600 * 24 * 7; /* 7 days */
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 */
public const TOKEN_EXPIRATION_PHONE = 60 * 15; /* 15 minutes */
/**
* @var string
@@ -163,7 +166,7 @@ class Auth
*
* @throws \Exception
*/
public static function passwordGenerator(int $length = 20):string
public static function passwordGenerator(int $length = 20): string
{
return \bin2hex(\random_bytes($length));
}
@@ -179,7 +182,7 @@ class Auth
*
* @throws \Exception
*/
public static function tokenGenerator(int $length = 128):string
public static function tokenGenerator(int $length = 128): string
{
return \bin2hex(\random_bytes($length));
}
@@ -195,13 +198,16 @@ class Auth
*/
public static function tokenVerify(array $tokens, int $type, string $secret)
{
foreach ($tokens as $token) { /** @var Document $token */
if ($token->isSet('type') &&
foreach ($tokens as $token) {
/** @var Document $token */
if (
$token->isSet('type') &&
$token->isSet('secret') &&
$token->isSet('expire') &&
$token->getAttribute('type') == $type &&
$token->getAttribute('secret') === self::hash($secret) &&
$token->getAttribute('expire') >= \time()) {
$token->getAttribute('expire') >= \time()
) {
return (string)$token->getId();
}
}
@@ -209,6 +215,25 @@ class Auth
return false;
}
public static function phoneTokenVerify(array $tokens, string $secret)
{
foreach ($tokens as $token) {
/** @var Document $token */
if (
$token->isSet('type') &&
$token->isSet('secret') &&
$token->isSet('expire') &&
$token->getAttribute('type') == Auth::TOKEN_TYPE_PHONE &&
$token->getAttribute('secret') === $secret &&
$token->getAttribute('expire') >= \time()
) {
return (string) $token->getId();
}
}
return false;
}
/**
* Verify session and check that its not expired.
*
@@ -219,12 +244,15 @@ class Auth
*/
public static function sessionVerify(array $sessions, string $secret)
{
foreach ($sessions as $session) { /** @var Document $session */
if ($session->isSet('secret') &&
foreach ($sessions as $session) {
/** @var Document $session */
if (
$session->isSet('secret') &&
$session->isSet('expire') &&
$session->isSet('provider') &&
$session->getAttribute('secret') === self::hash($secret) &&
$session->getAttribute('expire') >= \time()) {
$session->getAttribute('expire') >= \time()
) {
return (string)$session->getId();
}
}
@@ -242,9 +270,9 @@ class Auth
public static function isPrivilegedUser(array $roles): bool
{
if (
in_array('role:'.self::USER_ROLE_OWNER, $roles) ||
in_array('role:'.self::USER_ROLE_DEVELOPER, $roles) ||
in_array('role:'.self::USER_ROLE_ADMIN, $roles)
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;
}
@@ -261,7 +289,7 @@ class Auth
*/
public static function isAppUser(array $roles): bool
{
if (in_array('role:'.self::USER_ROLE_APP, $roles)) {
if (in_array('role:' . self::USER_ROLE_APP, $roles)) {
return true;
}
@@ -280,10 +308,10 @@ class Auth
if (!self::isPrivilegedUser(Authorization::getRoles()) && !self::isAppUser(Authorization::getRoles())) {
if ($user->getId()) {
$roles[] = 'user:'.$user->getId();
$roles[] = 'role:'.Auth::USER_ROLE_MEMBER;
$roles[] = 'user:' . $user->getId();
$roles[] = 'role:' . Auth::USER_ROLE_MEMBER;
} else {
return ['role:'.Auth::USER_ROLE_GUEST];
return ['role:' . Auth::USER_ROLE_GUEST];
}
}
@@ -299,4 +327,11 @@ class Auth
return $roles;
}
public static function isAnonymousUser(Document $user): bool
{
return (is_null($user->getAttribute('email'))
|| is_null($user->getAttribute('phone'))
) && is_null($user->getAttribute('password'));
}
}
+33 -27
View File
@@ -7,27 +7,27 @@ abstract class OAuth2
/**
* @var string
*/
protected $appID;
protected string $appID;
/**
* @var string
*/
protected $appSecret;
protected string $appSecret;
/**
* @var string
*/
protected $callback;
protected string $callback;
/**
* @var array
*/
protected $state;
protected array $state;
/**
* @var array
*/
protected $scopes;
protected array $scopes;
/**
* OAuth2 constructor.
@@ -52,66 +52,69 @@ abstract class OAuth2
/**
* @return string
*/
abstract public function getName():string;
abstract public function getName(): string;
/**
* @return string
*/
abstract public function getLoginURL():string;
abstract public function getLoginURL(): string;
/**
* @param string $code
*
* @return array
*/
abstract protected function getTokens(string $code):array;
abstract protected function getTokens(string $code): array;
/**
* @param string $refreshToken
*
* @return array
*/
abstract public function refreshTokens(string $refreshToken):array;
abstract public function refreshTokens(string $refreshToken): array;
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
abstract public function getUserID(string $accessToken):string;
abstract public function getUserEmail(string $accessToken): string;
/**
* @param $accessToken
* Check if the OAuth email is verified
*
* @param string $accessToken
*
* @return bool
*/
abstract public function isEmailVerified(string $accessToken): bool;
/**
* @param string $accessToken
*
* @return string
*/
abstract public function getUserEmail(string $accessToken):string;
/**
* @param $accessToken
*
* @return string
*/
abstract public function getUserName(string $accessToken):string;
abstract public function getUserName(string $accessToken): string;
/**
* @param $scope
*
* @return $this
*/
protected function addScope(string $scope):OAuth2
protected function addScope(string $scope): OAuth2
{
// Add a scope to the scopes array if it isn't already present
if (!\in_array($scope, $this->scopes)) {
$this->scopes[] = $scope;
}
return $this;
}
/**
* @return array
*/
protected function getScopes():array
protected function getScopes(): array
{
return $this->scopes;
}
@@ -121,9 +124,10 @@ abstract class OAuth2
*
* @return string
*/
public function getAccessToken(string $code):string
public function getAccessToken(string $code): string
{
$tokens = $this->getTokens($code);
return $tokens['access_token'] ?? '';
}
@@ -132,9 +136,10 @@ abstract class OAuth2
*
* @return string
*/
public function getRefreshToken(string $code):string
public function getRefreshToken(string $code): string
{
$tokens = $this->getTokens($code);
return $tokens['refresh_token'] ?? '';
}
@@ -143,9 +148,10 @@ abstract class OAuth2
*
* @return string
*/
public function getAccessTokenExpiry(string $code):string
public function getAccessTokenExpiry(string $code): string
{
$tokens = $this->getTokens($code);
return $tokens['expires_in'] ?? '';
}
@@ -170,7 +176,7 @@ abstract class OAuth2
*
* @return string
*/
protected function request(string $method, string $url = '', array $headers = [], string $payload = ''):string
protected function request(string $method, string $url = '', array $headers = [], string $payload = ''): string
{
$ch = \curl_init($url);
@@ -183,7 +189,7 @@ abstract class OAuth2
\curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
}
$headers[] = 'Content-length: '.\strlen($payload);
$headers[] = 'Content-length: ' . \strlen($payload);
\curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// Send the request & save response to $response
+37 -33
View File
@@ -14,17 +14,17 @@ class Amazon extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
"profile"
];
@@ -37,7 +37,7 @@ class Amazon extends OAuth2
}
/**
* @param $state
* @param string $state
*
* @return array
*/
@@ -52,13 +52,13 @@ class Amazon extends OAuth2
*/
public function getLoginURL(): string
{
return 'https://www.amazon.com/ap/oa?'.\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
'redirect_uri' => $this->callback
]);
return 'https://www.amazon.com/ap/oa?' . \http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
'redirect_uri' => $this->callback
]);
}
/**
@@ -68,7 +68,7 @@ class Amazon extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded;charset=UTF-8'];
$this->tokens = \json_decode($this->request(
'POST',
@@ -92,7 +92,7 @@ class Amazon extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded;charset=UTF-8'];
$this->tokens = \json_decode($this->request(
@@ -107,7 +107,7 @@ class Amazon extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -123,11 +123,7 @@ class Amazon extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['user_id'])) {
return $user['user_id'];
}
return '';
return $user['user_id'] ?? '';
}
/**
@@ -139,11 +135,23 @@ class Amazon extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return $user['email'] ?? '';
}
return '';
/**
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Amazon sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
@@ -155,11 +163,7 @@ class Amazon extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@@ -170,7 +174,7 @@ class Amazon extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request('GET', 'https://api.amazon.com/user/profile?access_token='.\urlencode($accessToken));
$user = $this->request('GET', 'https://api.amazon.com/user/profile?access_token=' . \urlencode($accessToken));
$this->user = \json_decode($user, true);
}
return $this->user;
+44 -35
View File
@@ -13,17 +13,17 @@ class Apple extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
"name",
"email"
];
@@ -31,7 +31,7 @@ class Apple extends OAuth2
/**
* @var array
*/
protected $claims = [];
protected array $claims = [];
/**
* @return string
@@ -40,13 +40,13 @@ class Apple extends OAuth2
{
return 'apple';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://appleid.apple.com/auth/authorize?'.\http_build_query([
return 'https://appleid.apple.com/auth/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state),
@@ -63,7 +63,7 @@ class Apple extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
@@ -90,7 +90,7 @@ class Apple extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
@@ -105,7 +105,7 @@ class Apple extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -122,11 +122,7 @@ class Apple extends OAuth2
*/
public function getUserID(string $accessToken): string
{
if (isset($this->claims['sub']) && !empty($this->claims['sub'])) {
return $this->claims['sub'];
}
return '';
return $this->claims['sub'] ?? '';
}
/**
@@ -136,14 +132,25 @@ class Apple extends OAuth2
*/
public function getUserEmail(string $accessToken): string
{
if (isset($this->claims['email']) &&
!empty($this->claims['email']) &&
isset($this->claims['email_verified']) &&
$this->claims['email_verified'] === 'true') {
return $this->claims['email'];
return $this->claims['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://developer.apple.com/forums/thread/121411
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
if ($this->claims['email_verified'] ?? false) {
return true;
}
return '';
return false;
}
/**
@@ -153,17 +160,19 @@ class Apple extends OAuth2
*/
public function getUserName(string $accessToken): string
{
if (isset($this->claims['email']) &&
if (
isset($this->claims['email']) &&
!empty($this->claims['email']) &&
isset($this->claims['email_verified']) &&
$this->claims['email_verified'] === 'true') {
$this->claims['email_verified'] === 'true'
) {
return $this->claims['email'];
}
return '';
}
protected function getAppSecret():string
protected function getAppSecret(): string
{
try {
$secret = \json_decode($this->appSecret, true);
@@ -180,18 +189,18 @@ class Apple extends OAuth2
'alg' => 'ES256',
'kid' => $keyID,
];
$claims = [
'iss' => $teamID,
'iat' => \time(),
'exp' => \time() + 86400*180,
'exp' => \time() + 86400 * 180,
'aud' => 'https://appleid.apple.com',
'sub' => $bundleID,
];
$pkey = \openssl_pkey_get_private($keyfile);
$payload = $this->encode(\json_encode($headers)).'.'.$this->encode(\json_encode($claims));
$payload = $this->encode(\json_encode($headers)) . '.' . $this->encode(\json_encode($claims));
$signature = '';
@@ -201,7 +210,7 @@ class Apple extends OAuth2
return '';
}
return $payload.'.'.$this->encode($this->fromDER($signature, 64));
return $payload . '.' . $this->encode($this->fromDER($signature, 64));
}
/**
@@ -230,10 +239,10 @@ class Apple extends OAuth2
* @param string $der
* @param int $partLength
*/
protected function fromDER(string $der, int $partLength):string
protected function fromDER(string $der, int $partLength): string
{
$hex = \unpack('H*', $der)[1];
if ('30' !== \mb_substr($hex, 0, 2, '8bit')) { // SEQUENCE
throw new \RuntimeException();
}
@@ -252,7 +261,7 @@ class Apple extends OAuth2
$R = \str_pad($R, $partLength, '0', STR_PAD_LEFT);
$hex = \mb_substr($hex, 4 + $Rl * 2, null, '8bit');
if ('02' !== \mb_substr($hex, 0, 2, '8bit')) { // INTEGER
throw new \RuntimeException();
}
@@ -261,6 +270,6 @@ class Apple extends OAuth2
$S = $this->retrievePositiveInteger(\mb_substr($hex, 4, $Sl * 2, '8bit'));
$S = \str_pad($S, $partLength, '0', STR_PAD_LEFT);
return \pack('H*', $R.$S);
return \pack('H*', $R . $S);
}
}
+219
View File
@@ -0,0 +1,219 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://auth0.com/docs/api/authentication
class Auth0 extends OAuth2
{
/**
* @var array
*/
protected array $scopes = [
'openid',
'profile',
'email',
'offline_access'
];
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @return string
*/
public function getName(): string
{
return 'auth0';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://' . $this->getAuth0Domain() . '/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state),
'scope' => \implode(' ', $this->getScopes()),
'response_type' => 'code'
]);
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://' . $this->getAuth0Domain() . '/oauth/token',
$headers,
\http_build_query([
'code' => $code,
'client_id' => $this->appID,
'client_secret' => $this->getClientSecret(),
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'grant_type' => 'authorization_code'
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://' . $this->getAuth0Domain() . '/oauth/token',
$headers,
\http_build_query([
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->getClientSecret(),
'grant_type' => 'refresh_token'
])
), true);
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['sub'] ?? '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://auth0.com/docs/api/authentication?javascript#user-profile
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['email_verified'] ?? false) {
return true;
}
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['name'] ?? '';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$headers = ['Authorization: Bearer ' . \urlencode($accessToken)];
$user = $this->request('GET', 'https://' . $this->getAuth0Domain() . '/userinfo', $headers);
$this->user = \json_decode($user, true);
}
return $this->user;
}
/**
* Extracts the Client Secret from the JSON stored in appSecret
*
* @return string
*/
protected function getClientSecret(): string
{
$secret = $this->getAppSecret();
return $secret['clientSecret'] ?? '';
}
/**
* Extracts the Auth0 Domain from the JSON stored in appSecret
*
* @return string
*/
protected function getAuth0Domain(): string
{
$secret = $this->getAppSecret();
return $secret['auth0Domain'] ?? '';
}
/**
* Decode the JSON stored in appSecret
*
* @return array
*/
protected function getAppSecret(): array
{
try {
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
} catch (\Throwable $th) {
throw new \Exception('Invalid secret');
}
return $secret;
}
}
+178
View File
@@ -0,0 +1,178 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
class Autodesk extends OAuth2
{
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'user-profile:read',
];
/**
* @return string
*/
public function getName(): string
{
return 'autodesk';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://developer.api.autodesk.com/authentication/v1/authorize?' . \http_build_query([
'client_id' => $this->appID,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
'redirect_uri' => $this->callback,
'response_type' => 'code'
]);
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$response = $this->request(
'POST',
'https://developer.api.autodesk.com/authentication/v1/gettoken',
$headers,
\http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'client_secret' => $this->appSecret,
'code' => $code,
'grant_type' => 'authorization_code'
])
);
$this->tokens = \json_decode($response, true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$response = $this->request(
'POST',
'https://developer.api.autodesk.com/authentication/v1/refreshtoken',
[],
\http_build_query([
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'grant_type' => 'refresh_token',
'code' => $code,
'redirect_uri' => $this->callback,
])
);
$output = [];
\parse_str($response, $output);
$this->tokens = $output;
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['userId'] ?? '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['emailId'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://docs.github.com/en/rest/users/emails#list-email-addresses-for-the-authenticated-user
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['emailVerified'] ?? false) {
return true;
}
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['userName'] ?? '';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$headers = ['Authorization: Bearer ' . \urlencode($accessToken)];
$user = $this->request('GET', 'https://developer.api.autodesk.com/userprofile/v1/users/@me', $headers);
$this->user = \json_decode($user, true);
}
return $this->user;
}
}
+47 -32
View File
@@ -12,17 +12,17 @@ class Bitbucket extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [];
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [];
/**
* @return string
@@ -37,12 +37,12 @@ class Bitbucket extends OAuth2
*/
public function getLoginURL(): string
{
return 'https://bitbucket.org/site/oauth2/authorize?'.\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
]);
return 'https://bitbucket.org/site/oauth2/authorize?' . \http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
]);
}
/**
@@ -52,7 +52,7 @@ class Bitbucket extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
// Required as per Bitbucket Spec.
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
@@ -76,7 +76,7 @@ class Bitbucket extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
@@ -91,7 +91,7 @@ class Bitbucket extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -107,11 +107,7 @@ class Bitbucket extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['uuid'])) {
return $user['uuid'];
}
return '';
return $user['uuid'] ?? '';
}
/**
@@ -123,11 +119,25 @@ class Bitbucket extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['is_confirmed'] ?? false) {
return true;
}
return '';
return false;
}
/**
@@ -139,11 +149,7 @@ class Bitbucket extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['display_name'])) {
return $user['display_name'];
}
return '';
return $user['display_name'] ?? '';
}
/**
@@ -154,11 +160,20 @@ class Bitbucket extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request('GET', 'https://api.bitbucket.org/2.0/user?access_token='.\urlencode($accessToken));
$user = $this->request('GET', 'https://api.bitbucket.org/2.0/user?access_token=' . \urlencode($accessToken));
$this->user = \json_decode($user, true);
$email = $this->request('GET', 'https://api.bitbucket.org/2.0/user/emails?access_token='.\urlencode($accessToken));
$this->user['email'] = \json_decode($email, true)['values'][0]['email'];
$emails = $this->request('GET', 'https://api.bitbucket.org/2.0/user/emails?access_token=' . \urlencode($accessToken));
$emails = \json_decode($emails, true);
if (isset($emails['values'])) {
foreach ($emails['values'] as $email) {
if ($email['is_confirmed']) {
$this->user['email'] = $email['email'];
$this->user['is_confirmed'] = $email['is_confirmed'];
break;
}
}
}
}
return $this->user;
}
+43 -36
View File
@@ -3,43 +3,41 @@
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
use Utopia\Exception;
// Reference Material
// https://dev.bitly.com/v4_documentation.html
class Bitly extends OAuth2
{
/**
* @var string
*/
private string $endpoint = 'https://bitly.com/oauth/';
/**
* @var string
*/
private $endpoint = 'https://bitly.com/oauth/';
/**
* @var string
*/
private $resourceEndpoint = 'https://api-ssl.bitly.com/';
private string $resourceEndpoint = 'https://api-ssl.bitly.com/';
/**
* @var array
*/
protected $scopes = [];
protected array $scopes = [];
/**
* @var array
*/
protected $user = [];
protected array $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $tokens = [];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'bitly';
}
@@ -47,9 +45,9 @@ class Bitly extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return $this->endpoint . 'authorize?'.
return $this->endpoint . 'authorize?' .
\http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
@@ -64,7 +62,7 @@ class Bitly extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$response = $this->request(
'POST',
$this->resourceEndpoint . 'oauth/access_token',
@@ -91,7 +89,7 @@ class Bitly extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$response = $this->request(
'POST',
@@ -109,7 +107,7 @@ class Bitly extends OAuth2
\parse_str($response, $output);
$this->tokens = $output;
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -117,51 +115,61 @@ class Bitly extends OAuth2
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['login'])) {
return $user['login'];
}
return '';
return $user['login'] ?? '';
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['emails'])) {
return $user['emails'][0]['email'];
foreach ($user['emails'] as $email) {
if ($email['is_verified'] === true) {
return $email['email'];
}
}
}
return '';
}
/**
* @param $accessToken
* Check if the OAuth email is verified
*
* @link https://dev.bitly.com/api-reference#getUser
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
return true;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@@ -172,7 +180,7 @@ class Bitly extends OAuth2
protected function getUser(string $accessToken)
{
$headers = [
'Authorization: Bearer '. \urlencode($accessToken),
'Authorization: Bearer ' . \urlencode($accessToken),
"Accept: application/json"
];
@@ -180,7 +188,6 @@ class Bitly extends OAuth2
$this->user = \json_decode($this->request('GET', $this->resourceEndpoint . "v4/user", $headers), true);
}
return $this->user;
}
}
+32 -28
View File
@@ -12,27 +12,27 @@ class Box extends OAuth2
/**
* @var string
*/
private $endpoint = 'https://account.box.com/api/oauth2/';
private string $endpoint = 'https://account.box.com/api/oauth2/';
/**
* @var string
*/
private $resourceEndpoint = 'https://api.box.com/2.0/';
private string $resourceEndpoint = 'https://api.box.com/2.0/';
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'manage_app_users',
];
@@ -49,7 +49,7 @@ class Box extends OAuth2
*/
public function getLoginURL(): string
{
$url = $this->endpoint . 'authorize?'.
$url = $this->endpoint . 'authorize?' .
\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
@@ -68,7 +68,7 @@ class Box extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
@@ -93,7 +93,7 @@ class Box extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
@@ -108,7 +108,7 @@ class Box extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -124,11 +124,7 @@ class Box extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
@@ -140,11 +136,23 @@ class Box extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['login'])) {
return $user['login'];
}
return $user['login'] ?? '';
}
return '';
/**
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Box sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
@@ -156,11 +164,7 @@ class Box extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@@ -171,7 +175,7 @@ class Box extends OAuth2
protected function getUser(string $accessToken): array
{
$header = [
'Authorization: Bearer '.\urlencode($accessToken),
'Authorization: Bearer ' . \urlencode($accessToken),
];
if (empty($this->user)) {
$user = $this->request(
+213
View File
@@ -0,0 +1,213 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://developers.dailymotion.com/api/#authentication
class Dailymotion extends OAuth2
{
/**
* @var string
*/
private string $endpoint = 'https://api.dailymotion.com';
/**
* @var string
*/
private string $authEndpoint = 'https://www.dailymotion.com/oauth/authorize';
/**
* @var array
*/
protected array $scopes = [
'userinfo',
'email'
];
/**
* @var array
*/
protected array $fields = [
'email',
'id',
'fullname',
'verified'
];
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @return string
*/
public function getName(): string
{
return 'dailymotion';
}
/**
* @return array
*/
public function getFields(): array
{
return $this->fields;
}
/**
* @return string
*/
public function getLoginURL(): string
{
$url = $this->authEndpoint . '?' .
\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'state' => \json_encode($this->state),
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes())
]);
return $url;
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . '/oauth/token',
["Content-Type: application/x-www-form-urlencoded"],
\http_build_query([
'grant_type' => 'authorization_code',
"client_id" => $this->appID,
"client_secret" => $this->appSecret,
"redirect_uri" => $this->callback,
'code' => $code,
'scope' => \implode(' ', $this->getScopes()),
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . '/oauth/token',
['Content-Type: application/x-www-form-urlencoded'],
\http_build_query([
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
])
), true);
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
$userId = $user['id'] ?? '';
return $userId;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
$userEmail = $user['email'] ?? '';
return $userEmail;
}
/**
* Check if the OAuth email is verified
*
* @link https://developers.dailymotion.com/api/#user-fields
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
return $user['verified'] ?? false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
$username = $user['fullname'] ?? '';
return $username;
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request(
'GET',
$this->endpoint . '/user/me?fields=' . \implode(',', $this->getFields()),
['Authorization: Bearer ' . \urlencode($accessToken)],
);
$this->user = \json_decode($user, true);
}
return $this->user;
}
}
+34 -26
View File
@@ -12,22 +12,22 @@ class Discord extends OAuth2
/**
* @var string
*/
private $endpoint = 'https://discordapp.com/api';
private string $endpoint = 'https://discordapp.com/api';
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'identify',
'email'
];
@@ -45,7 +45,7 @@ class Discord extends OAuth2
*/
public function getLoginURL(): string
{
$url = $this->endpoint . '/oauth2/authorize?'.
$url = $this->endpoint . '/oauth2/authorize?' .
\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
@@ -64,7 +64,7 @@ class Discord extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . '/oauth2/token',
@@ -88,7 +88,7 @@ class Discord extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
@@ -102,7 +102,7 @@ class Discord extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -118,11 +118,7 @@ class Discord extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
@@ -134,11 +130,27 @@ class Discord extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://discord.com/developers/docs/resources/user
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['verified'] ?? false) {
return true;
}
return '';
return false;
}
/**
@@ -150,11 +162,7 @@ class Discord extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['username'])) {
return $user['username'];
}
return '';
return $user['username'] ?? '';
}
/**
@@ -168,7 +176,7 @@ class Discord extends OAuth2
$user = $this->request(
'GET',
$this->endpoint . '/users/@me',
['Authorization: Bearer '.\urlencode($accessToken)]
['Authorization: Bearer ' . \urlencode($accessToken)]
);
$this->user = \json_decode($user, true);
}
+38 -30
View File
@@ -13,17 +13,17 @@ class Dropbox extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [];
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [];
/**
* @return string
@@ -32,17 +32,17 @@ class Dropbox extends OAuth2
{
return 'dropbox';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://www.dropbox.com/oauth2/authorize?'.\http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state),
'response_type' => 'code'
return 'https://www.dropbox.com/oauth2/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state),
'response_type' => 'code'
]);
}
@@ -53,7 +53,7 @@ class Dropbox extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
@@ -77,7 +77,7 @@ class Dropbox extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
@@ -92,7 +92,7 @@ class Dropbox extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -108,11 +108,7 @@ class Dropbox extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['account_id'])) {
return $user['account_id'];
}
return '';
return $user['account_id'] ?? '';
}
/**
@@ -124,11 +120,27 @@ class Dropbox extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://www.dropbox.com/developers/documentation/http/documentation#users-get_current_account
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['email_verified'] ?? false) {
return true;
}
return '';
return false;
}
/**
@@ -140,11 +152,7 @@ class Dropbox extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name']['display_name'];
}
return '';
return $user['name']['display_name'] ?? '';
}
/**
@@ -155,7 +163,7 @@ class Dropbox extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$headers = ['Authorization: Bearer '. \urlencode($accessToken)];
$headers = ['Authorization: Bearer ' . \urlencode($accessToken)];
$user = $this->request('POST', 'https://api.dropboxapi.com/2/users/get_current_account', $headers);
$this->user = \json_decode($user, true);
}
+38 -35
View File
@@ -3,36 +3,35 @@
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
use Utopia\Exception;
class Facebook extends OAuth2
{
/**
* @var string
*/
protected $version = 'v2.8';
protected string $version = 'v2.8';
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'email'
];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'facebook';
}
@@ -40,10 +39,10 @@ class Facebook extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return 'https://www.facebook.com/'.$this->version.'/dialog/oauth?'.\http_build_query([
'client_id'=> $this->appID,
return 'https://www.facebook.com/' . $this->version . '/dialog/oauth?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state)
@@ -57,7 +56,7 @@ class Facebook extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'GET',
'https://graph.facebook.com/' . $this->version . '/oauth/access_token?' . \http_build_query([
@@ -77,7 +76,7 @@ class Facebook extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'GET',
@@ -90,7 +89,7 @@ class Facebook extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -102,15 +101,11 @@ class Facebook extends OAuth2
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
@@ -118,15 +113,27 @@ class Facebook extends OAuth2
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return $user['email'] ?? '';
}
return '';
/**
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Facebook sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
@@ -134,15 +141,11 @@ class Facebook extends OAuth2
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@@ -150,10 +153,10 @@ class Facebook extends OAuth2
*
* @return array
*/
protected function getUser(string $accessToken):array
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request('GET', 'https://graph.facebook.com/'.$this->version.'/me?fields=email,name&access_token='.\urlencode($accessToken));
$user = $this->request('GET', 'https://graph.facebook.com/' . $this->version . '/me?fields=email,name&access_token=' . \urlencode($accessToken));
$this->user = \json_decode($user, true);
}
+61 -45
View File
@@ -3,31 +3,30 @@
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
use Utopia\Exception;
class Github extends OAuth2
{
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'user:email',
];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'github';
}
@@ -35,9 +34,9 @@ class Github extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return 'https://github.com/login/oauth/authorize?'. \http_build_query([
return 'https://github.com/login/oauth/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
@@ -52,7 +51,7 @@ class Github extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$response = $this->request(
'POST',
'https://github.com/login/oauth/access_token',
@@ -78,7 +77,7 @@ class Github extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$response = $this->request(
'POST',
@@ -96,7 +95,7 @@ class Github extends OAuth2
\parse_str($response, $output);
$this->tokens = $output;
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -104,53 +103,59 @@ class Github extends OAuth2
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken):string
{
$emails = \json_decode($this->request('GET', 'https://api.github.com/user/emails', ['Authorization: token '.\urlencode($accessToken)]), true);
foreach ($emails as $email) {
if ($email['primary'] && $email['verified']) {
return $email['email'];
}
}
return '';
}
/**
* @param $accessToken
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://docs.github.com/en/rest/users/emails#list-email-addresses-for-the-authenticated-user
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['verified'] ?? false) {
return true;
}
return '';
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['name'] ?? '';
}
/**
@@ -161,7 +166,18 @@ class Github extends OAuth2
protected function getUser(string $accessToken)
{
if (empty($this->user)) {
$this->user = \json_decode($this->request('GET', 'https://api.github.com/user', ['Authorization: token '.\urlencode($accessToken)]), true);
$this->user = \json_decode($this->request('GET', 'https://api.github.com/user', ['Authorization: token ' . \urlencode($accessToken)]), true);
$emails = $this->request('GET', 'https://api.github.com/user/emails', ['Authorization: token ' . \urlencode($accessToken)]);
$emails = \json_decode($emails, true);
foreach ($emails as $email) {
if (isset($email['verified']) && $email['verified'] === true) {
$this->user['email'] = $email['email'];
$this->user['verified'] = $email['verified'];
break;
}
}
}
return $this->user;
+65 -24
View File
@@ -12,17 +12,17 @@ class Gitlab extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'read_user'
];
@@ -39,7 +39,7 @@ class Gitlab extends OAuth2
*/
public function getLoginURL(): string
{
return 'https://gitlab.com/oauth/authorize?'.\http_build_query([
return $this->getEndpoint() . '/oauth/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
@@ -55,13 +55,13 @@ class Gitlab extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
'https://gitlab.com/oauth/token?' . \http_build_query([
$this->getEndpoint() . '/oauth/token?' . \http_build_query([
'code' => $code,
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'client_secret' => $this->getAppSecret()['clientSecret'],
'redirect_uri' => $this->callback,
'grant_type' => 'authorization_code'
])
@@ -76,19 +76,19 @@ class Gitlab extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
'https://gitlab.com/oauth/token?' . \http_build_query([
$this->getEndpoint() . '/oauth/token?' . \http_build_query([
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'client_secret' => $this->getAppSecret()['clientSecret'],
'grant_type' => 'refresh_token'
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -120,11 +120,27 @@ class Gitlab extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://docs.gitlab.com/ee/api/users.html#list-current-user-for-normal-users
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['confirmed_at'] ?? false) {
return true;
}
return '';
return false;
}
/**
@@ -136,11 +152,7 @@ class Gitlab extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@@ -151,10 +163,39 @@ class Gitlab extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request('GET', 'https://gitlab.com/api/v4/user?access_token='.\urlencode($accessToken));
$user = $this->request('GET', $this->getEndpoint() . '/api/v4/user?access_token=' . \urlencode($accessToken));
$this->user = \json_decode($user, true);
}
return $this->user;
}
/**
* Decode the JSON stored in appSecret
*
* @return array
*/
protected function getAppSecret(): array
{
try {
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
} catch (\Throwable $th) {
throw new \Exception('Invalid secret');
}
return $secret;
}
/**
* Extracts the Tenant Id from the JSON stored in appSecret. Defaults to 'common' as a fallback
*
* @return string
*/
protected function getEndpoint(): string
{
$defaultEndpoint = 'https://gitlab.com';
$secret = $this->getAppSecret();
$endpoint = $secret['endpoint'] ?? $defaultEndpoint;
return empty($endpoint) ? $defaultEndpoint : $endpoint;
}
}
+31 -23
View File
@@ -14,12 +14,12 @@ class Google extends OAuth2
/**
* @var string
*/
protected $version = 'v4';
protected string $version = 'v4';
/**
* @var array
*/
protected $scopes = [
protected array $scopes = [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'openid'
@@ -28,12 +28,12 @@ class Google extends OAuth2
/**
* @var array
*/
protected $user = [];
protected array $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $tokens = [];
/**
* @return string
@@ -48,7 +48,7 @@ class Google extends OAuth2
*/
public function getLoginURL(): string
{
return 'https://accounts.google.com/o/oauth2/v2/auth?'. \http_build_query([
return 'https://accounts.google.com/o/oauth2/v2/auth?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
@@ -64,7 +64,7 @@ class Google extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
'https://oauth2.googleapis.com/token?' . \http_build_query([
@@ -86,7 +86,7 @@ class Google extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
@@ -98,7 +98,7 @@ class Google extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -114,11 +114,7 @@ class Google extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['sub'] ?? '';
}
/**
@@ -130,11 +126,27 @@ class Google extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://www.oauth.com/oauth2-servers/signing-in-with-google/verifying-the-user-info/
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['email_verified'] ?? false) {
return true;
}
return '';
return false;
}
/**
@@ -146,11 +158,7 @@ class Google extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@@ -161,7 +169,7 @@ class Google extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request('GET', 'https://www.googleapis.com/oauth2/v2/userinfo?access_token='.\urlencode($accessToken));
$user = $this->request('GET', 'https://www.googleapis.com/oauth2/v3/userinfo?access_token=' . \urlencode($accessToken));
$this->user = \json_decode($user, true);
}
+46 -43
View File
@@ -9,17 +9,17 @@ class Linkedin extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'r_liteprofile',
'r_emailaddress',
];
@@ -40,7 +40,7 @@ class Linkedin extends OAuth2
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'linkedin';
}
@@ -48,15 +48,15 @@ class Linkedin extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return 'https://www.linkedin.com/oauth/v2/authorization?'.\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
]);
return 'https://www.linkedin.com/oauth/v2/authorization?' . \http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
]);
}
/**
@@ -66,7 +66,7 @@ class Linkedin extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
'https://www.linkedin.com/oauth/v2/accessToken',
@@ -89,7 +89,7 @@ class Linkedin extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
@@ -104,7 +104,7 @@ class Linkedin extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -112,48 +112,51 @@ class Linkedin extends OAuth2
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$email = \json_decode($this->request('GET', 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))', ['Authorization: Bearer '.\urlencode($accessToken)]), true);
$email = \json_decode($this->request('GET', 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))', ['Authorization: Bearer ' . \urlencode($accessToken)]), true);
if (
isset($email['elements']) &&
isset($email['elements'][0]) &&
isset($email['elements'][0]['handle~']) &&
isset($email['elements'][0]['handle~']['emailAddress'])
) {
return $email['elements'][0]['handle~']['emailAddress'];
}
return '';
return $email['elements'][0]['handle~']['emailAddress'] ?? '';
}
/**
* @param $accessToken
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Linkedin sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
$name = '';
@@ -163,7 +166,7 @@ class Linkedin extends OAuth2
}
if (isset($user['localizedLastName'])) {
$name = (empty($name)) ? $user['localizedLastName'] : $name.' '.$user['localizedLastName'];
$name = (empty($name)) ? $user['localizedLastName'] : $name . ' ' . $user['localizedLastName'];
}
return $name;
@@ -177,7 +180,7 @@ class Linkedin extends OAuth2
protected function getUser(string $accessToken)
{
if (empty($this->user)) {
$this->user = \json_decode($this->request('GET', 'https://api.linkedin.com/v2/me', ['Authorization: Bearer '.\urlencode($accessToken)]), true);
$this->user = \json_decode($this->request('GET', 'https://api.linkedin.com/v2/me', ['Authorization: Bearer ' . \urlencode($accessToken)]), true);
}
return $this->user;
+58 -50
View File
@@ -13,17 +13,17 @@ class Microsoft extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'offline_access',
'user.read'
];
@@ -35,17 +35,17 @@ class Microsoft extends OAuth2
{
return 'microsoft';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://login.microsoftonline.com/'.$this->getTenantId().'/oauth2/v2.0/authorize?'.\http_build_query([
return 'https://login.microsoftonline.com/' . $this->getTenantID() . '/oauth2/v2.0/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state'=> \json_encode($this->state),
'scope'=> \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
'scope' => \implode(' ', $this->getScopes()),
'response_type' => 'code',
'response_mode' => 'query'
]);
@@ -58,11 +58,11 @@ class Microsoft extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://login.microsoftonline.com/' . $this->getTenantId() . '/oauth2/v2.0/token',
'https://login.microsoftonline.com/' . $this->getTenantID() . '/oauth2/v2.0/token',
$headers,
\http_build_query([
'code' => $code,
@@ -83,12 +83,12 @@ class Microsoft extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://login.microsoftonline.com/' . $this->getTenantId() . '/oauth2/v2.0/token',
'https://login.microsoftonline.com/' . $this->getTenantID() . '/oauth2/v2.0/token',
$headers,
\http_build_query([
'refresh_token' => $refreshToken,
@@ -98,7 +98,7 @@ class Microsoft extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -114,11 +114,7 @@ class Microsoft extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
@@ -130,11 +126,23 @@ class Microsoft extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['userPrincipalName'])) {
return $user['userPrincipalName'];
}
return $user['userPrincipalName'] ?? '';
}
return '';
/**
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Microsoft sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
@@ -146,11 +154,7 @@ class Microsoft extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['displayName'])) {
return $user['displayName'];
}
return '';
return $user['displayName'] ?? '';
}
/**
@@ -161,7 +165,7 @@ class Microsoft extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$headers = ['Authorization: Bearer '. \urlencode($accessToken)];
$headers = ['Authorization: Bearer ' . \urlencode($accessToken)];
$user = $this->request('GET', 'https://graph.microsoft.com/v1.0/me', $headers);
$this->user = \json_decode($user, true);
}
@@ -169,38 +173,42 @@ class Microsoft extends OAuth2
return $this->user;
}
/**
* Extracts the Client Secret from the JSON stored in appSecret
* @return string
*/
protected function getClientSecret(): string
{
$secret = $this->decodeJson();
return (isset($secret['clientSecret'])) ? $secret['clientSecret'] : '';
}
/**
* Decode the JSON stored in appSecret
*
* @return array
*/
protected function decodeJson(): array
{
protected function getAppSecret(): array
{
try {
$secret = \json_decode($this->appSecret, true);
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
} catch (\Throwable $th) {
throw new Exception('Invalid secret');
throw new \Exception('Invalid secret');
}
return $secret;
}
/**
* Extracts the Tenant Id from the JSON stored in appSecret. Defaults to 'common' as a fallback
* Extracts the Client Secret from the JSON stored in appSecret
*
* @return string
*/
protected function getTenantId(): string
protected function getClientSecret(): string
{
$secret = $this->decodeJson();
return (isset($secret['tenantId'])) ? $secret['tenantId'] : 'common';
$secret = $this->getAppSecret();
return $secret['clientSecret'] ?? '';
}
/**
* Extracts the Tenant Id from the JSON stored in appSecret. Defaults to 'common' as a fallback
*
* @return string
*/
protected function getTenantID(): string
{
$secret = $this->getAppSecret();
return $secret['tenantID'] ?? 'common';
}
}
+42 -42
View File
@@ -10,29 +10,29 @@ class Mock extends OAuth2
/**
* @var string
*/
protected $version = 'v1';
protected string $version = 'v1';
/**
* @var array
*/
protected $scopes = [
protected array $scopes = [
'email'
];
/**
* @var array
*/
protected $user = [];
protected array $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $tokens = [];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'mock';
}
@@ -40,9 +40,9 @@ class Mock extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return 'http://localhost/'.$this->version.'/mock/tests/general/oauth2?'. \http_build_query([
return 'http://localhost/' . $this->version . '/mock/tests/general/oauth2?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
@@ -57,16 +57,16 @@ class Mock extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'GET',
'http://localhost/' . $this->version . '/mock/tests/general/oauth2/token?' .
\http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'client_secret' => $this->appSecret,
'code' => $code
])
\http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'client_secret' => $this->appSecret,
'code' => $code
])
), true);
}
@@ -78,20 +78,20 @@ class Mock extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'GET',
'http://localhost/' . $this->version . '/mock/tests/general/oauth2/token?' .
\http_build_query([
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'refresh_token' => $refreshToken,
'grant_type' => 'refresh_token'
])
\http_build_query([
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'refresh_token' => $refreshToken,
'grant_type' => 'refresh_token'
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -103,15 +103,11 @@ class Mock extends OAuth2
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
@@ -119,15 +115,23 @@ class Mock extends OAuth2
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return $user['email'] ?? '';
}
return '';
/**
* Check if the OAuth email is verified
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
return true;
}
/**
@@ -135,15 +139,11 @@ class Mock extends OAuth2
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@@ -151,10 +151,10 @@ class Mock extends OAuth2
*
* @return array
*/
protected function getUser(string $accessToken):array
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request('GET', 'http://localhost/'.$this->version.'/mock/tests/general/oauth2/user?token='.\urlencode($accessToken));
$user = $this->request('GET', 'http://localhost/' . $this->version . '/mock/tests/general/oauth2/user?token=' . \urlencode($accessToken));
$this->user = \json_decode($user, true);
}
+42 -38
View File
@@ -9,32 +9,32 @@ class Notion extends OAuth2
/**
* @var string
*/
private $endpoint = 'https://api.notion.com/v1';
private string $endpoint = 'https://api.notion.com/v1';
/**
* @var string
*/
private $version = '2021-08-16';
private string $version = '2021-08-16';
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [];
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'notion';
}
@@ -42,9 +42,9 @@ class Notion extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return $this->endpoint . '/oauth/authorize?'. \http_build_query([
return $this->endpoint . '/oauth/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'response_type' => 'code',
@@ -60,7 +60,7 @@ class Notion extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = ['Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret)];
$this->tokens = \json_decode($this->request(
'POST',
@@ -82,7 +82,7 @@ class Notion extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret)];
$this->tokens = \json_decode($this->request(
@@ -95,7 +95,7 @@ class Notion extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -103,51 +103,55 @@ class Notion extends OAuth2
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$response = $this->getUser($accessToken);
if (isset($response['bot']['owner']['user']['id'])) {
return $response['bot']['owner']['user']['id'];
}
return '';
return $response['bot']['owner']['user']['id'] ?? '';
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$response = $this->getUser($accessToken);
if(isset($response['bot']['owner']['user']['person']['email'])){
return $response['bot']['owner']['user']['person']['email'];
}
return '';
return $response['bot']['owner']['user']['person']['email'] ?? '';
}
/**
* @param $accessToken
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Notion sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$response = $this->getUser($accessToken);
if (isset($response['bot']['owner']['user']['name'])) {
return $response['bot']['owner']['user']['name'];
}
return '';
return $response['bot']['owner']['user']['name'] ?? '';
}
/**
@@ -155,11 +159,11 @@ class Notion extends OAuth2
*
* @return array
*/
protected function getUser(string $accessToken)
protected function getUser(string $accessToken): array
{
$headers = [
'Notion-Version: ' . $this->version,
'Authorization: Bearer '.\urlencode($accessToken)
'Authorization: Bearer ' . \urlencode($accessToken)
];
if (empty($this->user)) {
+231
View File
@@ -0,0 +1,231 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://developer.okta.com/docs/guides/sign-into-web-app-redirect/php/main/
class Okta extends OAuth2
{
/**
* @var array
*/
protected array $scopes = [
'openid',
'profile',
'email',
'offline_access'
];
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @return string
*/
public function getName(): string
{
return 'okta';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://' . $this->getOktaDomain() . '/oauth2/' . $this->getAuthorizationServerId() . '/v1/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state),
'scope' => \implode(' ', $this->getScopes()),
'response_type' => 'code'
]);
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://' . $this->getOktaDomain() . '/oauth2/' . $this->getAuthorizationServerId() . '/v1/token',
$headers,
\http_build_query([
'code' => $code,
'client_id' => $this->appID,
'client_secret' => $this->getClientSecret(),
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'grant_type' => 'authorization_code'
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
'https://' . $this->getOktaDomain() . '/oauth2/' . $this->getAuthorizationServerId() . '/v1/token',
$headers,
\http_build_query([
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->getClientSecret(),
'grant_type' => 'refresh_token'
])
), true);
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['sub'] ?? '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://developer.okta.com/docs/reference/api/oidc/#userinfo
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['email_verified'] ?? false) {
return true;
}
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['name'] ?? '';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$headers = ['Authorization: Bearer ' . \urlencode($accessToken)];
$user = $this->request('GET', 'https://' . $this->getOktaDomain() . '/oauth2/' . $this->getAuthorizationServerId() . '/v1/userinfo', $headers);
$this->user = \json_decode($user, true);
}
return $this->user;
}
/**
* Extracts the Client Secret from the JSON stored in appSecret
*
* @return string
*/
protected function getClientSecret(): string
{
$secret = $this->getAppSecret();
return $secret['clientSecret'] ?? '';
}
/**
* Extracts the Okta Domain from the JSON stored in appSecret
*
* @return string
*/
protected function getOktaDomain(): string
{
$secret = $this->getAppSecret();
return $secret['oktaDomain'] ?? '';
}
/**
* Extracts the Okta Authorization Server ID from the JSON stored in appSecret
*
* @return string
*/
protected function getAuthorizationServerId(): string
{
$secret = $this->getAppSecret();
return $secret['authorizationServerId'] ?? 'default';
}
/**
* Decode the JSON stored in appSecret
*
* @return array
*/
protected function getAppSecret(): array
{
try {
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
} catch (\Throwable $th) {
throw new \Exception('Invalid secret');
}
return $secret;
}
}
+44 -26
View File
@@ -12,7 +12,7 @@ class Paypal extends OAuth2
/**
* @var array
*/
private $endpoint = [
private array $endpoint = [
'sandbox' => 'https://www.sandbox.paypal.com/',
'live' => 'https://www.paypal.com/',
];
@@ -20,7 +20,7 @@ class Paypal extends OAuth2
/**
* @var array
*/
private $resourceEndpoint = [
private array $resourceEndpoint = [
'sandbox' => 'https://api.sandbox.paypal.com/v1/',
'live' => 'https://api.paypal.com/v1/',
];
@@ -28,22 +28,22 @@ class Paypal extends OAuth2
/**
* @var string
*/
protected $environment = 'live';
protected string $environment = 'live';
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'openid',
'profile',
'email'
@@ -62,7 +62,7 @@ class Paypal extends OAuth2
*/
public function getLoginURL(): string
{
$url = $this->endpoint[$this->environment] . 'connect/?'.
$url = $this->endpoint[$this->environment] . 'connect/?' .
\http_build_query([
'flowEntry' => 'static',
'response_type' => 'code',
@@ -83,7 +83,7 @@ class Paypal extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
$this->resourceEndpoint[$this->environment] . 'oauth2/token',
@@ -103,7 +103,7 @@ class Paypal extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
@@ -115,7 +115,7 @@ class Paypal extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -131,11 +131,7 @@ class Paypal extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['payer_id'])) {
return $user['payer_id'];
}
return '';
return $user['payer_id'] ?? '';
}
/**
@@ -148,12 +144,38 @@ class Paypal extends OAuth2
$user = $this->getUser($accessToken);
if (isset($user['emails'])) {
return $user['emails'][0]['value'];
$email = array_filter($user['emails'], function ($email) {
return $email['primary'] === true;
});
if (!empty($email)) {
return $email[0]['value'];
}
}
return '';
}
/**
* Check if the OAuth email is verified
*
* @link https://developer.paypal.com/docs/api/identity/v1/#userinfo_get
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['verified_account'] ?? false) {
return true;
}
return false;
}
/**
* @param string $accessToken
*
@@ -163,11 +185,7 @@ class Paypal extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@@ -179,7 +197,7 @@ class Paypal extends OAuth2
{
$header = [
'Content-Type: application/json',
'Authorization: Bearer '.\urlencode($accessToken),
'Authorization: Bearer ' . \urlencode($accessToken),
];
if (empty($this->user)) {
$user = $this->request(
+1 -1
View File
@@ -6,7 +6,7 @@ use Appwrite\Auth\OAuth2\Paypal;
class PaypalSandbox extends Paypal
{
protected $environment = 'sandbox';
protected string $environment = 'sandbox';
/**
* @return string
+40 -32
View File
@@ -14,17 +14,17 @@ class Salesforce extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
"openid"
];
@@ -37,7 +37,7 @@ class Salesforce extends OAuth2
}
/**
* @param $state
* @param string $state
*
* @return array
*/
@@ -52,13 +52,13 @@ class Salesforce extends OAuth2
*/
public function getLoginURL(): string
{
return 'https://login.salesforce.com/services/oauth2/authorize?'.\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'redirect_uri'=> $this->callback,
'scope'=> \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state)
]);
return 'https://login.salesforce.com/services/oauth2/authorize?' . \http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state)
]);
}
/**
@@ -68,7 +68,7 @@ class Salesforce extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = [
'Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret),
'Content-Type: application/x-www-form-urlencoded',
@@ -93,7 +93,7 @@ class Salesforce extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = [
'Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret),
@@ -109,7 +109,7 @@ class Salesforce extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -125,11 +125,7 @@ class Salesforce extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['user_id'])) {
return $user['user_id'];
}
return '';
return $user['user_id'] ?? '';
}
/**
@@ -141,11 +137,27 @@ class Salesforce extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://help.salesforce.com/s/articleView?id=sf.remoteaccess_using_userinfo_endpoint.htm&type=5
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['email_verified'] ?? false) {
return true;
}
return '';
return false;
}
/**
@@ -157,11 +169,7 @@ class Salesforce extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@@ -172,7 +180,7 @@ class Salesforce extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request('GET', 'https://login.salesforce.com/services/oauth2/userinfo?access_token='.\urlencode($accessToken));
$user = $this->request('GET', 'https://login.salesforce.com/services/oauth2/userinfo?access_token=' . \urlencode($accessToken));
$this->user = \json_decode($user, true);
}
return $this->user;
+41 -35
View File
@@ -3,24 +3,23 @@
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
use Utopia\Exception;
class Slack extends OAuth2
{
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'identity.avatar',
'identity.basic',
'identity.email',
@@ -30,7 +29,7 @@ class Slack extends OAuth2
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'slack';
}
@@ -38,11 +37,11 @@ class Slack extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
// https://api.slack.com/docs/oauth#step_1_-_sending_users_to_authorize_and_or_install
return 'https://slack.com/oauth/authorize?'.\http_build_query([
'client_id'=> $this->appID,
return 'https://slack.com/oauth/authorize?' . \http_build_query([
'client_id' => $this->appID,
'scope' => \implode(' ', $this->getScopes()),
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state)
@@ -56,7 +55,7 @@ class Slack extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
// https://api.slack.com/docs/oauth#step_3_-_exchanging_a_verification_code_for_an_access_token
$this->tokens = \json_decode($this->request(
'GET',
@@ -77,7 +76,7 @@ class Slack extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'GET',
@@ -89,7 +88,7 @@ class Slack extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -101,15 +100,11 @@ class Slack extends OAuth2
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['user']['id'])) {
return $user['user']['id'];
}
return '';
return $user['user']['id'] ?? '';
}
/**
@@ -117,15 +112,29 @@ class Slack extends OAuth2
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['user']['email'])) {
return $user['user']['email'];
}
return $user['user']['email'] ?? '';
}
return '';
/**
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Slack sign up process
*
* @link https://slack.com/help/articles/207262907-Change-your-email-address
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
@@ -133,29 +142,26 @@ class Slack extends OAuth2
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['user']['name'])) {
return $user['user']['name'];
}
return '';
return $user['user']['name'] ?? '';
}
/**
* @link https://api.slack.com/methods/users.identity
*
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken):array
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
// https://api.slack.com/methods/users.identity
$user = $this->request(
'GET',
'https://slack.com/api/users.identity?token='.\urlencode($accessToken)
'https://slack.com/api/users.identity?token=' . \urlencode($accessToken)
);
$this->user = \json_decode($user, true);
+42 -39
View File
@@ -9,38 +9,37 @@ use Appwrite\Auth\OAuth2;
class Spotify extends OAuth2
{
/**
* @var string
*/
private string $endpoint = 'https://accounts.spotify.com/';
/**
* @var string
*/
private $endpoint = 'https://accounts.spotify.com/';
/**
* @var string
*/
private $resourceEndpoint = 'https://api.spotify.com/v1/';
private string $resourceEndpoint = 'https://api.spotify.com/v1/';
/**
* @var array
*/
protected $scopes = [
protected array $scopes = [
'user-read-email',
];
/**
* @var array
*/
protected $user = [];
protected array $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $tokens = [];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'spotify';
}
@@ -48,9 +47,9 @@ class Spotify extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return $this->endpoint . 'authorize?'.
return $this->endpoint . 'authorize?' .
\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
@@ -67,7 +66,7 @@ class Spotify extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = ['Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret)];
$this->tokens = \json_decode($this->request(
'POST',
@@ -89,7 +88,7 @@ class Spotify extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret)];
$this->tokens = \json_decode($this->request(
@@ -102,7 +101,7 @@ class Spotify extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -110,51 +109,55 @@ class Spotify extends OAuth2
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return '';
return $user['email'] ?? '';
}
/**
* @param $accessToken
* Check if the OAuth email is verified
*
* Spotify does not assure that the email is verified
*
* @link https://developer.spotify.com/documentation/web-api/reference/#/operations/get-current-users-profile
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['display_name'])) {
return $user['display_name'];
}
return '';
return $user['display_name'] ?? '';
}
/**
@@ -167,8 +170,8 @@ class Spotify extends OAuth2
if (empty($this->user)) {
$this->user = \json_decode($this->request(
'GET',
$this->resourceEndpoint . "me",
['Authorization: Bearer '.\urlencode($accessToken)]
$this->resourceEndpoint . 'me',
['Authorization: Bearer ' . \urlencode($accessToken)]
), true);
}
+49 -44
View File
@@ -10,38 +10,37 @@ class Stripe extends OAuth2
/**
* @var array
*/
protected $user = [];
protected array $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $tokens = [];
/**
* @var string
*/
protected $stripeAccountId = '';
protected string $stripeAccountId = '';
/**
* @var array
*/
protected $scopes = [
protected array $scopes = [
'read_write',
];
/**
* @return string
/**
* @var array
*/
protected $grantType = [
'authorize' => 'authorization_code',
'refresh' => 'refresh_token',
protected array $grantType = [
'authorize' => 'authorization_code',
'refresh' => 'refresh_token',
];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'stripe';
}
@@ -49,9 +48,9 @@ class Stripe extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return 'https://connect.stripe.com/oauth/authorize?'. \http_build_query([
return 'https://connect.stripe.com/oauth/authorize?' . \http_build_query([
'response_type' => 'code', // The only option at the moment is "code."
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
@@ -67,7 +66,7 @@ class Stripe extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
'https://connect.stripe.com/oauth/token',
@@ -89,7 +88,7 @@ class Stripe extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
@@ -101,7 +100,7 @@ class Stripe extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -110,51 +109,59 @@ class Stripe extends OAuth2
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if(empty($user)) {
return '';
if (empty($user)) {
return '';
}
return $user['email'] ?? '';
}
/**
* @param $accessToken
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Stripe sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@@ -166,15 +173,13 @@ class Stripe extends OAuth2
{
if (empty($this->user) && !empty($this->stripeAccountId)) {
$this->user = \json_decode(
$this->request(
'GET',
'https://api.stripe.com/v1/accounts/' . $this->stripeAccountId,
['Authorization: Bearer '.\urlencode($accessToken)]
),
true
$this->request(
'GET',
'https://api.stripe.com/v1/accounts/' . $this->stripeAccountId,
['Authorization: Bearer ' . \urlencode($accessToken)]
),
true
);
}
return $this->user;
+33 -15
View File
@@ -9,38 +9,40 @@ use Appwrite\Auth\OAuth2;
class Tradeshift extends OAuth2
{
const TRADESHIFT_SANDBOX_API_DOMAIN = 'api-sandbox.tradeshift.com';
const TRADESHIFT_API_DOMAIN = 'api.tradeshift.com';
public const TRADESHIFT_SANDBOX_API_DOMAIN = 'api-sandbox.tradeshift.com';
public const TRADESHIFT_API_DOMAIN = 'api.tradeshift.com';
private $apiDomain = [
private array $apiDomain = [
'sandbox' => self::TRADESHIFT_SANDBOX_API_DOMAIN,
'live' => self::TRADESHIFT_API_DOMAIN,
];
private $endpoint = [
private array $endpoint = [
'sandbox' => 'https://' . self::TRADESHIFT_SANDBOX_API_DOMAIN . '/tradeshift/',
'live' => 'https://' . self::TRADESHIFT_API_DOMAIN . '/tradeshift/',
];
private $resourceEndpoint = [
private array $resourceEndpoint = [
'sandbox' => 'https://' . self::TRADESHIFT_SANDBOX_API_DOMAIN . '/tradeshift/rest/external/',
'live' => 'https://' . self::TRADESHIFT_API_DOMAIN . '/tradeshift/rest/external/',
];
protected $environment = 'live';
protected string $environment = 'live';
/**
* @var array
*/
protected $user = [];
protected array $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $tokens = [];
protected $scopes = [
/**
* @var array
*/
protected array $scopes = [
'openid',
'offline',
];
@@ -78,7 +80,7 @@ class Tradeshift extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint[$this->environment] . 'auth/token',
@@ -98,7 +100,7 @@ class Tradeshift extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
@@ -109,8 +111,8 @@ class Tradeshift extends OAuth2
'refresh_token' => $refreshToken,
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -141,6 +143,22 @@ class Tradeshift extends OAuth2
return $user['Username'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Tradeshift sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUser($accessToken);
return !empty($email);
}
/**
* @param string $accessToken
*
+1 -1
View File
@@ -6,7 +6,7 @@ use Appwrite\Auth\OAuth2\Tradeshift;
class TradeshiftBox extends Tradeshift
{
protected $environment = 'sandbox';
protected string $environment = 'sandbox';
/**
* @return string
+44 -39
View File
@@ -9,38 +9,37 @@ use Appwrite\Auth\OAuth2;
class Twitch extends OAuth2
{
/**
* @var string
*/
private string $endpoint = 'https://id.twitch.tv/oauth2/';
/**
* @var string
*/
private $endpoint = 'https://id.twitch.tv/oauth2/';
/**
* @var string
*/
private $resourceEndpoint = 'https://api.twitch.tv/helix/users';
private string $resourceEndpoint = 'https://api.twitch.tv/helix/users';
/**
* @var array
*/
protected $scopes = [
protected array $scopes = [
'user:read:email',
];
/**
* @var array
*/
protected $user = [];
protected array $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $tokens = [];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'twitch';
}
@@ -48,9 +47,9 @@ class Twitch extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return $this->endpoint . 'authorize?'.
return $this->endpoint . 'authorize?' .
\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
@@ -68,7 +67,7 @@ class Twitch extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . 'token?' . \http_build_query([
@@ -89,7 +88,7 @@ class Twitch extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
@@ -101,7 +100,7 @@ class Twitch extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -109,51 +108,57 @@ class Twitch extends OAuth2
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return '';
return $user['email'] ?? '';
}
/**
* @param $accessToken
* Check if the OAuth email is verified
*
* If present, the email is verified
*
* @link https://dev.twitch.tv/docs/api/reference#get-users
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['display_name'])) {
return $user['display_name'];
}
return '';
return $user['display_name'] ?? '';
}
/**
@@ -168,8 +173,8 @@ class Twitch extends OAuth2
'GET',
$this->resourceEndpoint,
[
'Authorization: Bearer '.\urlencode($accessToken),
'Client-Id: '. \urlencode($this->appID)
'Authorization: Bearer ' . \urlencode($accessToken),
'Client-Id: ' . \urlencode($this->appID)
]
), true);
-191
View File
@@ -1,191 +0,0 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
use Utopia\Exception;
// Reference Material
// https://vk.com/dev/first_guide
// https://vk.com/dev/auth_sites
// https://vk.com/dev/api_requests
// https://plugins.miniorange.com/guide-to-configure-vkontakte-as-oauth-server
class Vk extends OAuth2
{
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
/**
* @var array
*/
protected $scopes = [
'openid',
'email'
];
/**
* @var string
*/
protected $version = '5.101';
/**
* @return string
*/
public function getName(): string
{
return 'vk';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://oauth.vk.com/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'response_type' => 'code',
'state' => \json_encode($this->state),
'v' => $this->version,
'scope' => \implode(' ', $this->getScopes())
]);
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded;charset=UTF-8'];
$this->tokens = \json_decode($this->request(
'POST',
'https://oauth.vk.com/access_token?',
$headers,
\http_build_query([
'code' => $code,
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'redirect_uri' => $this->callback
])
), true);
$this->user['email'] = $this->tokens['email'];
$this->user['user_id'] = $this->tokens['user_id'];
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
{
$headers = ['Content-Type: application/x-www-form-urlencoded;charset=UTF-8'];
$this->tokens = \json_decode($this->request(
'POST',
'https://oauth.vk.com/access_token?',
$headers,
\http_build_query([
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'grant_type' => 'refresh_token'
])
), true);
if(empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
$this->user['email'] = $this->tokens['email'];
$this->user['user_id'] = $this->tokens['user_id'];
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['user_id'])) {
return $user['user_id'];
}
return '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user['name'])) {
$user = $this->request(
'GET',
'https://api.vk.com/method/users.get?'. \http_build_query([
'v' => $this->version,
'fields' => 'id,name,email,first_name,last_name',
'access_token' => $accessToken
])
);
$user = \json_decode($user, true);
$this->user['name'] = $user['response'][0]['first_name'] ." ".$user['response'][0]['last_name'];
}
return $this->user;
}
}
+43 -31
View File
@@ -12,24 +12,24 @@ class WordPress extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'auth',
];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'wordpress';
}
@@ -37,9 +37,9 @@ class WordPress extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return 'https://public-api.wordpress.com/oauth2/authorize?'. \http_build_query([
return 'https://public-api.wordpress.com/oauth2/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'response_type' => 'code',
@@ -55,7 +55,7 @@ class WordPress extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
'https://public-api.wordpress.com/oauth2/token',
@@ -78,7 +78,7 @@ class WordPress extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
@@ -92,7 +92,7 @@ class WordPress extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -100,51 +100,63 @@ class WordPress extends OAuth2
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['ID'])) {
return $user['ID'];
return $user['ID'] ?? '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if ($user['verified']) {
return $user['email'] ?? '';
}
return '';
}
/**
* @param $accessToken
* Check if the OAuth email is verified
*
* @return string
* @link https://developer.wordpress.com/docs/api/1.1/get/me/
*
* @param string $accessToken
*
* @return bool
*/
public function getUserEmail(string $accessToken):string
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if (isset($user['email']) && $user['verified']) {
return $user['email'];
if ($user['email_verified'] ?? false) {
return true;
}
return '';
return false;
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['username'])) {
return $user['username'];
}
return '';
return $user['username'] ?? '';
}
/**
@@ -155,7 +167,7 @@ class WordPress extends OAuth2
protected function getUser(string $accessToken)
{
if (empty($this->user)) {
$this->user = \json_decode($this->request('GET', 'https://public-api.wordpress.com/rest/v1/me', ['Authorization: Bearer '.$accessToken]), true);
$this->user = \json_decode($this->request('GET', 'https://public-api.wordpress.com/rest/v1/me', ['Authorization: Bearer ' . $accessToken]), true);
}
return $this->user;
+41 -38
View File
@@ -9,21 +9,20 @@ use Appwrite\Auth\OAuth2;
class Yahoo extends OAuth2
{
/**
* @var string
*/
private string $endpoint = 'https://api.login.yahoo.com/oauth2/';
/**
* @var string
*/
private $endpoint = 'https://api.login.yahoo.com/oauth2/';
/**
* @var string
*/
private $resourceEndpoint = 'https://api.login.yahoo.com/openid/v1/userinfo';
private string $resourceEndpoint = 'https://api.login.yahoo.com/openid/v1/userinfo';
/**
* @var array
*/
protected $scopes = [
protected array $scopes = [
'sdct-r',
'sdpp-w',
];
@@ -31,17 +30,17 @@ class Yahoo extends OAuth2
/**
* @var array
*/
protected $user = [];
protected array $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $tokens = [];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'yahoo';
}
@@ -60,9 +59,9 @@ class Yahoo extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return $this->endpoint . 'request_auth?'.
return $this->endpoint . 'request_auth?' .
\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
@@ -79,7 +78,7 @@ class Yahoo extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = [
'Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret),
'Content-Type: application/x-www-form-urlencoded',
@@ -105,7 +104,7 @@ class Yahoo extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = [
'Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret),
@@ -122,7 +121,7 @@ class Yahoo extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -130,51 +129,55 @@ class Yahoo extends OAuth2
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['sub'])) {
return $user['sub'];
}
return '';
return $user['sub'] ?? '';
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return '';
return $user['email'] ?? '';
}
/**
* @param $accessToken
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Yahoo sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@@ -188,7 +191,7 @@ class Yahoo extends OAuth2
$this->user = \json_decode($this->request(
'GET',
$this->resourceEndpoint,
['Authorization: Bearer '.\urlencode($accessToken)]
['Authorization: Bearer ' . \urlencode($accessToken)]
), true);
}
+34 -30
View File
@@ -12,17 +12,17 @@ class Yammer extends OAuth2
/**
* @var string
*/
private $endpoint = 'https://www.yammer.com/oauth2/';
private string $endpoint = 'https://www.yammer.com/oauth2/';
/**
* @var array
*/
protected $user = [];
protected array $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $tokens = [];
/**
* @return string
@@ -37,13 +37,13 @@ class Yammer extends OAuth2
*/
public function getLoginURL(): string
{
return $this->endpoint . 'oauth2/authorize?'.
\http_build_query([
'client_id' => $this->appID,
'response_type' => 'code',
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state)
]);
return $this->endpoint . 'oauth2/authorize?' .
\http_build_query([
'client_id' => $this->appID,
'response_type' => 'code',
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state)
]);
}
/**
@@ -53,7 +53,7 @@ class Yammer extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
@@ -76,7 +76,7 @@ class Yammer extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
@@ -91,7 +91,7 @@ class Yammer extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -107,11 +107,7 @@ class Yammer extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
@@ -123,11 +119,23 @@ class Yammer extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return '';
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Yammer sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
@@ -139,11 +147,7 @@ class Yammer extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['full_name'])) {
return $user['full_name'];
}
return '';
return $user['full_name'] ?? '';
}
/**
@@ -154,7 +158,7 @@ class Yammer extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$headers = ['Authorization: Bearer '. \urlencode($accessToken)];
$headers = ['Authorization: Bearer ' . \urlencode($accessToken)];
$user = $this->request('GET', 'https://www.yammer.com/api/v1/users/current.json', $headers);
$this->user = \json_decode($user, true);
}
+32 -32
View File
@@ -14,17 +14,17 @@ class Yandex extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [];
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [];
/**
* @return string
@@ -35,7 +35,7 @@ class Yandex extends OAuth2
}
/**
* @param $state
* @param string $state
*
* @return array
*/
@@ -50,12 +50,12 @@ class Yandex extends OAuth2
*/
public function getLoginURL(): string
{
return 'https://oauth.yandex.com/authorize?'.\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'scope'=> \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state)
]);
return 'https://oauth.yandex.com/authorize?' . \http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state)
]);
}
/**
@@ -65,7 +65,7 @@ class Yandex extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = [
'Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret),
'Content-Type: application/x-www-form-urlencoded',
@@ -89,7 +89,7 @@ class Yandex extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = [
'Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret),
@@ -105,7 +105,7 @@ class Yandex extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -121,11 +121,7 @@ class Yandex extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
@@ -137,11 +133,19 @@ class Yandex extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['default_email'])) {
return $user['default_email'];
}
return $user['default_email'] ?? '';
}
return '';
/**
* Check if the OAuth email is verified
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
return false;
}
/**
@@ -153,11 +157,7 @@ class Yandex extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['display_name'])) {
return $user['display_name'];
}
return '';
return $user['display_name'] ?? '';
}
/**
@@ -168,7 +168,7 @@ class Yandex extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request('GET', 'https://login.yandex.ru/info?'.\http_build_query([
$user = $this->request('GET', 'https://login.yandex.ru/info?' . \http_build_query([
'format' => 'json',
'oauth_token' => $accessToken
]));
+180
View File
@@ -0,0 +1,180 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
class Zoom extends OAuth2
{
/**
* @var string
*/
private string $endpoint = 'https://zoom.us';
/**
* @var string
*/
private string $version = '2022-03-26';
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'user_info:read'
];
/**
* @return string
*/
public function getName(): string
{
return 'zoom';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return $this->endpoint . '/oauth/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'response_type' => 'code',
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
]);
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$headers = ['Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret), 'Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . '/oauth/token',
$headers,
\http_build_query([
'grant_type' => 'authorization_code',
'redirect_uri' => $this->callback,
'code' => $code
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$headers = ['Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret), 'Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . '/oauth/token',
$headers,
\http_build_query([
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
])
), true);
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$response = $this->getUser($accessToken);
return $response['id'] ?? '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$response = $this->getUser($accessToken);
return $response['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://marketplace.zoom.us/docs/api-reference/zoom-api/methods/#operation/user
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if (($user['verified'] ?? false) === 1) {
return true;
}
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$response = $this->getUser($accessToken);
return ($response['first_name'] ?? '') . ' ' . ($response['last_name'] ?? '');
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken)
{
$headers = [
'Authorization: Bearer ' . \urlencode($accessToken)
];
if (empty($this->user)) {
$this->user = \json_decode($this->request('GET', 'https://api.zoom.us/v2/users/me', $headers), true);
}
return $this->user;
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
namespace Appwrite\Auth;
use Appwrite\Extend\Exception;
abstract class Phone
{
/**
* @var string
*/
protected string $user;
/**
* @var string
*/
protected string $secret;
/**
* @param string $key
*/
public function __construct(string $user, string $secret)
{
$this->user = $user;
$this->secret = $secret;
}
/**
* Send Message to phone.
* @param string $from
* @param string $to
* @param string $message
* @return void
*/
abstract public function send(string $from, string $to, string $message): void;
/**
* @param string $method
* @param string $url
* @param array $headers
* @param string $payload
*
* @return string
*/
protected function request(string $method, string $url, array $headers = [], ?string $payload = null, ?string $userpwd = null): string
{
$ch = \curl_init($url);
\curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
\curl_setopt($ch, CURLOPT_HEADER, 0);
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
\curl_setopt($ch, CURLOPT_USERAGENT, 'Appwrite Phone Authentication');
if (!is_null($payload)) {
\curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
}
if (!is_null($userpwd)) {
\curl_setopt($ch, CURLOPT_USERPWD, $userpwd);
}
$headers[] = 'Content-length: ' . \strlen($payload);
\curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = (string) \curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
\curl_close($ch);
if ($code >= 400) {
throw new Exception($response);
}
return $response;
}
/**
* Generate 6 random digits for phone verification.
*
* @param int $digits
* @return string
*/
public function generateSecretDigits(int $digits = 6): string
{
return substr(str_shuffle("0123456789"), 0, $digits);
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace Appwrite\Auth\Phone;
use Appwrite\Auth\Phone;
class Mock extends Phone
{
/**
* @var string
*/
public static string $defaultDigits = '123456';
/**
* @param string $from
* @param string $to
* @param string $message
* @return void
*/
public function send(string $from, string $to, string $message): void
{
return;
}
/**
* @param int $digits
* @return string
*/
public function generateSecretDigits(int $digits = 6): string
{
return self::$defaultDigits;
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace Appwrite\Auth\Phone;
use Appwrite\Auth\Phone;
// Reference Material
// https://docs.msg91.com/p/tf9GTextN/e/Irz7-x1PK/MSG91
class Msg91 extends Phone
{
/**
* @var string
*/
private string $endpoint = 'https://api.msg91.com/api/v5/flow/';
/**
* For Flow based sending SMS sender ID should not be set in flow
* In environment _APP_PHONE_PROVIDER format is 'phone://[senderID]:[authKey]@msg91'.
* _APP_PHONE_FROM value is flow ID created in Msg91
* Eg. _APP_PHONE_PROVIDER = phone://DINESH:5e1e93cad6fc054d8e759a5b@msg91
* _APP_PHONE_FROM = 3968636f704b303135323339
* @param string $from-> utilized from for flow id
* @param string $to
* @param string $message
* @return void
*/
public function send(string $from, string $to, string $message): void
{
$to = ltrim($to, '+');
$this->request(
method: 'POST',
url: $this->endpoint,
payload: json_encode([
'sender' => $this->user,
'otp' => $message,
'flow_id' => $from,
'mobiles' => $to
]),
headers: [
"content-type: application/JSON",
"authkey: {$this->secret}",
]
);
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace Appwrite\Auth\Phone;
use Appwrite\Auth\Phone;
// Reference Material
// https://developer.telesign.com/enterprise/docs/sms-api-send-an-sms
class Telesign extends Phone
{
/**
* @var string
*/
private string $endpoint = 'https://rest-api.telesign.com/v1/messaging';
/**
* @param string $from
* @param string $to
* @param string $message
* @return void
* @throws \Appwrite\Extend\Exception
*/
public function send(string $from, string $to, string $message): void
{
$to = ltrim($to, '+');
$this->request(
method: 'POST',
url: $this->endpoint,
payload: \http_build_query([
'message' => $message,
'message_type' => 'otp',
'phone_number' => $to
]),
userpwd: "{$this->user}:{$this->secret}"
);
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace Appwrite\Auth\Phone;
use Appwrite\Auth\Phone;
// Reference Material
// https://www.textmagic.com/docs/api/start/
class TextMagic extends Phone
{
/**
* @var string
*/
private string $endpoint = 'https://rest.textmagic.com/api/v2';
/**
* @param string $from
* @param string $to
* @param string $message
* @return void
*/
public function send(string $from, string $to, string $message): void
{
$to = ltrim($to, '+');
$from = ltrim($from, '+');
$this->request(
method: 'POST',
url: $this->endpoint . '/messages',
payload: \http_build_query([
'text' => $message,
'from' => $from,
'phones' => $to
]),
headers: [
"X-TM-Username: {$this->user}",
"X-TM-Key: {$this->secret}",
]
);
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace Appwrite\Auth\Phone;
use Appwrite\Auth\Phone;
// Reference Material
// https://www.twilio.com/docs/sms/api
class Twilio extends Phone
{
/**
* @var string
*/
private string $endpoint = 'https://api.twilio.com/2010-04-01';
/**
* @param string $from
* @param string $to
* @param string $message
* @return void
*/
public function send(string $from, string $to, string $message): void
{
$this->request(
method: 'POST',
url: "{$this->endpoint}/Accounts/{$this->user}/Messages.json",
payload: \http_build_query([
'Body' => $message,
'From' => $from,
'To' => $to
]),
userpwd: "{$this->user}:{$this->secret}"
);
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace Appwrite\Auth\Phone;
use Appwrite\Auth\Phone;
// Reference Material
// https://developer.vonage.com/api/sms
class Vonage extends Phone
{
/**
* @var string
*/
private string $endpoint = 'https://rest.nexmo.com/sms/json';
/**
* @param string $from
* @param string $to
* @param string $message
* @return void
*/
public function send(string $from, string $to, string $message): void
{
$to = ltrim($to, '+');
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->request(
method: 'POST',
url: $this->endpoint,
headers: $headers,
payload: \http_build_query([
'text' => $message,
'from' => $from,
'to' => $to,
'api_key' => $this->user,
'api_secret' => $this->secret
])
);
}
}
+1 -1
View File
@@ -34,7 +34,7 @@ class Password extends Validator
{
if (!\is_string($value)) {
return false;
}
}
if (\strlen($value) < 8) {
return false;
+61
View File
@@ -0,0 +1,61 @@
<?php
namespace Appwrite\Auth\Validator;
use Utopia\Validator;
/**
* Phone.
*
* Validates a number for the E.164 format.
*/
class Phone extends Validator
{
/**
* Get Description.
*
* Returns validator description
*
* @return string
*/
public function getDescription(): string
{
return "Phone number must start with a '+' can have a maximum of fifteen digits.";
}
/**
* Is valid.
*
* @param mixed $value
*
* @return bool
*/
public function isValid($value): bool
{
return is_string($value) && !!\preg_match('/^\+[1-9]\d{1,14}$/', $value);
}
/**
* Is array
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_STRING;
}
}
+135
View File
@@ -0,0 +1,135 @@
<?php
namespace Appwrite\DSN;
class DSN
{
/**
* @var string
*/
protected string $scheme;
/**
* @var ?string
*/
protected ?string $user;
/**
* @var ?string
*/
protected ?string $password;
/**
* @var string
*/
protected string $host;
/**
* @var ?string
*/
protected ?string $port;
/**
* @var ?string
*/
protected ?string $database;
/**
* @var ?string
*/
protected ?string $query;
/**
* Construct
*
* Construct a new DSN object
*
* @param string $dsn
*/
public function __construct(string $dsn)
{
$parts = parse_url($dsn);
if (!$parts) {
throw new \InvalidArgumentException("Unable to parse DSN: $dsn");
}
$this->scheme = $parts['scheme'] ?? null;
$this->user = $parts['user'] ?? null;
$this->password = $parts['pass'] ?? null;
$this->host = $parts['host'] ?? null;
$this->port = $parts['port'] ?? null;
$this->database = $parts['path'] ?? null;
$this->query = $parts['query'] ?? null;
}
/**
* Return the scheme.
*
* @return string
*/
public function getScheme(): string
{
return $this->scheme;
}
/**
* Return the user.
*
* @return ?string
*/
public function getUser(): ?string
{
return $this->user;
}
/**
* Return the password.
*
* @return ?string
*/
public function getPassword(): ?string
{
return $this->password;
}
/**
* Return the host
*
* @return string
*/
public function getHost(): string
{
return $this->host;
}
/**
* Return the port
*
* @return ?string
*/
public function getPort(): ?string
{
return $this->port;
}
/**
* Return the database
*
* @return ?string
*/
public function getDatabase(): ?string
{
return ltrim($this->database, '/');
}
/**
* Return the query string
*
* @return ?string
*/
public function getQuery(): ?string
{
return $this->query;
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ class Compose
$this->compose['services'] = (isset($this->compose['services']) && is_array($this->compose['services']))
? $this->compose['services'] : [];
foreach ($this->compose['services'] as $key => &$service) {
$service = new Service($service);
}
+2 -2
View File
@@ -17,7 +17,7 @@ class Service
public function __construct(array $service)
{
$this->service = $service;
$ports = (isset($this->service['ports']) && is_array($this->service['ports'])) ? $this->service['ports'] : [];
$this->service['ports'] = [];
@@ -54,7 +54,7 @@ class Service
public function getImageVersion(): string
{
$image = $this->getImage();
return substr($image, ((int)strpos($image, ':'))+1);
return substr($image, ((int)strpos($image, ':')) + 1);
}
/**
+1 -1
View File
@@ -70,7 +70,7 @@ class Env
$output = '';
foreach ($this->vars as $key => $value) {
$output .= $key.'='.$value."\n";
$output .= $key . '=' . $value . "\n";
}
return $output;
+130
View File
@@ -0,0 +1,130 @@
<?php
namespace Appwrite\Event;
use Resque;
class Audit extends Event
{
protected string $resource = '';
protected string $mode = '';
protected string $userAgent = '';
protected string $ip = '';
public function __construct()
{
parent::__construct(Event::AUDITS_QUEUE_NAME, Event::AUDITS_CLASS_NAME);
}
/**
* Set resource for this audit event.
*
* @param string $resource
* @return self
*/
public function setResource(string $resource): self
{
$this->resource = $resource;
return $this;
}
/**
* Returns the set audit resource.
*
* @return string
*/
public function getResource(): string
{
return $this->resource;
}
/**
* Set mode for this audit event
*
* @param string $mode
* @return self
*/
public function setMode(string $mode): self
{
$this->mode = $mode;
return $this;
}
/**
* Returns the set audit mode.
*
* @return string
*/
public function getMode(): string
{
return $this->mode;
}
/**
* Set user agent for this audit event.
*
* @param string $userAgent
* @return self
*/
public function setUserAgent(string $userAgent): self
{
$this->userAgent = $userAgent;
return $this;
}
/**
* Returns the set audit user agent.
*
* @return string
*/
public function getUserAgent(): string
{
return $this->userAgent;
}
/**
* Set IP for this audit event.
*
* @param string $ip
* @return self
*/
public function setIP(string $ip): self
{
$this->ip = $ip;
return $this;
}
/**
* Returns the set audit IP.
*
* @return string
*/
public function getIP(): string
{
return $this->ip;
}
/**
* Executes the event and sends it to the audit worker.
*
* @return string|bool
* @throws \InvalidArgumentException
*/
public function trigger(): string|bool
{
return Resque::enqueue($this->queue, $this->class, [
'project' => $this->project,
'user' => $this->user,
'payload' => $this->payload,
'resource' => $this->resource,
'mode' => $this->mode,
'ip' => $this->ip,
'userAgent' => $this->userAgent,
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
]);
}
}
+103
View File
@@ -0,0 +1,103 @@
<?php
namespace Appwrite\Event;
use Resque;
use Utopia\Database\Document;
class Build extends Event
{
protected string $type = '';
protected ?Document $resource = null;
protected ?Document $deployment = null;
public function __construct()
{
parent::__construct(Event::BUILDS_QUEUE_NAME, Event::BUILDS_CLASS_NAME);
}
/**
* Sets resource document for the build event.
*
* @param Document $resource
* @return self
*/
public function setResource(Document $resource): self
{
$this->resource = $resource;
return $this;
}
/**
* Returns set resource document for the build event.
*
* @return null|Document
*/
public function getResource(): ?Document
{
return $this->resource;
}
/**
* Sets deployment for the build event.
*
* @param Document $deployment
* @return self
*/
public function setDeployment(Document $deployment): self
{
$this->deployment = $deployment;
return $this;
}
/**
* Returns set deployment for the build event.
*
* @return null|Document
*/
public function getDeployment(): ?Document
{
return $this->deployment;
}
/**
* Sets type for the build event.
*
* @param string $type Can be `BUILD_TYPE_DEPLOYMENT` or `BUILD_TYPE_RETRY`.
* @return self
*/
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
/**
* Returns set type for the function event.
*
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* Executes the function event and sends it to the functions worker.
*
* @return string|bool
* @throws \InvalidArgumentException
*/
public function trigger(): string|bool
{
return Resque::enqueue($this->queue, $this->class, [
'project' => $this->project,
'resource' => $this->resource,
'deployment' => $this->deployment,
'type' => $this->type
]);
}
}
+78
View File
@@ -0,0 +1,78 @@
<?php
namespace Appwrite\Event;
use Resque;
use Utopia\Database\Document;
class Certificate extends Event
{
protected bool $skipRenewCheck = false;
protected ?Document $domain = null;
public function __construct()
{
parent::__construct(Event::CERTIFICATES_QUEUE_NAME, Event::CERTIFICATES_CLASS_NAME);
}
/**
* Set domain for this certificates event.
*
* @param Document $domain
* @return self
*/
public function setDomain(Document $domain): self
{
$this->domain = $domain;
return $this;
}
/**
* Returns the set domain for this certificate event.
*
* @return null|Document
*/
public function getDomain(): ?Document
{
return $this->domain;
}
/**
* Set if the certificate needs to be validated.
*
* @param bool $skipRenewCheck
* @return self
*/
public function setSkipRenewCheck(bool $skipRenewCheck): self
{
$this->skipRenewCheck = $skipRenewCheck;
return $this;
}
/**
* Return if the certificate needs be validated.
*
* @return bool
*/
public function getSkipRenewCheck(): bool
{
return $this->skipRenewCheck;
}
/**
* Executes the event and sends it to the certificates worker.
*
* @return string|bool
* @throws \InvalidArgumentException
*/
public function trigger(): string|bool
{
return Resque::enqueue($this->queue, $this->class, [
'project' => $this->project,
'domain' => $this->domain,
'skipRenewCheck' => $this->skipRenewCheck
]);
}
}
+117
View File
@@ -0,0 +1,117 @@
<?php
namespace Appwrite\Event;
use Resque;
use Utopia\Database\Document;
class Database extends Event
{
protected string $type = '';
protected ?Document $database = null;
protected ?Document $collection = null;
protected ?Document $document = null;
public function __construct()
{
parent::__construct(Event::DATABASE_QUEUE_NAME, Event::DATABASE_CLASS_NAME);
}
/**
* Sets the type for this database event (use the constants starting with DATABASE_TYPE_*).
*
* @param string $type
* @return self
*/
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
/**
* Returns the set type for the database event.
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* Set the database for this event
*
* @param Document $database
* @return self
*/
public function setDatabase(Document $database): self
{
$this->database = $database;
return $this;
}
/**
* Set the collection for this database event.
*
* @param Document $collection
* @return self
*/
public function setCollection(Document $collection): self
{
$this->collection = $collection;
return $this;
}
/**
* Returns set collection for this event.
*
* @return null|Document
*/
public function getCollection(): ?Document
{
return $this->collection;
}
/**
* Set the document for this database event.
*
* @param Document $document
* @return self
*/
public function setDocument(Document $document): self
{
$this->document = $document;
return $this;
}
/**
* Returns set document for this database event.
* @return null|Document
*/
public function getDocument(): ?Document
{
return $this->document;
}
/**
* Executes the event and send it to the database worker.
*
* @return string|bool
* @throws \InvalidArgumentException
*/
public function trigger(): string|bool
{
return Resque::enqueue($this->queue, $this->class, [
'project' => $this->project,
'user' => $this->user,
'type' => $this->type,
'collection' => $this->collection,
'document' => $this->document,
'database' => $this->database,
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
]);
}
}
+123
View File
@@ -0,0 +1,123 @@
<?php
namespace Appwrite\Event;
use Resque;
use Utopia\Database\Document;
class Delete extends Event
{
protected string $type = '';
protected ?int $timestamp = null;
protected ?int $timestamp1d = null;
protected ?int $timestamp30m = null;
protected ?Document $document = null;
public function __construct()
{
parent::__construct(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME);
}
/**
* Sets the type for the delete event (use the constants starting with DELETE_TYPE_*).
*
* @param string $type
* @return self
*/
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
/**
* Returns the set type for the delete event.
*
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* Set timestamp.
*
* @param int $timestamp
* @return self
*/
public function setTimestamp(int $timestamp): self
{
$this->timestamp = $timestamp;
return $this;
}
/**
* Set timestamp for 1 day interval.
*
* @param int $timestamp
* @return self
*/
public function setTimestamp1d(int $timestamp): self
{
$this->timestamp1d = $timestamp;
return $this;
}
/**
* Sets timestamp for 30m interval.
*
* @param int $timestamp
* @return self
*/
public function setTimestamp30m(int $timestamp): self
{
$this->timestamp30m = $timestamp;
return $this;
}
/**
* Sets the document for the delete event.
*
* @param Document $document
* @return self
*/
public function setDocument(Document $document): self
{
$this->document = $document;
return $this;
}
/**
* Returns the set document for the delete event.
*
* @return null|Document
*/
public function getDocument(): ?Document
{
return $this->document;
}
/**
* Executes this event and sends it to the deletes worker.
*
* @return string|bool
* @throws \InvalidArgumentException
*/
public function trigger(): string|bool
{
return Resque::enqueue($this->queue, $this->class, [
'project' => $this->project,
'type' => $this->type,
'document' => $this->document,
'timestamp' => $this->timestamp,
'timestamp1d' => $this->timestamp1d,
'timestamp30m' => $this->timestamp30m
]);
}
}
+385 -54
View File
@@ -2,58 +2,52 @@
namespace Appwrite\Event;
use InvalidArgumentException;
use Resque;
use Utopia\Database\Document;
class Event
{
public const DATABASE_QUEUE_NAME = 'v1-database';
public const DATABASE_CLASS_NAME = 'DatabaseV1';
const DATABASE_QUEUE_NAME= 'v1-database';
const DATABASE_CLASS_NAME = 'DatabaseV1';
public const DELETE_QUEUE_NAME = 'v1-deletes';
public const DELETE_CLASS_NAME = 'DeletesV1';
const DELETE_QUEUE_NAME = 'v1-deletes';
const DELETE_CLASS_NAME = 'DeletesV1';
public const AUDITS_QUEUE_NAME = 'v1-audits';
public const AUDITS_CLASS_NAME = 'AuditsV1';
const AUDITS_QUEUE_NAME = 'v1-audits';
const AUDITS_CLASS_NAME = 'AuditsV1';
public const MAILS_QUEUE_NAME = 'v1-mails';
public const MAILS_CLASS_NAME = 'MailsV1';
const USAGE_QUEUE_NAME = 'v1-usage';
const USAGE_CLASS_NAME = 'UsageV1';
public const FUNCTIONS_QUEUE_NAME = 'v1-functions';
public const FUNCTIONS_CLASS_NAME = 'FunctionsV1';
const MAILS_QUEUE_NAME = 'v1-mails';
const MAILS_CLASS_NAME = 'MailsV1';
public const WEBHOOK_QUEUE_NAME = 'v1-webhooks';
public const WEBHOOK_CLASS_NAME = 'WebhooksV1';
const FUNCTIONS_QUEUE_NAME = 'v1-functions';
const FUNCTIONS_CLASS_NAME = 'FunctionsV1';
public const CERTIFICATES_QUEUE_NAME = 'v1-certificates';
public const CERTIFICATES_CLASS_NAME = 'CertificatesV1';
const WEBHOOK_QUEUE_NAME = 'v1-webhooks';
const WEBHOOK_CLASS_NAME = 'WebhooksV1';
public const BUILDS_QUEUE_NAME = 'v1-builds';
public const BUILDS_CLASS_NAME = 'BuildsV1';
const CERTIFICATES_QUEUE_NAME = 'v1-certificates';
const CERTIFICATES_CLASS_NAME = 'CertificatesV1';
public const MESSAGING_QUEUE_NAME = 'v1-messaging';
public const MESSAGING_CLASS_NAME = 'MessagingV1';
const BUILDS_QUEUE_NAME = 'v1-builds';
const BUILDS_CLASS_NAME = 'BuildsV1';
/**
* @var string
*/
protected $queue = '';
protected string $queue = '';
protected string $class = '';
protected string $event = '';
protected array $params = [];
protected array $payload = [];
protected array $context = [];
protected ?Document $project = null;
protected ?Document $user = null;
/**
* @var string
*/
protected $class = '';
/**
* @var array
*/
protected $params = [];
/**
* Event constructor.
*
* @param string $queue
* @param string $class
* @return void
*/
public function __construct(string $queue, string $class)
{
@@ -62,48 +56,175 @@ class Event
}
/**
* Set queue used for this event.
*
* @param string $queue
* return $this
* @return Event
*/
public function setQueue(string $queue): self
{
$this->queue = $queue;
return $this;
}
/**
* Get queue used for this event.
*
* @return string
*/
public function getQueue()
public function getQueue(): string
{
return $this->queue;
}
/**
* @param string $class
* return $this
* Set event name used for this event.
* @param string $event
* @return Event
*/
public function setClass(string $class): self
public function setEvent(string $event): self
{
$this->class = $class;
$this->event = $event;
return $this;
}
/**
* Get event name used for this event.
*
* @return string
*/
public function getClass()
public function getEvent(): string
{
return $this->event;
}
/**
* Set project for this event.
*
* @param Document $project
* @return self
*/
public function setProject(Document $project): self
{
$this->project = $project;
return $this;
}
/**
* Get project for this event.
*
* @return Document
*/
public function getProject(): Document
{
return $this->project;
}
/**
* Set user for this event.
*
* @param Document $user
* @return self
*/
public function setUser(Document $user): self
{
$this->user = $user;
return $this;
}
/**
* Get project for this event.
*
* @return Document
*/
public function getUser(): Document
{
return $this->user;
}
/**
* Set payload for this event.
*
* @param array $payload
* @return self
*/
public function setPayload(array $payload): self
{
$this->payload = $payload;
return $this;
}
/**
* Get payload for this event.
*
* @return array
*/
public function getPayload(): array
{
return $this->payload;
}
/**
* Set context for this event.
*
* @param string $key
* @param Document $context
* @return self
*/
public function setContext(string $key, Document $context): self
{
$this->context[$key] = $context;
return $this;
}
/**
* Get context for this event.
*
* @param string $key
*
* @return null|Document
*/
public function getContext(string $key): ?Document
{
return $this->context[$key] ?? null;
}
/**
* Set class used for this event.
* @param string $class
* @return self
*/
public function setClass(string $class): self
{
$this->class = $class;
return $this;
}
/**
* Get class used for this event.
*
* @return string
*/
public function getClass(): string
{
return $this->class;
}
/**
* @param string $key
* @param mixed $value
* Set param of event.
*
* @return $this
* @param string $key
* @param mixed $value
* @return self
*/
public function setParam(string $key, $value): self
public function setParam(string $key, mixed $value): self
{
$this->params[$key] = $value;
@@ -111,29 +232,239 @@ class Event
}
/**
* @param string $key
* Get param of event.
*
* @return mixed|null
* @param string $key
* @return mixed
*/
public function getParam(string $key)
public function getParam(string $key): mixed
{
return (isset($this->params[$key])) ? $this->params[$key] : null;
return $this->params[$key] ?? null;
}
/**
* Get all params of the event.
*
* @return array
*/
public function getParams(): array
{
return $this->params;
}
/**
* Execute Event.
*
* @return string|bool
* @throws InvalidArgumentException
*/
public function trigger(): void
public function trigger(): string|bool
{
Resque::enqueue($this->queue, $this->class, $this->params);
$this->reset();
return Resque::enqueue($this->queue, $this->class, [
'project' => $this->project,
'user' => $this->user,
'payload' => $this->payload,
'context' => $this->context,
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
]);
}
/**
* Resets event.
*
* @return self
*/
public function reset(): self
{
$this->params = [];
return $this;
}
/**
* Parses event pattern and returns the parts in their respective section.
*
* @param string $pattern
* @return array
*/
public static function parseEventPattern(string $pattern): array
{
$parts = \explode('.', $pattern);
$count = \count($parts);
/**
* Identify all sections of the pattern.
*/
$type = $parts[0] ?? false;
$resource = $parts[1] ?? false;
$hasSubResource = $count > 3 && \str_starts_with($parts[3], '[');
$hasSubSubResource = $count > 5 && \str_starts_with($parts[5], '[') && $hasSubResource;
if ($hasSubResource) {
$subType = $parts[2];
$subResource = $parts[3];
}
if ($hasSubSubResource) {
$subSubType = $parts[4];
$subSubResource = $parts[5];
if ($count == 8) {
$attribute = $parts[7];
}
}
if ($hasSubResource && !$hasSubSubResource) {
if ($count === 6) {
$attribute = $parts[5];
}
}
if (!$hasSubResource) {
if ($count === 4) {
$attribute = $parts[3];
}
}
$subType ??= false;
$subResource ??= false;
$subSubType ??= false;
$subSubResource ??= false;
$attribute ??= false;
$action = match (true) {
!$hasSubResource && $count > 2 => $parts[2],
$hasSubSubResource => $parts[6] ?? false,
$hasSubResource && $count > 4 => $parts[4],
default => false
};
return [
'type' => $type,
'resource' => $resource,
'subType' => $subType,
'subResource' => $subResource,
'subSubType' => $subSubType,
'subSubResource' => $subSubResource,
'action' => $action,
'attribute' => $attribute,
];
}
/**
* Generates all possible events from a pattern.
*
* @param string $pattern
* @param array $params
* @return array
* @throws \InvalidArgumentException
*/
public static function generateEvents(string $pattern, array $params = []): array
{
// $params = \array_filter($params, fn($param) => !\is_array($param));
$paramKeys = \array_keys($params);
$paramValues = \array_values($params);
$patterns = [];
$parsed = self::parseEventPattern($pattern);
$type = $parsed['type'];
$resource = $parsed['resource'];
$subType = $parsed['subType'];
$subResource = $parsed['subResource'];
$subSubType = $parsed['subSubType'];
$subSubResource = $parsed['subSubResource'];
$action = $parsed['action'];
$attribute = $parsed['attribute'];
if ($resource && !\in_array(\trim($resource, "\[\]"), $paramKeys)) {
throw new InvalidArgumentException("{$resource} is missing from the params.");
}
if ($subResource && !\in_array(\trim($subResource, "\[\]"), $paramKeys)) {
throw new InvalidArgumentException("{$subResource} is missing from the params.");
}
if ($subSubResource && !\in_array(\trim($subSubResource, "\[\]"), $paramKeys)) {
throw new InvalidArgumentException("{$subSubResource} is missing from the params.");
}
/**
* Create all possible patterns including placeholders.
*/
if ($action) {
if ($subSubResource) {
if ($attribute) {
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $subSubType, $subSubResource, $action, $attribute]);
}
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $subSubType, $subSubResource, $action]);
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $subSubType, $subSubResource]);
} elseif ($subResource) {
if ($attribute) {
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $action, $attribute]);
}
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $action]);
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource]);
} else {
$patterns[] = \implode('.', [$type, $resource, $action]);
}
if ($attribute) {
$patterns[] = \implode('.', [$type, $resource, $action, $attribute]);
}
}
if ($subSubResource) {
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $subSubType, $subSubResource]);
}
if ($subResource) {
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource]);
}
$patterns[] = \implode('.', [$type, $resource]);
/**
* Removes all duplicates.
*/
$patterns = \array_unique($patterns);
/**
* Set all possible values of the patterns and replace placeholders.
*/
$events = [];
foreach ($patterns as $eventPattern) {
$events[] = \str_replace($paramKeys, $paramValues, $eventPattern);
$events[] = \str_replace($paramKeys, '*', $eventPattern);
foreach ($paramKeys as $key) {
foreach ($paramKeys as $current) {
if ($subSubResource) {
foreach ($paramKeys as $subCurrent) {
if ($subCurrent === $current || $subCurrent === $key) {
continue;
}
$filtered1 = \array_filter($paramKeys, fn(string $k) => $k === $subCurrent);
$events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered1, '*', $eventPattern));
$filtered2 = \array_filter($paramKeys, fn(string $k) => $k === $current);
$events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered2, '*', \str_replace($filtered1, '*', $eventPattern)));
$events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered2, '*', $eventPattern));
}
} else {
if ($current === $key) {
continue;
}
$filtered = \array_filter($paramKeys, fn(string $k) => $k === $current);
$events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered, '*', $eventPattern));
}
}
}
}
/**
* Remove [] from the events.
*/
$events = \array_map(fn (string $event) => \str_replace(['[', ']'], '', $event), $events);
$events = \array_unique($events);
/**
* Force a non-assoc array.
*/
return \array_values($events);
}
}
+178
View File
@@ -0,0 +1,178 @@
<?php
namespace Appwrite\Event;
use DateTime;
use Resque;
use ResqueScheduler;
use Utopia\Database\Document;
class Func extends Event
{
protected string $jwt = '';
protected string $type = '';
protected string $data = '';
protected ?Document $function = null;
protected ?Document $execution = null;
public function __construct()
{
parent::__construct(Event::FUNCTIONS_QUEUE_NAME, Event::FUNCTIONS_CLASS_NAME);
}
/**
* Sets function document for the function event.
*
* @param Document $function
* @return self
*/
public function setFunction(Document $function): self
{
$this->function = $function;
return $this;
}
/**
* Returns set function document for the function event.
*
* @return null|Document
*/
public function getFunction(): ?Document
{
return $this->function;
}
/**
* Sets execution for the function event.
*
* @param Document $execution
* @return self
*/
public function setExecution(Document $execution): self
{
$this->execution = $execution;
return $this;
}
/**
* Returns set execution for the function event.
*
* @return null|Document
*/
public function getExecution(): ?Document
{
return $this->execution;
}
/**
* Sets type for the function event.
*
* @param string $type Can be `schedule`, `event` or `http`.
* @return self
*/
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
/**
* Returns set type for the function event.
*
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* Sets custom data for the function event.
*
* @param string $data
* @return self
*/
public function setData(string $data): self
{
$this->data = $data;
return $this;
}
/**
* Returns set custom data for the function event.
*
* @return string
*/
public function getData(): string
{
return $this->data;
}
/**
* Sets JWT for the function event.
*
* @param string $jwt
* @return self
*/
public function setJWT(string $jwt): self
{
$this->jwt = $jwt;
return $this;
}
/**
* Returns set JWT for the function event.
*
* @return string
*/
public function getJWT(): string
{
return $this->jwt;
}
/**
* Executes the function event and sends it to the functions worker.
*
* @return string|bool
* @throws \InvalidArgumentException
*/
public function trigger(): string|bool
{
return Resque::enqueue($this->queue, $this->class, [
'project' => $this->project,
'user' => $this->user,
'function' => $this->function,
'execution' => $this->execution,
'type' => $this->type,
'jwt' => $this->jwt,
'payload' => $this->payload,
'data' => $this->data
]);
}
/**
* Schedules the function event and schedules it in the functions worker queue.
*
* @param \DateTime|int $at
* @return void
* @throws \Resque_Exception
* @throws \ResqueScheduler_InvalidTimestampException
*/
public function schedule(DateTime|int $at): void
{
ResqueScheduler::enqueueAt($at, $this->queue, $this->class, [
'project' => $this->project,
'user' => $this->user,
'function' => $this->function,
'execution' => $this->execution,
'type' => $this->type,
'payload' => $this->payload,
'data' => $this->data
]);
}
}
+181
View File
@@ -0,0 +1,181 @@
<?php
namespace Appwrite\Event;
use Resque;
use Utopia\Database\Document;
class Mail extends Event
{
protected string $recipient = '';
protected string $url = '';
protected string $type = '';
protected string $name = '';
protected string $locale = '';
protected ?Document $team = null;
public function __construct()
{
parent::__construct(Event::MAILS_QUEUE_NAME, Event::MAILS_CLASS_NAME);
}
/**
* Sets team for the mail event.
*
* @param Document $team
* @return self
*/
public function setTeam(Document $team): self
{
$this->team = $team;
return $this;
}
/**
* Returns set team for the mail event.
*
* @return null|Document
*/
public function getTeam(): ?Document
{
return $this->team;
}
/**
* Sets recipient for the mail event.
*
* @param string $recipient
* @return self
*/
public function setRecipient(string $recipient): self
{
$this->recipient = $recipient;
return $this;
}
/**
* Returns set recipient for mail event.
*
* @return string
*/
public function getRecipient(): string
{
return $this->recipient;
}
/**
* Sets url for the mail event.
*
* @param string $url
* @return self
*/
public function setUrl(string $url): self
{
$this->url = $url;
return $this;
}
/**
* Returns set url for the mail event.
*
* @return string
*/
public function getURL(): string
{
return $this->url;
}
/**
* Sets type for the mail event (use the constants starting with MAIL_TYPE_*).
*
* @param string $type
* @return self
*/
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
/**
* Returns set type for the mail event.
*
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* Sets name for the mail event.
*
* @param string $name
* @return self
*/
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* Returns set name for the mail event.
*
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* Sets locale for the mail event.
*
* @param string $locale
* @return self
*/
public function setLocale(string $locale): self
{
$this->locale = $locale;
return $this;
}
/**
* Returns set locale for the mail event.
*
* @return string
*/
public function getLocale(): string
{
return $this->locale;
}
/**
* Executes the event and sends it to the mails worker.
*
* @return string|bool
* @throws \InvalidArgumentException
*/
public function trigger(): string|bool
{
return Resque::enqueue($this->queue, $this->class, [
'project' => $this->project,
'user' => $this->user,
'payload' => $this->payload,
'recipient' => $this->recipient,
'url' => $this->url,
'locale' => $this->locale,
'type' => $this->type,
'name' => $this->name,
'team' => $this->team,
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
]);
}
}
+80
View File
@@ -0,0 +1,80 @@
<?php
namespace Appwrite\Event;
use Resque;
class Phone extends Event
{
protected string $recipient = '';
protected string $message = '';
public function __construct()
{
parent::__construct(Event::MESSAGING_QUEUE_NAME, Event::MESSAGING_CLASS_NAME);
}
/**
* Sets recipient for the messaging event.
*
* @param string $recipient
* @return self
*/
public function setRecipient(string $recipient): self
{
$this->recipient = $recipient;
return $this;
}
/**
* Returns set recipient for this messaging event.
*
* @return string
*/
public function getRecipient(): string
{
return $this->recipient;
}
/**
* Sets url for the messaging event.
*
* @param string $message
* @return self
*/
public function setMessage(string $message): self
{
$this->message = $message;
return $this;
}
/**
* Returns set url for the messaging event.
*
* @return string
*/
public function getMessage(): string
{
return $this->message;
}
/**
* Executes the event and sends it to the messaging worker.
*
* @return string|bool
* @throws \InvalidArgumentException
*/
public function trigger(): string|bool
{
return Resque::enqueue($this->queue, $this->class, [
'project' => $this->project,
'user' => $this->user,
'payload' => $this->payload,
'recipient' => $this->recipient,
'message' => $this->message,
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
]);
}
}
+144
View File
@@ -0,0 +1,144 @@
<?php
namespace Appwrite\Event\Validator;
use Utopia\Config\Config;
use Utopia\Validator;
class Event extends Validator
{
/**
* Get Description.
*
* Returns validator description
*
* @return string
*/
public function getDescription(): string
{
return 'Event is not valid.';
}
/**
* Is valid.
*
* @param mixed $value
*
* @return bool
*/
public function isValid($value): bool
{
$events = Config::getParam('events', []);
$parts = \explode('.', $value);
$count = \count($parts);
if ($count < 2 || $count > 7) {
return false;
}
/**
* Identify all sections of the pattern.
*/
$type = $parts[0] ?? false;
$resource = $parts[1] ?? false;
$hasSubResource = $count > 3 && ($events[$type]['$resource'] ?? false) && ($events[$type][$parts[2]]['$resource'] ?? false);
$hasSubSubResource = $count > 5 && $hasSubResource && ($events[$type][$parts[2]][$parts[4]]['$resource'] ?? false);
if (!$type || !$resource) {
return false;
}
if ($hasSubResource) {
$subType = $parts[2];
$subResource = $parts[3];
}
if ($hasSubSubResource) {
$subSubType = $parts[4];
$subSubResource = $parts[5];
if ($count === 8) {
$attribute = $parts[7];
}
}
if ($hasSubResource && !$hasSubSubResource) {
if ($count === 6) {
$attribute = $parts[5];
}
}
if (!$hasSubResource) {
if ($count === 4) {
$attribute = $parts[3];
}
}
$subSubType ??= false;
$subSubResource ??= false;
$subType ??= false;
$subResource ??= false;
$attribute ??= false;
$action = match (true) {
!$hasSubResource && $count > 2 => $parts[2],
$hasSubSubResource => $parts[6] ?? false,
$hasSubResource && $count > 4 => $parts[4],
default => false
};
if (!\array_key_exists($type, $events)) {
return false;
}
if ($subType) {
if ($action && !\array_key_exists($action, $events[$type][$subType])) {
return false;
}
if (!($subResource) || !\array_key_exists($subType, $events[$type])) {
return false;
}
} else {
if ($action && !\array_key_exists($action, $events[$type])) {
return false;
}
}
if ($attribute) {
if (($subType)) {
if (!\array_key_exists($attribute, $events[$type][$subType][$action])) {
return false;
}
} else {
if (!\array_key_exists($attribute, $events[$type][$action])) {
return false;
}
}
}
return true;
}
/**
* Is array
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_STRING;
}
}
+110 -101
View File
@@ -6,10 +6,10 @@ class Exception extends \Exception
{
/**
* Error Codes
*
* Naming the error types based on the following convention
*
* Naming the error types based on the following convention
* <ENTITY>_<ERROR_TYPE>
*
*
* Appwrite has the follwing entities:
* - General
* - Users
@@ -32,133 +32,143 @@ class Exception extends \Exception
*/
/** General */
const GENERAL_UNKNOWN = 'general_unknown';
const GENERAL_MOCK = 'general_mock';
const GENERAL_ACCESS_FORBIDDEN = 'general_access_forbidden';
const GENERAL_UNKNOWN_ORIGIN = 'general_unknown_origin';
const GENERAL_SERVICE_DISABLED = 'general_service_disabled';
const GENERAL_UNAUTHORIZED_SCOPE = 'general_unauthorized_scope';
const GENERAL_RATE_LIMIT_EXCEEDED = 'general_rate_limit_exceeded';
const GENERAL_SMTP_DISABLED = 'general_smtp_disabled';
const GENERAL_ARGUMENT_INVALID = 'general_argument_invalid';
const GENERAL_QUERY_LIMIT_EXCEEDED = 'general_query_limit_exceeded';
const GENERAL_QUERY_INVALID = 'general_query_invalid';
const GENERAL_ROUTE_NOT_FOUND = 'general_route_not_found';
const GENERAL_CURSOR_NOT_FOUND = 'general_cursor_not_found';
const GENERAL_SERVER_ERROR = 'general_server_error';
public const GENERAL_UNKNOWN = 'general_unknown';
public const GENERAL_MOCK = 'general_mock';
public const GENERAL_ACCESS_FORBIDDEN = 'general_access_forbidden';
public const GENERAL_UNKNOWN_ORIGIN = 'general_unknown_origin';
public const GENERAL_SERVICE_DISABLED = 'general_service_disabled';
public const GENERAL_UNAUTHORIZED_SCOPE = 'general_unauthorized_scope';
public const GENERAL_RATE_LIMIT_EXCEEDED = 'general_rate_limit_exceeded';
public const GENERAL_SMTP_DISABLED = 'general_smtp_disabled';
public const GENERAL_PHONE_DISABLED = 'general_phone_disabled';
public const GENERAL_ARGUMENT_INVALID = 'general_argument_invalid';
public const GENERAL_QUERY_LIMIT_EXCEEDED = 'general_query_limit_exceeded';
public const GENERAL_QUERY_INVALID = 'general_query_invalid';
public const GENERAL_ROUTE_NOT_FOUND = 'general_route_not_found';
public const GENERAL_CURSOR_NOT_FOUND = 'general_cursor_not_found';
public const GENERAL_SERVER_ERROR = 'general_server_error';
public const GENERAL_PROTOCOL_UNSUPPORTED = 'general_protocol_unsupported';
/** Users */
const USER_COUNT_EXCEEDED = 'user_count_exceeded';
const USER_JWT_INVALID = 'user_jwt_invalid';
const USER_ALREADY_EXISTS = 'user_already_exists';
const USER_BLOCKED = 'user_blocked';
const USER_INVALID_TOKEN = 'user_invalid_token';
const USER_PASSWORD_RESET_REQUIRED = 'user_password_reset_required';
const USER_EMAIL_NOT_WHITELISTED = 'user_email_not_whitelisted';
const USER_IP_NOT_WHITELISTED = 'user_ip_not_whitelisted';
const USER_INVALID_CREDENTIALS = 'user_invalid_credentials';
const USER_ANONYMOUS_CONSOLE_PROHIBITED = 'user_anonymous_console_prohibited';
const USER_SESSION_ALREADY_EXISTS = 'user_session_already_exists';
const USER_NOT_FOUND = 'user_not_found';
const USER_EMAIL_ALREADY_EXISTS = 'user_email_already_exists';
const USER_PASSWORD_MISMATCH = 'user_password_mismatch';
const USER_SESSION_NOT_FOUND = 'user_session_not_found';
const USER_UNAUTHORIZED = 'user_unauthorized';
const USER_AUTH_METHOD_UNSUPPORTED = 'user_auth_method_unsupported';
public const USER_COUNT_EXCEEDED = 'user_count_exceeded';
public const USER_JWT_INVALID = 'user_jwt_invalid';
public const USER_ALREADY_EXISTS = 'user_already_exists';
public const USER_BLOCKED = 'user_blocked';
public const USER_INVALID_TOKEN = 'user_invalid_token';
public const USER_PASSWORD_RESET_REQUIRED = 'user_password_reset_required';
public const USER_EMAIL_NOT_WHITELISTED = 'user_email_not_whitelisted';
public const USER_IP_NOT_WHITELISTED = 'user_ip_not_whitelisted';
public const USER_INVALID_CREDENTIALS = 'user_invalid_credentials';
public const USER_ANONYMOUS_CONSOLE_PROHIBITED = 'user_anonymous_console_prohibited';
public const USER_SESSION_ALREADY_EXISTS = 'user_session_already_exists';
public const USER_NOT_FOUND = 'user_not_found';
public const USER_EMAIL_ALREADY_EXISTS = 'user_email_already_exists';
public const USER_PASSWORD_MISMATCH = 'user_password_mismatch';
public const USER_SESSION_NOT_FOUND = 'user_session_not_found';
public const USER_UNAUTHORIZED = 'user_unauthorized';
public const USER_AUTH_METHOD_UNSUPPORTED = 'user_auth_method_unsupported';
public const USER_PHONE_ALREADY_EXISTS = 'user_phone_already_exists';
public const USER_PHONE_NOT_FOUND = 'user_phone_not_found';
/** Teams */
const TEAM_NOT_FOUND = 'team_not_found';
const TEAM_INVITE_ALREADY_EXISTS = 'team_invite_already_exists';
const TEAM_INVITE_NOT_FOUND = 'team_invite_not_found';
const TEAM_INVALID_SECRET = 'team_invalid_secret';
const TEAM_MEMBERSHIP_MISMATCH = 'team_membership_mismatch';
const TEAM_INVITE_MISMATCH = 'team_invite_mismatch';
public const TEAM_NOT_FOUND = 'team_not_found';
public const TEAM_INVITE_ALREADY_EXISTS = 'team_invite_already_exists';
public const TEAM_INVITE_NOT_FOUND = 'team_invite_not_found';
public const TEAM_INVALID_SECRET = 'team_invalid_secret';
public const TEAM_MEMBERSHIP_MISMATCH = 'team_membership_mismatch';
public const TEAM_INVITE_MISMATCH = 'team_invite_mismatch';
/** Membership */
const MEMBERSHIP_NOT_FOUND = 'membership_not_found';
public const MEMBERSHIP_NOT_FOUND = 'membership_not_found';
/** Avatars */
const AVATAR_SET_NOT_FOUND = 'avatar_set_not_found';
const AVATAR_NOT_FOUND = 'avatar_not_found';
const AVATAR_IMAGE_NOT_FOUND = 'avatar_image_not_found';
const AVATAR_REMOTE_URL_FAILED = 'avatar_remote_url_failed';
const AVATAR_ICON_NOT_FOUND = 'avatar_icon_not_found';
public const AVATAR_SET_NOT_FOUND = 'avatar_set_not_found';
public const AVATAR_NOT_FOUND = 'avatar_not_found';
public const AVATAR_IMAGE_NOT_FOUND = 'avatar_image_not_found';
public const AVATAR_REMOTE_URL_FAILED = 'avatar_remote_url_failed';
public const AVATAR_ICON_NOT_FOUND = 'avatar_icon_not_found';
/** Storage */
const STORAGE_FILE_NOT_FOUND = 'storage_file_not_found';
const STORAGE_DEVICE_NOT_FOUND = 'storage_device_not_found';
const STORAGE_FILE_EMPTY = 'storage_file_empty';
const STORAGE_FILE_TYPE_UNSUPPORTED = 'storage_file_type_unsupported';
const STORAGE_INVALID_FILE_SIZE = 'storage_invalid_file_size';
const STORAGE_INVALID_FILE = 'storage_invalid_file';
const STORAGE_BUCKET_ALREADY_EXISTS = 'storage_bucket_already_exists';
const STORAGE_BUCKET_NOT_FOUND = 'storage_bucket_not_found';
const STORAGE_INVALID_CONTENT_RANGE = 'storage_invalid_content_range';
const STORAGE_INVALID_RANGE = 'storage_invalid_range';
public const STORAGE_FILE_NOT_FOUND = 'storage_file_not_found';
public const STORAGE_DEVICE_NOT_FOUND = 'storage_device_not_found';
public const STORAGE_FILE_EMPTY = 'storage_file_empty';
public const STORAGE_FILE_TYPE_UNSUPPORTED = 'storage_file_type_unsupported';
public const STORAGE_INVALID_FILE_SIZE = 'storage_invalid_file_size';
public const STORAGE_INVALID_FILE = 'storage_invalid_file';
public const STORAGE_BUCKET_ALREADY_EXISTS = 'storage_bucket_already_exists';
public const STORAGE_BUCKET_NOT_FOUND = 'storage_bucket_not_found';
public const STORAGE_INVALID_CONTENT_RANGE = 'storage_invalid_content_range';
public const STORAGE_INVALID_RANGE = 'storage_invalid_range';
/** Functions */
const FUNCTION_NOT_FOUND = 'function_not_found';
const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported';
public const FUNCTION_NOT_FOUND = 'function_not_found';
public const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported';
/** Deployments */
const DEPLOYMENT_NOT_FOUND = 'deployment_not_found';
public const DEPLOYMENT_NOT_FOUND = 'deployment_not_found';
/** Builds */
const BUILD_NOT_FOUND = 'build_not_found';
const BUILD_NOT_READY = 'build_not_ready';
const BUILD_IN_PROGRESS = 'build_in_progress';
public const BUILD_NOT_FOUND = 'build_not_found';
public const BUILD_NOT_READY = 'build_not_ready';
public const BUILD_IN_PROGRESS = 'build_in_progress';
/** Execution */
const EXECUTION_NOT_FOUND = 'execution_not_found';
public const EXECUTION_NOT_FOUND = 'execution_not_found';
/** Databases */
public const DATABASE_NOT_FOUND = 'database_not_found';
public const DATABASE_ALREADY_EXISTS = 'database_already_exists';
/** Collections */
const COLLECTION_NOT_FOUND = 'collection_not_found';
const COLLECTION_ALREADY_EXISTS = 'collection_already_exists';
const COLLECTION_LIMIT_EXCEEDED = 'collection_limit_exceeded';
public const COLLECTION_NOT_FOUND = 'collection_not_found';
public const COLLECTION_ALREADY_EXISTS = 'collection_already_exists';
public const COLLECTION_LIMIT_EXCEEDED = 'collection_limit_exceeded';
/** Documents */
const DOCUMENT_NOT_FOUND = 'document_not_found';
const DOCUMENT_INVALID_STRUCTURE = 'document_invalid_structure';
const DOCUMENT_MISSING_PAYLOAD = 'document_missing_payload';
const DOCUMENT_ALREADY_EXISTS = 'document_already_exists';
public const DOCUMENT_NOT_FOUND = 'document_not_found';
public const DOCUMENT_INVALID_STRUCTURE = 'document_invalid_structure';
public const DOCUMENT_MISSING_PAYLOAD = 'document_missing_payload';
public const DOCUMENT_ALREADY_EXISTS = 'document_already_exists';
/** Attribute */
const ATTRIBUTE_NOT_FOUND = 'attribute_not_found';
const ATTRIBUTE_UNKNOWN = 'attribute_unknown';
const ATTRIBUTE_NOT_AVAILABLE = 'attribute_not_available';
const ATTRIBUTE_FORMAT_UNSUPPORTED = 'attribute_format_unsupported';
const ATTRIBUTE_DEFAULT_UNSUPPORTED = 'attribute_default_unsupported';
const ATTRIBUTE_ALREADY_EXISTS = 'attribute_already_exists';
const ATTRIBUTE_LIMIT_EXCEEDED = 'attribute_limit_exceeded';
const ATTRIBUTE_VALUE_INVALID = 'attribute_value_invalid';
public const ATTRIBUTE_NOT_FOUND = 'attribute_not_found';
public const ATTRIBUTE_UNKNOWN = 'attribute_unknown';
public const ATTRIBUTE_NOT_AVAILABLE = 'attribute_not_available';
public const ATTRIBUTE_FORMAT_UNSUPPORTED = 'attribute_format_unsupported';
public const ATTRIBUTE_DEFAULT_UNSUPPORTED = 'attribute_default_unsupported';
public const ATTRIBUTE_ALREADY_EXISTS = 'attribute_already_exists';
public const ATTRIBUTE_LIMIT_EXCEEDED = 'attribute_limit_exceeded';
public const ATTRIBUTE_VALUE_INVALID = 'attribute_value_invalid';
/** Indexes */
const INDEX_NOT_FOUND = 'index_not_found';
const INDEX_LIMIT_EXCEEDED = 'index_limit_exceeded';
const INDEX_ALREADY_EXISTS = 'index_already_exists';
public const INDEX_NOT_FOUND = 'index_not_found';
public const INDEX_LIMIT_EXCEEDED = 'index_limit_exceeded';
public const INDEX_ALREADY_EXISTS = 'index_already_exists';
/** Projects */
const PROJECT_NOT_FOUND = 'project_not_found';
const PROJECT_UNKNOWN = 'project_unknown';
const PROJECT_PROVIDER_DISABLED = 'project_provider_disabled';
const PROJECT_PROVIDER_UNSUPPORTED = 'project_provider_unsupported';
const PROJECT_INVALID_SUCCESS_URL = 'project_invalid_success_url';
const PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url';
const PROJECT_MISSING_USER_ID = 'project_missing_user_id';
public const PROJECT_NOT_FOUND = 'project_not_found';
public const PROJECT_UNKNOWN = 'project_unknown';
public const PROJECT_PROVIDER_DISABLED = 'project_provider_disabled';
public const PROJECT_PROVIDER_UNSUPPORTED = 'project_provider_unsupported';
public const PROJECT_INVALID_SUCCESS_URL = 'project_invalid_success_url';
public const PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url';
public const PROJECT_MISSING_USER_ID = 'project_missing_user_id';
public const PROJECT_RESERVED_PROJECT = 'project_reserved_project';
public const PROJECT_KEY_EXPIRED = 'project_key_expired';
/** Webhooks */
const WEBHOOK_NOT_FOUND = 'webhook_not_found';
public const WEBHOOK_NOT_FOUND = 'webhook_not_found';
/** Keys */
const KEY_NOT_FOUND = 'key_not_found';
public const KEY_NOT_FOUND = 'key_not_found';
/** Platform */
const PLATFORM_NOT_FOUND = 'platform_not_found';
public const PLATFORM_NOT_FOUND = 'platform_not_found';
/** Domain */
const DOMAIN_NOT_FOUND = 'domain_not_found';
const DOMAIN_ALREADY_EXISTS = 'domain_already_exists';
const DOMAIN_VERIFICATION_FAILED = 'domain_verification_failed';
public const DOMAIN_NOT_FOUND = 'domain_not_found';
public const DOMAIN_ALREADY_EXISTS = 'domain_already_exists';
public const DOMAIN_VERIFICATION_FAILED = 'domain_verification_failed';
private $type = '';
@@ -172,9 +182,9 @@ class Exception extends \Exception
/**
* Get the type of the exception.
*
*
* @return string
*/
*/
public function getType(): string
{
return $this->type;
@@ -182,14 +192,13 @@ class Exception extends \Exception
/**
* Set the type of the exception.
*
*
* @param string $type
*
*
* @return void
*/
public function setType(string $type): void
{
$this->type = $type;
}
}
}
+2 -2
View File
@@ -99,8 +99,8 @@ class PDO extends PDONative
{
$this->pdo = new PDONative($this->dsn, $this->username, $this->passwd, $this->options);
echo '[PDO] MySQL connection restarted'.PHP_EOL;
echo '[PDO] MySQL connection restarted' . PHP_EOL;
// Connection settings
$this->pdo->setAttribute(PDONative::ATTR_DEFAULT_FETCH_MODE, PDONative::FETCH_ASSOC); // Return arrays
$this->pdo->setAttribute(PDONative::ATTR_ERRMODE, PDONative::ERRMODE_EXCEPTION); // Handle all errors with exceptions
+2 -2
View File
@@ -76,7 +76,7 @@ class PDOStatement extends PDOStatementNative
foreach ($this->values as $key => $set) {
$this->PDOStatement->bindValue($key, $set['value'], $set['data_type']);
}
foreach ($this->params as $key => $set) {
$this->PDOStatement->bindParam($key, $set['variable'], $set['data_type'], $set['length'], $set['driver_options']);
}
@@ -109,7 +109,7 @@ class PDOStatement extends PDOStatementNative
public function fetchAll(int $fetch_style = PDO::FETCH_BOTH, mixed ...$fetch_args)
{
$result = $this->PDOStatement->fetchAll();
return $result;
}
}
+3 -3
View File
@@ -4,7 +4,7 @@ namespace Appwrite\Messaging;
abstract class Adapter
{
public abstract function subscribe(string $projectId, mixed $identifier, array $roles, array $channels): void;
public abstract function unsubscribe(mixed $identifier): void;
public static abstract function send(string $projectId, array $payload, string $event, array $channels, array $roles, array $options): void;
abstract public function subscribe(string $projectId, mixed $identifier, array $roles, array $channels): void;
abstract public function unsubscribe(mixed $identifier): void;
abstract public static function send(string $projectId, array $payload, array $events, array $channels, array $roles, array $options): void;
}
+105 -104
View File
@@ -10,8 +10,8 @@ class Realtime extends Adapter
{
/**
* Connection Tree
*
* [CONNECTION_ID] ->
*
* [CONNECTION_ID] ->
* 'projectId' -> [PROJECT_ID]
* 'roles' -> [ROLE_x, ROLE_Y]
* 'channels' -> [CHANNEL_NAME_X, CHANNEL_NAME_Y, CHANNEL_NAME_Z]
@@ -20,13 +20,13 @@ class Realtime extends Adapter
/**
* Subscription Tree
*
* [PROJECT_ID] ->
* [ROLE_X] ->
*
* [PROJECT_ID] ->
* [ROLE_X] ->
* [CHANNEL_NAME_X] -> [CONNECTION_ID]
* [CHANNEL_NAME_Y] -> [CONNECTION_ID]
* [CHANNEL_NAME_Z] -> [CONNECTION_ID]
* [ROLE_Y] ->
* [ROLE_Y] ->
* [CHANNEL_NAME_X] -> [CONNECTION_ID]
* [CHANNEL_NAME_Y] -> [CONNECTION_ID]
* [CHANNEL_NAME_Z] -> [CONNECTION_ID]
@@ -35,12 +35,12 @@ class Realtime extends Adapter
/**
* Adds a subscription.
*
* @param string $projectId
* @param mixed $identifier
* @param array $roles
* @param array $channels
* @return void
*
* @param string $projectId
* @param mixed $identifier
* @param array $roles
* @param array $channels
* @return void
*/
public function subscribe(string $projectId, mixed $identifier, array $roles, array $channels): void
{
@@ -67,9 +67,9 @@ class Realtime extends Adapter
/**
* Removes Subscription.
*
*
* @param mixed $connection
* @return void
* @return void
*/
public function unsubscribe(mixed $connection): void
{
@@ -99,9 +99,9 @@ class Realtime extends Adapter
/**
* Checks if Channel has a subscriber.
* @param string $projectId
* @param string $role
* @param string $channel
* @param string $projectId
* @param string $role
* @param string $channel
* @return bool
*/
public function hasSubscriber(string $projectId, string $role, string $channel = ''): bool
@@ -118,18 +118,20 @@ class Realtime extends Adapter
}
/**
* Sends an event to the Realtime Server.
* @param string $projectId
* @param array $payload
* @param string $event
* @param array $channels
* @param array $roles
* @param array $options
* @return void
* Sends an event to the Realtime Server
* @param string $projectId
* @param array $payload
* @param string $event
* @param array $channels
* @param array $roles
* @param array $options
* @return void
*/
public static function send(string $projectId, array $payload, string $event, array $channels, array $roles, array $options = []): void
public static function send(string $projectId, array $payload, array $events, array $channels, array $roles, array $options = []): void
{
if (empty($channels) || empty($roles) || empty($projectId)) return;
if (empty($channels) || empty($roles) || empty($projectId)) {
return;
}
$permissionsChanged = array_key_exists('permissionsChanged', $options) && $options['permissionsChanged'];
$userId = array_key_exists('userId', $options) ? $options['userId'] : null;
@@ -142,7 +144,7 @@ class Realtime extends Adapter
'permissionsChanged' => $permissionsChanged,
'userId' => $userId,
'data' => [
'event' => $event,
'events' => $events,
'channels' => $channels,
'timestamp' => time(),
'payload' => $payload
@@ -152,15 +154,15 @@ class Realtime extends Adapter
/**
* Identifies the receivers of all subscriptions, based on the permissions and event.
*
*
* Example of performance with an event with user:XXX permissions and with X users spread across 10 different channels:
* - 0.014 ms (±6.88%) | 10 Connections / 100 Subscriptions
* - 0.070 ms (±3.71%) | 100 Connections / 1,000 Subscriptions
* - 0.014 ms (±6.88%) | 10 Connections / 100 Subscriptions
* - 0.070 ms (±3.71%) | 100 Connections / 1,000 Subscriptions
* - 0.846 ms (±2.74%) | 1,000 Connections / 10,000 Subscriptions
* - 10.866 ms (±1.01%) | 10,000 Connections / 100,000 Subscriptions
* - 110.201 ms (±2.32%) | 100,000 Connections / 1,000,000 Subscriptions
* - 1,121.328 ms (±0.84%) | 1,000,000 Connections / 10,000,000 Subscriptions
*
* - 1,121.328 ms (±0.84%) | 1,000,000 Connections / 10,000,000 Subscriptions
*
* @param array $event
*/
public function getSubscribers(array $event)
@@ -205,11 +207,11 @@ class Realtime extends Adapter
}
/**
* Converts the channels from the Query Params into an array.
* Converts the channels from the Query Params into an array.
* Also renames the account channel to account.USER_ID and removes all illegal account channel variations.
* @param array $channels
* @param string $userId
* @return array
* @param array $channels
* @param string $userId
* @return array
*/
public static function convertChannels(array $channels, string $userId): array
{
@@ -235,88 +237,87 @@ class Realtime extends Adapter
/**
* Create channels array based on the event name and payload.
*
* @param string $event
* @param Document $payload
* @param Document|null $project
* @return array
* @param string $event
* @param Document $payload
* @param Document|null $project
* @return array
*/
public static function fromPayload(string $event, Document $payload, Document $project = null, Document $collection = null, Document $bucket = null): array
public static function fromPayload(string $event, Document $payload, Document $project = null, Document $database = null, Document $collection = null, Document $bucket = null): array
{
$channels = [];
$roles = [];
$permissionsChanged = false;
$projectId = null;
// TODO: add method here to remove all the magic index accesses
$parts = explode('.', $event);
switch (true) {
case strpos($event, 'account.recovery.') === 0:
case strpos($event, 'account.sessions.') === 0:
case strpos($event, 'account.verification.') === 0:
switch ($parts[0]) {
case 'users':
$channels[] = 'account';
$channels[] = 'account.' . $payload->getAttribute('userId');
$roles = ['user:' . $payload->getAttribute('userId')];
$channels[] = 'account.' . $parts[1];
$roles = ['user:' . $parts[1]];
break;
case strpos($event, 'account.') === 0:
$channels[] = 'account';
$channels[] = 'account.' . $payload->getId();
$roles = ['user:' . $payload->getId()];
break;
case strpos($event, 'teams.memberships') === 0:
$permissionsChanged = in_array($event, ['teams.memberships.update', 'teams.memberships.delete', 'teams.memberships.update.status']);
$channels[] = 'memberships';
$channels[] = 'memberships.' . $payload->getId();
$roles = ['team:' . $payload->getAttribute('teamId')];
break;
case strpos($event, 'teams.') === 0:
$permissionsChanged = $event === 'teams.create';
$channels[] = 'teams';
$channels[] = 'teams.' . $payload->getId();
$roles = ['team:' . $payload->getId()];
break;
case strpos($event, 'database.attributes.') === 0:
case strpos($event, 'database.indexes.') === 0:
$channels[] = 'console';
$projectId = 'console';
$roles = ['team:' . $project->getAttribute('teamId')];
break;
case strpos($event, 'database.documents.') === 0:
if ($collection->isEmpty()) {
throw new \Exception('Collection needs to be passed to Realtime for Document events in the Database.');
case 'teams':
if ($parts[2] === 'memberships') {
$permissionsChanged = $parts[4] ?? false;
$channels[] = 'memberships';
$channels[] = 'memberships.' . $parts[3];
$roles = ['team:' . $parts[1]];
} else {
$permissionsChanged = $parts[2] === 'create';
$channels[] = 'teams';
$channels[] = 'teams.' . $parts[1];
$roles = ['team:' . $parts[1]];
}
$channels[] = 'documents';
$channels[] = 'collections.' . $payload->getAttribute('$collection') . '.documents';
$channels[] = 'collections.' . $payload->getAttribute('$collection') . '.documents.' . $payload->getId();
$roles = ($collection->getAttribute('permission') === 'collection') ? $collection->getRead() : $payload->getRead();
break;
case strpos($event, 'storage.files') === 0:
if($bucket->isEmpty()) {
throw new \Exception('Bucket needs to be pased to Realtime for File events in the Storage.');
}
$channels[] = 'files';
$channels[] = 'buckets.' . $payload->getAttribute('bucketId') . '.files';
$channels[] = 'buckets.' . $payload->getAttribute('bucketId') . '.files.' . $payload->getId();
$roles = $payload->getRead();
break;
case strpos($event, 'functions.executions.') === 0:
if (!empty($payload->getRead())) {
case 'databases':
if (in_array($parts[4] ?? [], ['attributes', 'indexes'])) {
$channels[] = 'console';
$channels[] = 'executions';
$channels[] = 'executions.' . $payload->getId();
$channels[] = 'functions.' . $payload->getAttribute('functionId');
$roles = $payload->getRead();
$projectId = 'console';
$roles = ['team:' . $project->getAttribute('teamId')];
} elseif (($parts[4] ?? '') === 'documents') {
if ($database->isEmpty()) {
throw new \Exception('Database needs to be passed to Realtime for Document events in the Database.');
}
if ($collection->isEmpty()) {
throw new \Exception('Collection needs to be passed to Realtime for Document events in the Database.');
}
$channels[] = 'documents';
$channels[] = 'databases.' . $database->getId() . '.collections.' . $payload->getCollection() . '.documents';
$channels[] = 'databases.' . $database->getId() . '.collections.' . $payload->getCollection() . '.documents.' . $payload->getId();
$roles = ($collection->getAttribute('permission') === 'collection') ? $collection->getRead() : $payload->getRead();
}
break;
case strpos($event, 'functions.deployments.') === 0:
$channels[] = 'console';
$roles = ['team:' . $project->getAttribute('teamId')];
case 'buckets':
if ($parts[2] === 'files') {
if ($bucket->isEmpty()) {
throw new \Exception('Bucket needs to be pased to Realtime for File events in the Storage.');
}
$channels[] = 'files';
$channels[] = 'buckets.' . $payload->getAttribute('bucketId') . '.files';
$channels[] = 'buckets.' . $payload->getAttribute('bucketId') . '.files.' . $payload->getId();
$roles = ($bucket->getAttribute('permission') === 'bucket') ? $bucket->getRead() : $payload->getRead();
}
break;
case 'functions':
if ($parts[2] === 'executions') {
if (!empty($payload->getRead())) {
$channels[] = 'console';
$channels[] = 'executions';
$channels[] = 'executions.' . $payload->getId();
$channels[] = 'functions.' . $payload->getAttribute('functionId');
$roles = $payload->getRead();
}
} elseif ($parts[2] === 'deployments') {
$channels[] = 'console';
$roles = ['team:' . $project->getAttribute('teamId')];
}
break;
}
+166 -40
View File
@@ -8,6 +8,8 @@ use Utopia\Database\Database;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Exception;
use Utopia\App;
use Utopia\Database\Validator\Authorization;
abstract class Migration
{
@@ -38,6 +40,14 @@ abstract class Migration
'0.13.0' => 'V12',
'0.13.1' => 'V12',
'0.13.2' => 'V12',
'0.13.3' => 'V12',
'0.13.4' => 'V12',
'0.14.0' => 'V13',
'0.14.1' => 'V13',
'0.14.2' => 'V13',
'0.15.0' => 'V14',
'0.15.1' => 'V14',
'0.15.2' => 'V14'
];
/**
@@ -47,15 +57,20 @@ abstract class Migration
public function __construct()
{
Authorization::disable();
Authorization::setDefaultStatus(false);
$this->collections = array_merge([
'_metadata' => [
'$id' => '_metadata'
'$id' => '_metadata',
'$collection' => Database::METADATA
],
'audit' => [
'$id' => 'audit'
'$id' => 'audit',
'$collection' => Database::METADATA
],
'abuse' => [
'$id' => 'abuse'
'$id' => 'abuse',
'$collection' => Database::METADATA
]
], Config::getParam('collections', []));
}
@@ -90,6 +105,10 @@ abstract class Migration
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
foreach ($this->collections as $collection) {
if ($collection['$collection'] !== Database::METADATA) {
continue;
}
$sum = 0;
$nextDocument = null;
$collectionCount = $this->projectDB->count($collection['$id']);
@@ -112,21 +131,7 @@ abstract class Migration
$old = $document->getArrayCopy();
$new = call_user_func($callback, $document);
foreach ($document as &$attr) {
if ($attr instanceof Document) {
$attr = call_user_func($callback, $attr);
}
if (\is_array($attr)) {
foreach ($attr as &$child) {
if ($child instanceof Document) {
$child = call_user_func($callback, $child);
}
}
}
}
if (!$this->check_diff_multi($new->getArrayCopy(), $old)) {
if (is_null($new) || !self::hasDifference($new->getArrayCopy(), $old)) {
return;
}
@@ -155,35 +160,156 @@ abstract class Migration
/**
* Checks 2 arrays for differences.
*
* @param array $array1
* @param array $array2
* @return array
*
* @param array $array1
* @param array $array2
* @return bool
*/
public function check_diff_multi(array $array1, array $array2): array
public static function hasDifference(array $array1, array $array2): bool
{
$result = array();
foreach ($array1 as $key => $val) {
if (is_array($val) && isset($array2[$key])) {
$tmp = $this->check_diff_multi($val, $array2[$key]);
if ($tmp) {
$result[$key] = $tmp;
foreach ($array1 as $key => $value) {
if (is_array($value)) {
if (!isset($array2[$key]) || !is_array($array2[$key])) {
return true;
} else {
if (self::hasDifference($value, $array2[$key])) {
return true;
}
}
} elseif (!isset($array2[$key])) {
$result[$key] = null;
} elseif ($val !== $array2[$key]) {
$result[$key] = $array2[$key];
}
if (isset($array2[$key])) {
unset($array2[$key]);
} elseif (!array_key_exists($key, $array2) || $array2[$key] !== $value) {
return true;
}
}
$result = array_merge($result, $array2);
return false;
}
return $result;
/**
* Creates colletion from the config collection.
*
* @param string $id
* @param string|null $name
* @return void
* @throws \Throwable
*/
protected function createCollection(string $id, string $name = null): void
{
$name ??= $id;
if (!$this->projectDB->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'), $name)) {
$attributes = [];
$indexes = [];
$collection = $this->collections[$id];
foreach ($collection['attributes'] as $attribute) {
$attributes[] = new Document([
'$id' => $attribute['$id'],
'type' => $attribute['type'],
'size' => $attribute['size'],
'required' => $attribute['required'],
'signed' => $attribute['signed'],
'array' => $attribute['array'],
'filters' => $attribute['filters'],
]);
}
foreach ($collection['indexes'] as $index) {
$indexes[] = new Document([
'$id' => $index['$id'],
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]);
}
try {
$this->projectDB->createCollection($name, $attributes, $indexes);
} catch (\Throwable $th) {
throw $th;
}
}
}
/**
* Creates attribute from collections.php
*
* @param \Utopia\Database\Database $database
* @param string $collectionId
* @param string $attributeId
* @return void
* @throws \Exception
* @throws \Utopia\Database\Exception\Duplicate
* @throws \Utopia\Database\Exception\Limit
*/
public function createAttributeFromCollection(Database $database, string $collectionId, string $attributeId, string $from = null): void
{
$from ??= $collectionId;
$collection = Config::getParam('collections', [])[$from] ?? null;
if (is_null($collection)) {
throw new Exception("Collection {$collectionId} not found");
}
$attributes = $collection['attributes'];
$attributeKey = array_search($attributeId, array_column($attributes, '$id'));
if ($attributeKey === false) {
throw new Exception("Attribute {$attributeId} not found");
}
$attribute = $attributes[$attributeKey];
$database->createAttribute(
collection: $collectionId,
id: $attributeId,
type: $attribute['type'],
size: $attribute['size'],
required: $attribute['required'] ?? false,
default: $attribute['default'] ?? null,
signed: $attribute['signed'] ?? false,
array: $attribute['array'] ?? false,
format: $attribute['format'] ?? '',
formatOptions: $attribute['formatOptions'] ?? [],
filters: $attribute['filters'] ?? [],
);
}
/**
* Creates index from collections.php
*
* @param \Utopia\Database\Database $database
* @param string $collectionId
* @param string $indexId
* @return void
* @throws \Exception
* @throws \Utopia\Database\Exception\Duplicate
* @throws \Utopia\Database\Exception\Limit
*/
public function createIndexFromCollection(Database $database, string $collectionId, string $indexId): void
{
$collection = Config::getParam('collections', [])[$collectionId] ?? null;
if (is_null($collection)) {
throw new Exception("Collection {$collectionId} not found");
}
$indexes = $collection['indexes'];
$indexKey = array_search($indexId, array_column($indexes, '$id'));
if ($indexKey === false) {
throw new Exception("Attribute {$indexId} not found");
}
$index = $indexes[$indexKey];
$database->createIndex(
collection: $collectionId,
id: $indexId,
type: $index['type'],
attributes: $index['attributes'],
lengths: $index['lengths'] ?? [],
orders: $index['orders'] ?? []
);
}
/**
+7 -54
View File
@@ -297,61 +297,14 @@ class V12 extends Migration
}
}
/**
* Creates colletion from the config collection.
*
* @param string $id
* @param string|null $name
* @return void
* @throws \Throwable
*/
protected function createCollection(string $id, string $name = null): void
{
$name ??= $id;
if (!$this->projectDB->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'), $name)) {
$attributes = [];
$indexes = [];
$collection = $this->collections[$id];
foreach ($collection['attributes'] as $attribute) {
$attributes[] = new Document([
'$id' => $attribute['$id'],
'type' => $attribute['type'],
'size' => $attribute['size'],
'required' => $attribute['required'],
'signed' => $attribute['signed'],
'array' => $attribute['array'],
'filters' => $attribute['filters'],
]);
}
foreach ($collection['indexes'] as $index) {
$indexes[] = new Document([
'$id' => $index['$id'],
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]);
}
try {
$this->projectDB->createCollection($name, $attributes, $indexes);
} catch (\Throwable $th) {
throw $th;
}
}
}
/**
* Migrates permissions to dedicated table.
*
* @param \Utopia\Database\Document $document
* @param string $internalId
* @return void
* @throws \Exception
* @throws \PDOException
* @param \Utopia\Database\Document $document
* @param string $internalId
* @return void
* @throws \Exception
* @throws \PDOException
*/
protected function migratePermissionsToDedicatedTable(string $collection, Document $document): void
{
@@ -546,8 +499,8 @@ class V12 extends Migration
/**
* Fix run on each document
*
* @param \Utopia\Database\Document $document
* @return \Utopia\Database\Document
* @param \Utopia\Database\Document $document
* @return \Utopia\Database\Document
*/
protected function fixDocument(Document $document)
{
+337
View File
@@ -0,0 +1,337 @@
<?php
namespace Appwrite\Migration\Version;
use Appwrite\Migration\Migration;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
class V13 extends Migration
{
public array $events = [
'account.create',
'account.update.email',
'account.update.name',
'account.update.password',
'account.update.prefs',
'account.recovery.create',
'account.recovery.update',
'account.verification.create',
'account.verification.update',
'account.delete',
'account.sessions.create',
'account.sessions.delete',
'database.collections.create',
'database.collections.update',
'database.collections.delete',
'database.attributes.create',
'database.attributes.delete',
'database.indexes.create',
'database.indexes.delete',
'database.documents.create',
'database.documents.update',
'database.documents.delete',
'functions.create',
'functions.update',
'functions.delete',
'functions.deployments.create',
'functions.deployments.update',
'functions.deployments.delete',
'functions.executions.create',
'functions.executions.update',
'storage.files.create',
'storage.files.update',
'storage.files.delete',
'storage.buckets.create',
'storage.buckets.update',
'storage.buckets.delete',
'users.create',
'users.update.prefs',
'users.update.email',
'users.update.name',
'users.update.password',
'users.update.status',
'users.sessions.delete',
'users.delete',
'teams.create',
'teams.update',
'teams.delete',
'teams.memberships.create',
'teams.memberships.update',
'teams.memberships.update.status',
'teams.memberships.delete'
];
public function execute(): void
{
Console::log('Migrating project: ' . $this->project->getAttribute('name') . ' (' . $this->project->getId() . ')');
Console::info('Migrating Collections');
$this->migrateCollections();
Console::info('Migrating Documents');
$this->forEachDocument([$this, 'fixDocument']);
}
/**
* Migrate all Collections.
*
* @return void
*/
protected function migrateCollections(): void
{
foreach ($this->collections as $collection) {
$id = $collection['$id'];
Console::log("- {$id}");
switch ($id) {
case 'projects':
try {
/**
* Rename providers to authProviders.
*/
$this->projectDB->renameAttribute($id, 'providers', 'authProviders');
} catch (\Throwable $th) {
Console::warning("'providers' from {$id}: {$th->getMessage()}");
}
break;
case 'users':
try {
/**
* Recreate sessions for new subquery.
*/
$this->projectDB->deleteAttribute($id, 'sessions');
$this->projectDB->createAttribute(
collection: $id,
id: 'sessions',
required: false,
type: Database::VAR_STRING,
format: '',
size: 16384,
filters: ['subQuerySessions']
);
} catch (\Throwable $th) {
Console::warning("'sessions' from {$id}: {$th->getMessage()}");
}
try {
/**
* Recreate tokens for new subquery.
*/
$this->projectDB->deleteAttribute($id, 'tokens');
$this->projectDB->createAttribute(
collection: $id,
id: 'tokens',
required: false,
type: Database::VAR_STRING,
format: '',
size: 16384,
filters: ['subQueryTokens']
);
} catch (\Throwable $th) {
Console::warning("'tokens' from {$id}: {$th->getMessage()}");
}
try {
/**
* Recreate memberships for new subquery.
*/
$this->projectDB->deleteAttribute($id, 'memberships');
$this->projectDB->createAttribute(
collection: $id,
id: 'memberships',
required: false,
type: Database::VAR_STRING,
format: '',
size: 16384,
filters: ['subQueryMemberships']
);
} catch (\Throwable $th) {
Console::warning("'memberships' from {$id}: {$th->getMessage()}");
}
break;
case 'sessions':
try {
/**
* Add new index for users.
*/
$this->projectDB->createIndex(collection: $id, id: '_key_user', type: Database::INDEX_KEY, attributes: ['userId'], orders: [Database::ORDER_ASC]);
} catch (\Throwable $th) {
Console::warning("'_key_user' from {$id}: {$th->getMessage()}");
}
break;
case 'builds':
try {
/**
* Increase stdout size.
*/
$this->projectDB->updateAttribute($id, 'stdout', size: 1_000_000);
} catch (\Throwable $th) {
Console::warning("'stdout' from {$id}: {$th->getMessage()}");
}
try {
/**
* Increase stderr size.
*/
$this->projectDB->updateAttribute($id, 'stderr', size: 1_000_000);
} catch (\Throwable $th) {
Console::warning("'stderr' from {$id}: {$th->getMessage()}");
}
break;
case 'executions':
try {
/**
* Rename stdout to response.
* Increase response size.
*/
$this->projectDB->renameAttribute($id, 'stdout', 'response');
$this->projectDB->updateAttribute($id, 'response', size: 1_000_000);
} catch (\Throwable $th) {
Console::warning("'stdout' from {$id}: {$th->getMessage()}");
}
try {
/**
* Increase stderr size.
*/
$this->projectDB->updateAttribute($id, 'stderr', size: 1_000_000);
} catch (\Throwable $th) {
Console::warning("'stderr' from {$id}: {$th->getMessage()}");
}
break;
case 'stats':
try {
/**
* Increase value size ot BIGINT.
*/
$this->projectDB->updateAttribute($id, 'value', size: 8);
} catch (\Throwable $th) {
Console::warning("'size' from {$id}: {$th->getMessage()}");
}
break;
case 'tokens':
try {
/**
* Create new Tokens collection.
*/
$this->createCollection('tokens');
} catch (\Throwable $th) {
Console::warning("'tokens': {$th->getMessage()}");
}
break;
}
usleep(100000);
}
}
/**
* Fix run on each document
*
* @param \Utopia\Database\Document $document
* @return \Utopia\Database\Document
*/
protected function fixDocument(Document $document)
{
switch ($document->getCollection()) {
case 'projects':
/**
* Bump Project version number.
*/
$document->setAttribute('version', '0.14.0');
break;
case 'functions':
/**
* Migrate events.
*/
if (!empty($document->getAttribute('events'))) {
$document->setAttribute('events', $this->migrateEvents($document->getAttribute('events')));
}
break;
case 'webhooks':
/**
* Migrate events.
*/
if (!empty($document->getAttribute('events'))) {
$document->setAttribute('events', $this->migrateEvents($document->getAttribute('events')));
}
break;
case 'users':
/**
* Remove deleted users.
*/
if ($document->getAttribute('deleted', false) === true) {
$this->projectDB->deleteDocument('users', $document->getId());
}
break;
}
return $document;
}
public function migrateEvents(array $events): array
{
return array_filter(array_unique(array_map(function ($event) {
if (!in_array($event, $this->events)) {
return $event;
}
$parts = \explode('.', $event);
$first = array_shift($parts);
switch ($first) {
case 'account':
case 'users':
$first = 'users';
switch ($parts[0]) {
case 'recovery':
case 'sessions':
case 'verification':
$second = array_shift($parts);
return 'users.*.' . $second . '.*.' . implode('.', $parts);
default:
return 'users.*.' . implode('.', $parts);
}
case 'functions':
switch ($parts[0]) {
case 'deployments':
case 'executions':
$second = array_shift($parts);
return 'functions.*.' . $second . '.*.' . implode('.', $parts);
default:
return 'functions.*.' . implode('.', $parts);
}
case 'teams':
switch ($parts[0]) {
case 'memberships':
$second = array_shift($parts);
return 'teams.*.' . $second . '.*.' . implode('.', $parts);
default:
return 'teams.*.' . implode('.', $parts);
}
case 'storage':
$second = array_shift($parts);
switch ($second) {
case 'buckets':
return 'buckets.*.' . implode('.', $parts);
case 'files':
return 'buckets.*.' . $second . '.*.' . implode('.', $parts);
} // intentional fallthrough
case 'database':
$second = array_shift($parts);
switch ($second) {
case 'collections':
return 'collections.*.' . implode('.', $parts);
case 'documents':
case 'indexes':
case 'attributes':
return 'collections.*.' . $second . '.*.' . implode('.', $parts);
}
}
return '';
}, $events)));
}
}
+771
View File
@@ -0,0 +1,771 @@
<?php
namespace Appwrite\Migration\Version;
use Appwrite\Migration\Migration;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
class V14 extends Migration
{
/**
* @var \PDO $pdo
*/
private $pdo;
public function execute(): void
{
global $register;
$this->pdo = $register->get('db');
if ($this->project->getId() === 'console' && $this->project->getInternalId() !== 'console') {
return;
}
/**
* Disable SubQueries for Speed.
*/
foreach (['subQueryAttributes', 'subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships'] as $name) {
Database::addFilter($name, fn () => null, fn () => []);
}
Console::log('Migrating project: ' . $this->project->getAttribute('name') . ' (' . $this->project->getId() . ')');
Console::info('Migrating Collections');
$this->migrateCollections();
Console::info('Create Default Database Layer');
$this->createDatabaseLayer();
if ($this->project->getId() !== 'console') {
Console::info('Migrating Database Collections');
$this->migrateCustomCollections();
}
Console::info('Migrating Documents');
$this->forEachDocument([$this, 'fixDocument']);
}
/**
* Creates the default Database for existing Projects.
*
* @return void
* @throws \Throwable
*/
public function createDatabaseLayer(): void
{
try {
if (!$this->projectDB->exists('databases')) {
$this->createCollection('databases');
}
} catch (\Throwable $th) {
Console::warning($th->getMessage());
}
if ($this->project->getInternalId() === 'console') {
return;
}
try {
$this->projectDB->createDocument('databases', new Document([
'$id' => 'default',
'name' => 'Default',
'search' => 'default Default'
]));
} catch (\Throwable $th) {
Console::warning($th->getMessage());
}
}
/**
* Migrates all Files.
*
* @param \Utopia\Database\Document $bucket
* @return void
* @throws \Exception
*/
protected function migrateBucketFiles(Document $bucket): void
{
$nextFile = null;
do {
$documents = $this->projectDB->find("bucket_{$bucket->getInternalId()}", limit: $this->limit, cursor: $nextFile);
$count = count($documents);
foreach ($documents as $document) {
go(function (Document $bucket, Document $document) {
Console::log("Migrating File {$document->getId()}");
try {
/**
* Migrate $createdAt.
*/
if (empty($document->getCreatedAt())) {
$document->setAttribute('$createdAt', $document->getAttribute('dateCreated'));
$this->projectDB->updateDocument("bucket_{$bucket->getInternalId()}", $document->getId(), $document);
}
} catch (\Throwable $th) {
Console::warning($th->getMessage());
}
}, $bucket, $document);
}
if ($count !== $this->limit) {
$nextFile = null;
} else {
$nextFile = end($documents);
}
} while (!is_null($nextFile));
}
/**
* Migrates all Database Collections.
* @return void
* @throws \Exception
*/
protected function migrateCustomCollections(): void
{
try {
$this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_collections` RENAME TO `_{$this->project->getInternalId()}_database_1`")->execute();
} catch (\Throwable $th) {
Console::warning($th->getMessage());
}
try {
$this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_collections_perms` RENAME TO `_{$this->project->getInternalId()}_database_1_perms`")->execute();
} catch (\Throwable $th) {
Console::warning($th->getMessage());
}
/**
* Update metadata table.
*/
try {
$this->pdo->prepare("UPDATE `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}__metadata`
SET
_uid = 'database_1',
name = 'database_1'
WHERE _uid = 'collections';
")->execute();
} catch (\Throwable $th) {
Console::warning($th->getMessage());
}
try {
/**
* Add Database ID for Collections.
*/
$this->createAttributeFromCollection($this->projectDB, 'database_1', 'databaseId', 'collections');
/**
* Add Database Internal ID for Collections.
*/
$this->createAttributeFromCollection($this->projectDB, 'database_1', 'databaseInternalId', 'collections');
} catch (\Throwable $th) {
Console::warning($th->getMessage());
}
$nextCollection = null;
do {
$documents = $this->projectDB->find('database_1', limit: $this->limit, cursor: $nextCollection);
$count = count($documents);
\Co\run(function (array $documents) {
foreach ($documents as $document) {
go(function (Document $collection) {
$id = $collection->getId();
$internalId = $collection->getInternalId();
Console::log("- {$id} ({$collection->getAttribute('name')})");
try {
/**
* Rename user's colletion table schema
*/
$this->createNewMetaData("collection_{$internalId}", "database_1_collection_{$internalId}");
} catch (\Throwable $th) {
Console::warning($th->getMessage());
}
try {
/**
* Update metadata table.
*/
$this->pdo->prepare("UPDATE `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}__metadata`
SET
_uid = 'database_1_collection_{$internalId}',
name = 'database_1_collection_{$internalId}'
WHERE _uid = 'collection_{$internalId}';
")->execute();
} catch (\Throwable $th) {
Console::warning($th->getMessage());
}
try {
/**
* Update internal ID's.
*/
$collection
->setAttribute('databaseId', 'default')
->setAttribute('databaseInternalId', '1');
$this->projectDB->updateDocument('database_1', $collection->getId(), $collection);
} catch (\Throwable $th) {
Console::warning($th->getMessage());
}
}, $document);
}
}, $documents);
if ($count !== $this->limit) {
$nextCollection = null;
} else {
$nextCollection = end($documents);
}
} while (!is_null($nextCollection));
}
/**
* Migrate all Collections.
*
* @return void
*/
protected function migrateCollections(): void
{
foreach ($this->collections as $collection) {
$id = $collection['$id'];
Console::log("- {$id}");
$this->createNewMetaData($id);
$this->projectDB->setNamespace("_{$this->project->getInternalId()}");
switch ($id) {
case 'attributes':
case 'indexes':
try {
/**
* Create 'databaseInternalId' attribute
*/
$this->createAttributeFromCollection($this->projectDB, $id, 'databaseId');
} catch (\Throwable $th) {
Console::warning("'databaseInternalId' from {$id}: {$th->getMessage()}");
}
try {
/**
* Create 'databaseInternalId' attribute
*/
$this->createAttributeFromCollection($this->projectDB, $id, 'databaseInternalId');
} catch (\Throwable $th) {
Console::warning("'databaseInternalId' from {$id}: {$th->getMessage()}");
}
try {
/**
* Create 'collectionInternalId' attribute
*/
$this->createAttributeFromCollection($this->projectDB, $id, 'collectionInternalId');
} catch (\Throwable $th) {
Console::warning("'collectionInternalId' from {$id}: {$th->getMessage()}");
}
try {
/**
* Re-Create '_key_collection' index
*/
@$this->projectDB->deleteIndex($id, '_key_collection');
$this->createIndexFromCollection($this->projectDB, $id, '_key_db_collection');
} catch (\Throwable $th) {
Console::warning("'_key_collection' from {$id}: {$th->getMessage()}");
}
break;
case 'projects':
try {
/**
* Create 'teamInternalId' attribute
*/
$this->createAttributeFromCollection($this->projectDB, $id, 'teamInternalId');
} catch (\Throwable $th) {
Console::warning("'teamInternalId' from {$id}: {$th->getMessage()}");
}
break;
case 'platforms':
case 'domains':
try {
/**
* Create 'projectInternalId' attribute
*/
$this->createAttributeFromCollection($this->projectDB, $id, 'projectInternalId');
} catch (\Throwable $th) {
Console::warning("'projectInternalId' from {$id}: {$th->getMessage()}");
}
try {
/**
* Re-Create '_key_project' index
*/
@$this->projectDB->deleteIndex($id, '_key_project');
$this->createIndexFromCollection($this->projectDB, $id, '_key_project');
} catch (\Throwable $th) {
Console::warning("'_key_project' from {$id}: {$th->getMessage()}");
}
break;
case 'keys':
try {
/**
* Create 'projectInternalId' attribute
*/
$this->createAttributeFromCollection($this->projectDB, $id, 'projectInternalId');
} catch (\Throwable $th) {
Console::warning("'projectInternalId' from {$id}: {$th->getMessage()}");
}
try {
/**
* Create 'expire' attribute
*/
$this->createAttributeFromCollection($this->projectDB, $id, 'expire');
} catch (\Throwable $th) {
Console::warning("'expire' from {$id}: {$th->getMessage()}");
}
try {
/**
* Re-Create '_key_project' index
*/
@$this->projectDB->deleteIndex($id, '_key_project');
$this->createIndexFromCollection($this->projectDB, $id, '_key_project');
} catch (\Throwable $th) {
Console::warning("'_key_project' from {$id}: {$th->getMessage()}");
}
break;
case 'webhooks':
try {
/**
* Create 'signatureKey' attribute
*/
$this->createAttributeFromCollection($this->projectDB, $id, 'signatureKey');
} catch (\Throwable $th) {
Console::warning("'signatureKey' from {$id}: {$th->getMessage()}");
}
try {
/**
* Create 'projectInternalId' attribute
*/
$this->createAttributeFromCollection($this->projectDB, $id, 'projectInternalId');
} catch (\Throwable $th) {
Console::warning("'projectInternalId' from {$id}: {$th->getMessage()}");
}
try {
/**
* Re-Create '_key_project' index
*/
@$this->projectDB->deleteIndex($id, '_key_project');
$this->createIndexFromCollection($this->projectDB, $id, '_key_project');
} catch (\Throwable $th) {
Console::warning("'_key_project' from {$id}: {$th->getMessage()}");
}
break;
case 'users':
try {
/**
* Create 'phone' attribute
*/
$this->createAttributeFromCollection($this->projectDB, $id, 'phone');
} catch (\Throwable $th) {
Console::warning("'phone' from {$id}: {$th->getMessage()}");
}
try {
/**
* Create 'phoneVerification' attribute
*/
$this->createAttributeFromCollection($this->projectDB, $id, 'phoneVerification');
} catch (\Throwable $th) {
Console::warning("'phoneVerification' from {$id}: {$th->getMessage()}");
}
try {
/**
* Create '_key_phone' index
*/
$this->createIndexFromCollection($this->projectDB, $id, '_key_phone');
} catch (\Throwable $th) {
Console::warning("'_key_phone' from {$id}: {$th->getMessage()}");
}
break;
case 'tokens':
case 'sessions':
try {
/**
* Create 'userInternalId' attribute
*/
$this->createAttributeFromCollection($this->projectDB, $id, 'userInternalId');
} catch (\Throwable $th) {
Console::warning("'userInternalId' from {$id}: {$th->getMessage()}");
}
try {
/**
* Re-Create '_key_user' index
*/
@$this->projectDB->deleteIndex($id, '_key_user');
$this->createIndexFromCollection($this->projectDB, $id, '_key_user');
} catch (\Throwable $th) {
Console::warning("'_key_user' from {$id}: {$th->getMessage()}");
}
break;
case 'memberships':
try {
/**
* Create 'teamInternalId' attribute
*/
$this->createAttributeFromCollection($this->projectDB, $id, 'teamInternalId');
} catch (\Throwable $th) {
Console::warning("'teamInternalId' from {$id}: {$th->getMessage()}");
}
try {
/**
* Create 'userInternalId' attribute
*/
$this->createAttributeFromCollection($this->projectDB, $id, 'userInternalId');
} catch (\Throwable $th) {
Console::warning("'userInternalId' from {$id}: {$th->getMessage()}");
}
try {
/**
* Re-Create '_key_unique' index
*/
@$this->projectDB->deleteIndex($id, '_key_unique');
$this->createIndexFromCollection($this->projectDB, $id, '_key_unique');
} catch (\Throwable $th) {
Console::warning("'_key_unique' from {$id}: {$th->getMessage()}");
}
try {
/**
* Re-Create '_key_team' index
*/
@$this->projectDB->deleteIndex($id, '_key_team');
$this->createIndexFromCollection($this->projectDB, $id, '_key_team');
} catch (\Throwable $th) {
Console::warning("'_key_team' from {$id}: {$th->getMessage()}");
}
try {
/**
* Re-Create '_key_user' index
*/
@$this->projectDB->deleteIndex($id, '_key_user');
$this->createIndexFromCollection($this->projectDB, $id, '_key_user');
} catch (\Throwable $th) {
Console::warning("'_key_user' from {$id}: {$th->getMessage()}");
}
break;
}
usleep(50000);
}
}
/**
* Fix run on each document
*
* @param \Utopia\Database\Document $document
* @return \Utopia\Database\Document
*/
protected function fixDocument(Document $document)
{
switch ($document->getCollection()) {
case 'projects':
/**
* Bump Project version number.
*/
$document->setAttribute('version', '0.15.0');
if (!empty($document->getAttribute('teamId')) && is_null($document->getAttribute('teamInternalId'))) {
$internalId = $this->projectDB->getDocument('teams', $document->getAttribute('teamId'))->getInternalId();
$document->setAttribute('teamInternalId', $internalId);
}
break;
case 'keys':
/**
* Add new 'expire' attribute and default to never (0).
*/
if (is_null($document->getAttribute('expire'))) {
$document->setAttribute('expire', 0);
}
/**
* Add Internal ID 'projectId' for Subqueries.
*/
if (!empty($document->getAttribute('projectId')) && is_null($document->getAttribute('projectInternalId'))) {
$internalId = $this->projectDB->getDocument('projects', $document->getAttribute('projectId'))->getInternalId();
$document->setAttribute('projectInternalId', $internalId);
}
break;
case 'audit':
/**
* Add Database Layer to collection resource.
*/
if (str_starts_with($document->getAttribute('resource'), 'collection/')) {
$document
->setAttribute('resource', "database/default/{$document->getAttribute('resource')}")
->setAttribute('event', "databases.default.{$document->getAttribute('event')}");
}
if (str_starts_with($document->getAttribute('resource'), 'document/')) {
$collectionId = explode('.', $document->getAttribute('event'))[1];
$document
->setAttribute('resource', "database/default/collection/{$collectionId}/{$document->getAttribute('resource')}")
->setAttribute('event', "databases.default.{$document->getAttribute('event')}");
}
break;
case 'stats':
/**
* Add Database Layer to stats metric.
*/
if (str_starts_with($document->getAttribute('metric'), 'database.')) {
$metric = ltrim($document->getAttribute('metric'), 'database.');
$document->setAttribute('metric', "databases.default.{$metric}");
}
break;
case 'webhooks':
/**
* Add new 'signatureKey' attribute and generate a random value.
*/
if (empty($document->getAttribute('signatureKey'))) {
$document->setAttribute('signatureKey', \bin2hex(\random_bytes(64)));
}
/**
* Add Internal ID 'projectId' for Subqueries.
*/
if (!empty($document->getAttribute('projectId')) && is_null($document->getAttribute('projectInternalId'))) {
$internalId = $this->projectDB->getDocument('projects', $document->getAttribute('projectId'))->getInternalId();
$document->setAttribute('projectInternalId', $internalId);
}
break;
case 'domains':
/**
* Add Internal ID 'projectId' for Subqueries.
*/
if (!empty($document->getAttribute('projectId')) && is_null($document->getAttribute('projectInternalId'))) {
$internalId = $this->projectDB->getDocument('projects', $document->getAttribute('projectId'))->getInternalId();
$document->setAttribute('projectInternalId', $internalId);
}
break;
case 'tokens':
case 'sessions':
/**
* Add Internal ID 'userId' for Subqueries.
*/
if (!empty($document->getAttribute('userId')) && is_null($document->getAttribute('userInternalId'))) {
$internalId = $this->projectDB->getDocument('users', $document->getAttribute('userId'))->getInternalId();
$document->setAttribute('userInternalId', $internalId);
}
break;
case 'memberships':
/**
* Add Internal ID 'userId' for Subqueries.
*/
if (!empty($document->getAttribute('userId')) && is_null($document->getAttribute('userInternalId'))) {
$internalId = $this->projectDB->getDocument('users', $document->getAttribute('userId'))->getInternalId();
$document->setAttribute('userInternalId', $internalId);
}
/**
* Add Internal ID 'teamId' for Subqueries.
*/
if (!empty($document->getAttribute('teamId')) && is_null($document->getAttribute('teamInternalId'))) {
$internalId = $this->projectDB->getDocument('teams', $document->getAttribute('teamId'))->getInternalId();
$document->setAttribute('teamInternalId', $internalId);
}
break;
case 'attributes':
case 'indexes':
/**
* Add Internal ID 'collectionId' for Subqueries.
*/
if (!empty($document->getAttribute('collectionId')) && is_null($document->getAttribute('collectionInternalId'))) {
$internalId = $this->projectDB->getDocument('database_1', $document->getAttribute('collectionId'))->getInternalId();
$document->setAttribute('collectionInternalId', $internalId);
}
/**
* Add Internal ID 'databaseInternalId' for Subqueries.
*/
if (is_null($document->getAttribute('databaseInternalId'))) {
$document->setAttribute('databaseInternalId', '1');
}
/**
* Add Internal ID 'databaseInternalId' for Subqueries.
*/
if (is_null($document->getAttribute('databaseId'))) {
$document->setAttribute('databaseId', 'default');
}
try {
/**
* Re-create Collection Document
*/
$internalId = $this->projectDB->getDocument('database_1', $document->getAttribute('collectionId'))->getInternalId();
$this->projectDB->deleteDocument($document->getCollection(), $document->getId());
$this->projectDB->createDocument($document->getCollection(), $document->setAttribute('$id', "1_{$internalId}_{$document->getAttribute('key')}"));
} catch (\Throwable $th) {
Console::warning("Create Collection Document - {$th->getMessage()}");
}
$document = null;
break;
case 'platforms':
/**
* Migrate dateCreated to $createdAt.
*/
if (empty($document->getCreatedAt())) {
$document->setAttribute('$createdAt', $document->getAttribute('dateCreated'));
}
/**
* Migrate dateUpdated to $updatedAt.
*/
if (empty($document->getUpdatedAt())) {
$document->setAttribute('$updatedAt', $document->getAttribute('dateUpdated'));
}
/**
* Add Internal ID 'projectId' for Subqueries.
*/
if (!empty($document->getAttribute('projectId')) && is_null($document->getAttribute('projectInternalId'))) {
$internalId = $this->projectDB->getDocument('projects', $document->getAttribute('projectId'))->getInternalId();
$document->setAttribute('projectInternalId', $internalId);
}
break;
case 'buckets':
/**
* Migrate dateCreated to $createdAt.
*/
if (empty($document->getCreatedAt())) {
$document->setAttribute('$createdAt', $document->getAttribute('dateCreated'));
}
/**
* Migrate dateUpdated to $updatedAt.
*/
if (empty($document->getUpdatedAt())) {
$document->setAttribute('$updatedAt', $document->getAttribute('dateUpdated'));
}
/**
* Migrate all Storage Buckets to use Internal ID.
*/
$internalId = $this->projectDB->getDocument('buckets', $document->getId())->getInternalId();
$this->createNewMetaData("bucket_{$internalId}");
/**
* Migrate all Storage Bucket Files.
*/
$this->migrateBucketFiles($document);
break;
case 'users':
/**
* Set 'phoneVerification' to false if not set.
*/
if (is_null($document->getAttribute('phoneVerification'))) {
$document->setAttribute('phoneVerification', false);
}
break;
case 'functions':
/**
* Migrate dateCreated to $createdAt.
*/
if (empty($document->getCreatedAt())) {
$document->setAttribute('$createdAt', $document->getAttribute('dateCreated'));
}
/**
* Migrate dateUpdated to $updatedAt.
*/
if (empty($document->getUpdatedAt())) {
$document->setAttribute('$updatedAt', $document->getAttribute('dateUpdated'));
}
break;
case 'deployments':
case 'executions':
case 'teams':
/**
* Migrate dateCreated to $createdAt.
*/
if (empty($document->getCreatedAt())) {
$document->setAttribute('$createdAt', $document->getAttribute('dateCreated'));
}
break;
}
return $document;
}
/**
* Creates new metadata that was introduced for a collection and enforces the Internal ID.
*
* @param string $id
* @return void
*/
protected function createNewMetaData(string $id, string $to = null): void
{
$to ??= $id;
/**
* Skip files collection.
*/
if (in_array($id, ['files', 'databases'])) {
return;
}
try {
/**
* Replace project UID with Internal ID.
*/
$this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getId()}_{$id}` RENAME TO `_{$this->project->getInternalId()}_{$to}`")->execute();
} catch (\Throwable $th) {
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
}
try {
/**
* Replace project UID with Internal ID on permissions table.
*/
$this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getId()}_{$id}_perms` RENAME TO `_{$this->project->getInternalId()}_{$to}_perms`")->execute();
} catch (\Throwable $th) {
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
}
try {
/**
* Add _createdAt attribute.
*/
$this->pdo->prepare("ALTER TABLE `_{$this->project->getInternalId()}_{$to}` ADD COLUMN IF NOT EXISTS `_createdAt` int unsigned DEFAULT NULL")->execute();
} catch (\Throwable $th) {
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
}
try {
/**
* Add _updatedAt attribute.
*/
$this->pdo->prepare("ALTER TABLE `_{$this->project->getInternalId()}_{$to}` ADD COLUMN IF NOT EXISTS `_updatedAt` int unsigned DEFAULT NULL")->execute();
} catch (\Throwable $th) {
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
}
try {
/**
* Create index for _createdAt.
*/
$this->pdo->prepare("CREATE INDEX IF NOT EXISTS `_created_at` ON `_{$this->project->getInternalId()}_{$to}` (`_createdAt`)")->execute();
} catch (\Throwable $th) {
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
}
try {
/**
* Create index for _updatedAt.
*/
$this->pdo->prepare("CREATE INDEX IF NOT EXISTS `_updated_at` ON `_{$this->project->getInternalId()}_{$to}` (`_updatedAt`)")->execute();
} catch (\Throwable $th) {
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
}
}
}
+5 -5
View File
@@ -2,6 +2,7 @@
namespace Appwrite\Network\Validator;
use Utopia\Validator\Hostname;
use Utopia\Validator;
/**
@@ -45,17 +46,16 @@ class Host extends Validator
*/
public function isValid($value): bool
{
// Check if value is valid URL
$urlValidator = new URL();
if (!$urlValidator->isValid($value)) {
return false;
}
if (\in_array(\parse_url($value, PHP_URL_HOST), $this->whitelist)) {
return true;
}
return false;
$hostname = \parse_url($value, PHP_URL_HOST);
$hostnameValidator = new Hostname($this->whitelist);
return $hostnameValidator->isValid($hostname);
}
/**
+1 -1
View File
@@ -66,7 +66,7 @@ class IP extends Validator
if (\filter_var($value, FILTER_VALIDATE_IP)) {
return true;
}
break;
break;
case self::V4:
if (\filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
+5 -6
View File
@@ -2,6 +2,7 @@
namespace Appwrite\Network\Validator;
use Utopia\Validator\Hostname;
use Utopia\Validator;
class Origin extends Validator
@@ -94,8 +95,8 @@ class Origin extends Validator
return 'Unsupported platform';
}
return 'Invalid Origin. Register your new client ('.$this->host.') as a new '
.$this->platforms[$this->client].' platform on your project console dashboard';
return 'Invalid Origin. Register your new client (' . $this->host . ') as a new '
. $this->platforms[$this->client] . ' platform on your project console dashboard';
}
/**
@@ -122,11 +123,9 @@ class Origin extends Validator
return true;
}
if (\in_array($host, $this->clients)) {
return true;
}
$validator = new Hostname($this->clients);
return false;
return $validator->isValid($host);
}
/**
+81 -36
View File
@@ -12,9 +12,12 @@ use Utopia\Storage\Device;
use Utopia\Storage\Storage;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Device\DOSpaces;
use Utopia\Storage\Device\Linode;
use Utopia\Storage\Device\Wasabi;
use Utopia\Storage\Device\Backblaze;
use Utopia\Storage\Device\S3;
use Exception;
use Utopia\Database\Validator\Authorization;
abstract class Worker
{
@@ -23,7 +26,7 @@ abstract class Worker
*
* @var array
*/
static protected array $errorCallbacks = [];
protected static array $errorCallbacks = [];
/**
* Associative array holding all information passed into the worker
@@ -50,7 +53,8 @@ abstract class Worker
* @return void
* @throws \Exception|\Throwable
*/
public function init() {
public function init()
{
throw new Exception("Please implement init method in worker");
}
@@ -61,7 +65,8 @@ abstract class Worker
* @return void
* @throws \Exception|\Throwable
*/
public function run() {
public function run()
{
throw new Exception("Please implement run method in worker");
}
@@ -72,12 +77,13 @@ abstract class Worker
* @return void
* @throws \Exception|\Throwable
*/
public function shutdown() {
public function shutdown()
{
throw new Exception("Please implement shutdown method in worker");
}
const DATABASE_PROJECT = 'project';
const DATABASE_CONSOLE = 'console';
public const DATABASE_PROJECT = 'project';
public const DATABASE_CONSOLE = 'console';
/**
* A wrapper around 'init' function with non-worker-specific code
@@ -89,7 +95,7 @@ abstract class Worker
{
try {
$this->init();
} catch(\Throwable $error) {
} catch (\Throwable $error) {
foreach (self::$errorCallbacks as $errorCallback) {
$errorCallback($error, "init", $this->getName());
}
@@ -107,8 +113,13 @@ abstract class Worker
public function perform(): void
{
try {
/**
* Disabling global authorization in workers.
*/
Authorization::disable();
Authorization::setDefaultStatus(false);
$this->run();
} catch(\Throwable $error) {
} catch (\Throwable $error) {
foreach (self::$errorCallbacks as $errorCallback) {
$errorCallback($error, "run", $this->getName(), $this->args);
}
@@ -127,7 +138,7 @@ abstract class Worker
{
try {
$this->shutdown();
} catch(\Throwable $error) {
} catch (\Throwable $error) {
foreach (self::$errorCallbacks as $errorCallback) {
$errorCallback($error, "shutdown", $this->getName());
}
@@ -154,7 +165,16 @@ abstract class Worker
*/
protected function getProjectDB(string $projectId): Database
{
return $this->getDB(self::DATABASE_PROJECT, $projectId);
$consoleDB = $this->getConsoleDB();
if ($projectId === 'console') {
return $consoleDB;
}
/** @var Document $project */
$project = Authorization::skip(fn() => $consoleDB->getDocument('projects', $projectId));
return $this->getDB(self::DATABASE_PROJECT, $projectId, $project->getInternalId());
}
/**
@@ -172,7 +192,7 @@ abstract class Worker
* @param string $projectId of internal or external DB
* @return Database
*/
private function getDB($type, $projectId = ''): Database
private function getDB(string $type, string $projectId = '', string $projectInternalId = ''): Database
{
global $register;
@@ -184,7 +204,7 @@ abstract class Worker
if (!$projectId) {
throw new \Exception('ProjectID not provided - cannot get database');
}
$namespace = "_{$projectId}";
$namespace = "_{$projectInternalId}";
break;
case self::DATABASE_CONSOLE:
$namespace = "_console";
@@ -209,15 +229,15 @@ abstract class Worker
throw new \Exception("Project does not exist: {$projectId}");
}
if ($type === self::DATABASE_CONSOLE && !$database->exists($database->getDefaultDatabase(), 'realtime')) {
if ($type === self::DATABASE_CONSOLE && !$database->exists($database->getDefaultDatabase(), '_metadata')) {
throw new \Exception('Console project not ready');
}
break; // leave loop if successful
} catch(\Exception $e) {
} catch (\Exception $e) {
Console::warning("Database not ready. Retrying connection ({$attempts})...");
if ($attempts >= DATABASE_RECONNECT_MAX_ATTEMPTS) {
throw new \Exception('Failed to connect to database: '. $e->getMessage());
throw new \Exception('Failed to connect to database: ' . $e->getMessage());
}
sleep($sleep);
}
@@ -231,7 +251,8 @@ abstract class Worker
* @param string $projectId of the project
* @return Device
*/
protected function getFunctionsDevice($projectId): Device {
protected function getFunctionsDevice($projectId): Device
{
return $this->getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $projectId);
}
@@ -240,7 +261,8 @@ abstract class Worker
* @param string $projectId of the project
* @return Device
*/
protected function getFilesDevice($projectId): Device {
protected function getFilesDevice($projectId): Device
{
return $this->getDevice(APP_STORAGE_UPLOADS . '/app-' . $projectId);
}
@@ -250,7 +272,8 @@ abstract class Worker
* @param string $projectId of the project
* @return Device
*/
protected function getBuildsDevice($projectId): Device {
protected function getBuildsDevice($projectId): Device
{
return $this->getDevice(APP_STORAGE_BUILDS . '/app-' . $projectId);
}
@@ -259,25 +282,47 @@ abstract class Worker
* @param string $root path of the device
* @return Device
*/
private function getDevice($root): Device
public function getDevice($root): Device
{
switch (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)) {
case Storage::DEVICE_LOCAL:default:
return new Local($root);
case Storage::DEVICE_S3:
$s3AccessKey = App::getEnv('_APP_STORAGE_S3_ACCESS_KEY', '');
$s3SecretKey = App::getEnv('_APP_STORAGE_S3_SECRET', '');
$s3Region = App::getEnv('_APP_STORAGE_S3_REGION', '');
$s3Bucket = App::getEnv('_APP_STORAGE_S3_BUCKET', '');
$s3Acl = 'private';
return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl);
case Storage::DEVICE_DO_SPACES:
$doSpacesAccessKey = App::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', '');
$doSpacesSecretKey = App::getEnv('_APP_STORAGE_DO_SPACES_SECRET', '');
$doSpacesRegion = App::getEnv('_APP_STORAGE_DO_SPACES_REGION', '');
$doSpacesBucket = App::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', '');
$doSpacesAcl = 'private';
return new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl);
case Storage::DEVICE_LOCAL:
default:
return new Local($root);
case Storage::DEVICE_S3:
$s3AccessKey = App::getEnv('_APP_STORAGE_S3_ACCESS_KEY', '');
$s3SecretKey = App::getEnv('_APP_STORAGE_S3_SECRET', '');
$s3Region = App::getEnv('_APP_STORAGE_S3_REGION', '');
$s3Bucket = App::getEnv('_APP_STORAGE_S3_BUCKET', '');
$s3Acl = 'private';
return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl);
case Storage::DEVICE_DO_SPACES:
$doSpacesAccessKey = App::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', '');
$doSpacesSecretKey = App::getEnv('_APP_STORAGE_DO_SPACES_SECRET', '');
$doSpacesRegion = App::getEnv('_APP_STORAGE_DO_SPACES_REGION', '');
$doSpacesBucket = App::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', '');
$doSpacesAcl = 'private';
return new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl);
case Storage::DEVICE_BACKBLAZE:
$backblazeAccessKey = App::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', '');
$backblazeSecretKey = App::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', '');
$backblazeRegion = App::getEnv('_APP_STORAGE_BACKBLAZE_REGION', '');
$backblazeBucket = App::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', '');
$backblazeAcl = 'private';
return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl);
case Storage::DEVICE_LINODE:
$linodeAccessKey = App::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', '');
$linodeSecretKey = App::getEnv('_APP_STORAGE_LINODE_SECRET', '');
$linodeRegion = App::getEnv('_APP_STORAGE_LINODE_REGION', '');
$linodeBucket = App::getEnv('_APP_STORAGE_LINODE_BUCKET', '');
$linodeAcl = 'private';
return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl);
case Storage::DEVICE_WASABI:
$wasabiAccessKey = App::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', '');
$wasabiSecretKey = App::getEnv('_APP_STORAGE_WASABI_SECRET', '');
$wasabiRegion = App::getEnv('_APP_STORAGE_WASABI_REGION', '');
$wasabiBucket = App::getEnv('_APP_STORAGE_WASABI_BUCKET', '');
$wasabiAcl = 'private';
return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl);
}
}
}
+13 -44
View File
@@ -8,40 +8,22 @@ use Appwrite\Utopia\Response\Model;
abstract class Format
{
/**
* @var App
*/
protected $app;
/**
* @var array
*/
protected $services;
protected App $app;
/**
* @var Route[]
*/
protected $routes;
protected array $routes;
/**
* @var Model[]
*/
protected $models;
/**
* @var array
*/
protected $keys;
/**
* @var int
*/
protected $authCount;
/**
* @var array
*/
protected $params = [
protected array $models;
protected array $services;
protected array $keys;
protected int $authCount;
protected array $params = [
'name' => '',
'description' => '',
'endpoint' => 'https://localhost',
@@ -56,14 +38,6 @@ abstract class Format
'license.url' => '',
];
/**
* @param App $app
* @param array $services
* @param Route[] $routes
* @param Model[] $models
* @param array $keys
* @param int $authCount
*/
public function __construct(App $app, array $services, array $routes, array $models, array $keys, int $authCount)
{
$this->app = $app;
@@ -99,7 +73,7 @@ abstract class Format
*
* @param string $key
* @param string $value
*
*
* @return self
*/
public function setParam(string $key, string $value): self
@@ -116,16 +90,11 @@ abstract class Format
*
* @param string $key
* @param string $default
*
*
* @return string
*/
public function getParam(string $key, string $default = ''): string
{
if(!isset($this->params[$key])) {
return $default;
}
return $this->params[$key];
return $this->params[$key] ?? $default;
}
}
+96 -104
View File
@@ -4,35 +4,40 @@ namespace Appwrite\Specification\Format;
use Appwrite\Specification\Format;
use Appwrite\Template\Template;
use Appwrite\Utopia\Response\Model;
use Utopia\Validator;
class OpenAPI3 extends Format
{
/**
* Get Name.
*
* Get format name
*
* @return string
*/
public function getName():string
public function getName(): string
{
return 'Open API 3';
}
/**
* Parse
*
* Parses Appwrite App to given format
*
* @return array
*/
protected function getNestedModels(Model $model, array &$usedModels): void
{
foreach ($model->getRules() as $rule) {
if (
in_array($model->getType(), $usedModels)
&& !in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float', 'double'])
) {
$usedModels[] = $rule['type'];
foreach ($this->models as $m) {
if ($m->getType() === $rule['type']) {
$this->getNestedModels($m, $usedModels);
return;
}
}
}
}
}
public function parse(): array
{
/**
* Specifications (v3.0.0):
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
*/
* Specifications (v3.0.0):
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
*/
$output = [
'openapi' => '3.0.0',
'info' => [
@@ -89,7 +94,7 @@ class OpenAPI3 extends Format
$usedModels = [];
foreach ($this->routes as $route) { /** @var \Utopia\Route $route */
foreach ($this->routes as $route) {
$url = \str_replace('/v1', '', $route->getPath());
$scope = $route->getLabel('scope', '');
$hide = $route->getLabel('sdk.hide', false);
@@ -100,38 +105,36 @@ class OpenAPI3 extends Format
}
$id = $route->getLabel('sdk.method', \uniqid());
$desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__.'/../../../../'.$route->getLabel('sdk.description', '')) : null;
$desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__ . '/../../../../' . $route->getLabel('sdk.description', '')) : null;
$produces = $route->getLabel('sdk.response.type', null);
$model = $route->getLabel('sdk.response.model', 'none');
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlatofrms = [];
$sdkPlatforms = [];
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
$sdkPlatofrms[] = APP_PLATFORM_CONSOLE;
$sdkPlatforms[] = APP_PLATFORM_CONSOLE;
break;
}
}
if(empty($routeSecurity)) {
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
if (empty($routeSecurity)) {
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
}
$temp = [
'summary' => $route->getDesc(),
'operationId' => $route->getLabel('sdk.namespace', 'default').ucfirst($id),
// 'consumes' => [],
// 'produces' => [$produces],
'operationId' => $route->getLabel('sdk.namespace', 'default') . ucfirst($id),
'tags' => [$route->getLabel('sdk.namespace', 'default')],
'description' => ($desc) ? \file_get_contents($desc) : '',
'responses' => [],
@@ -140,38 +143,31 @@ class OpenAPI3 extends Format
'weight' => $route->getOrder(),
'cookies' => $route->getLabel('sdk.cookies', false),
'type' => $route->getLabel('sdk.methodType', ''),
'demo' => Template::fromCamelCaseToDash($route->getLabel('sdk.namespace', 'default')).'/'.Template::fromCamelCaseToDash($id).'.md',
'demo' => Template::fromCamelCaseToDash($route->getLabel('sdk.namespace', 'default')) . '/' . Template::fromCamelCaseToDash($id) . '.md',
'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $route->getLabel('sdk.description', ''),
'rate-limit' => $route->getLabel('abuse-limit', 0),
'rate-time' => $route->getLabel('abuse-time', 3600),
'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'),
'scope' => $route->getLabel('scope', ''),
'platforms' => $sdkPlatofrms,
'platforms' => $sdkPlatforms,
'packaging' => $route->getLabel('sdk.packaging', false),
],
];
foreach ($this->models as $key => $value) {
if(\is_array($model)) {
$model = \array_map(function($m) use($value) {
if($m === $value->getType()) {
return $value;
}
return $m;
}, $model);
foreach ($this->models as $value) {
if (\is_array($model)) {
$model = \array_map(fn ($m) => $m === $value->getType() ? $value : $m, $model);
} else {
if($value->getType() === $model) {
if ($value->getType() === $model) {
$model = $value;
break;
}
}
}
if(!(\is_array($model)) && $model->isNone()) {
if (!(\is_array($model)) && $model->isNone()) {
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => (in_array($produces, [
'description' => in_array($produces, [
'image/*',
'image/jpeg',
'image/gif',
@@ -180,16 +176,11 @@ class OpenAPI3 extends Format
'image/svg-x',
'image/x-icon',
'image/bmp',
])) ? 'Image' : 'File',
// 'schema' => [
// 'type' => 'file'
// ],
]) ? 'Image' : 'File',
];
} else {
if(\is_array($model)) {
$modelDescription = \join(', or ', \array_map(function ($m) {
return $m->getName();
}, $model));
if (\is_array($model)) {
$modelDescription = \join(', or ', \array_map(fn ($m) => $m->getName(), $model));
// model has multiple possible responses, we will use oneOf
foreach ($model as $m) {
@@ -201,9 +192,7 @@ class OpenAPI3 extends Format
'content' => [
$produces => [
'schema' => [
'oneOf' => \array_map(function($m) {
return ['$ref' => '#/components/schemas/'.$m->getType()];
}, $model)
'oneOf' => \array_map(fn ($m) => ['$ref' => '#/components/schemas/' . $m->getType()], $model)
],
],
],
@@ -216,16 +205,15 @@ class OpenAPI3 extends Format
'content' => [
$produces => [
'schema' => [
'$ref' => '#/components/schemas/'.$model->getType(),
'$ref' => '#/components/schemas/' . $model->getType(),
],
],
],
];
}
}
if($route->getLabel('sdk.response.code', 500) === 204) {
if ($route->getLabel('sdk.response.code', 500) === 204) {
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')]['description'] = 'No content';
unset($temp['responses'][(string)$route->getLabel('sdk.response.code', '500')]['schema']);
}
@@ -233,8 +221,8 @@ class OpenAPI3 extends Format
if ((!empty($scope))) { // && 'public' != $scope
$securities = ['Project' => []];
foreach($route->getLabel('sdk.auth', []) as $security) {
if(array_key_exists($security, $this->keys)) {
foreach ($route->getLabel('sdk.auth', []) as $security) {
if (array_key_exists($security, $this->keys)) {
$securities[$security] = [];
}
}
@@ -257,7 +245,10 @@ class OpenAPI3 extends Format
$bodyRequired = [];
foreach ($route->getParams() as $name => $param) { // Set params
$validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator']; /* @var $validator \Utopia\Validator */
/**
* @var \Utopia\Validator $validator
*/
$validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator'];
$node = [
'name' => $name,
@@ -265,10 +256,16 @@ class OpenAPI3 extends Format
'required' => !$param['optional'],
];
foreach ($this->services as $service) {
if ($route->getLabel('sdk.namespace', 'default') === $service['name'] && in_array($name, $service['x-globalAttributes'] ?? [])) {
$node['x-global'] = true;
}
}
switch ((!empty($validator)) ? \get_class($validator) : '') {
case 'Utopia\Validator\Text':
$node['schema']['type'] = $validator->getType();
$node['schema']['x-example'] = '['.\strtoupper(Template::fromCamelCaseToSnake($node['name'])).']';
$node['schema']['x-example'] = '[' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . ']';
break;
case 'Utopia\Validator\Boolean':
$node['schema']['type'] = $validator->getType();
@@ -276,14 +273,14 @@ class OpenAPI3 extends Format
break;
case 'Utopia\Database\Validator\UID':
$node['schema']['type'] = $validator->getType();
$node['schema']['x-example'] = '['.\strtoupper(Template::fromCamelCaseToSnake($node['name'])).']';
$node['schema']['x-example'] = '[' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . ']';
break;
case 'Appwrite\Utopia\Database\Validator\CustomId':
if($route->getLabel('sdk.methodType', '') === 'upload') {
if ($route->getLabel('sdk.methodType', '') === 'upload') {
$node['schema']['x-upload-id'] = true;
}
$node['schema']['type'] = $validator->getType();
$node['schema']['x-example'] = '['.\strtoupper(Template::fromCamelCaseToSnake($node['name'])).']';
$node['schema']['x-example'] = '[' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . ']';
break;
case 'Appwrite\Network\Validator\Email':
$node['schema']['type'] = $validator->getType();
@@ -325,8 +322,9 @@ class OpenAPI3 extends Format
$node['schema']['format'] = 'password';
$node['schema']['x-example'] = 'password';
break;
case 'Utopia\Validator\Range': /** @var \Utopia\Validator\Range $validator */
$node['schema']['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number': $validator->getType();
case 'Utopia\Validator\Range':
/** @var \Utopia\Validator\Range $validator */
$node['schema']['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number' : $validator->getType();
$node['schema']['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float';
$node['schema']['x-example'] = $validator->getMin();
break;
@@ -347,7 +345,8 @@ class OpenAPI3 extends Format
$node['schema']['format'] = 'url';
$node['schema']['x-example'] = 'https://example.com';
break;
case 'Utopia\Validator\WhiteList': /** @var \Utopia\Validator\WhiteList $validator */
case 'Utopia\Validator\WhiteList':
/** @var \Utopia\Validator\WhiteList $validator */
$node['schema']['type'] = $validator->getType();
$node['schema']['x-example'] = $validator->getList()[0];
@@ -364,14 +363,14 @@ class OpenAPI3 extends Format
$node['schema']['default'] = $param['default'];
}
if (false !== \strpos($url, ':'.$name)) { // Param is in URL path
if (false !== \strpos($url, ':' . $name)) { // Param is in URL path
$node['in'] = 'path';
$temp['parameters'][] = $node;
} elseif ($route->getMethod() == 'GET') { // Param is in query
$node['in'] = 'query';
$temp['parameters'][] = $node;
} else { // Param is in payload
if(!$param['optional']) {
if (!$param['optional']) {
$bodyRequired[] = $name;
}
@@ -381,44 +380,39 @@ class OpenAPI3 extends Format
'x-example' => $node['schema']['x-example'] ?? null
];
if($node['schema']['x-upload-id'] ?? false) {
if ($node['schema']['x-upload-id'] ?? false) {
$body['content'][$consumes[0]]['schema']['properties'][$name]['x-upload-id'] = $node['schema']['x-upload-id'];
}
if(isset($node['default'])) {
if (isset($node['default'])) {
$body['content'][$consumes[0]]['schema']['properties'][$name]['default'] = $node['default'];
}
if(\array_key_exists('items', $node['schema'])) {
if (\array_key_exists('items', $node['schema'])) {
$body['content'][$consumes[0]]['schema']['properties'][$name]['items'] = $node['schema']['items'];
}
if ($node['x-global'] ?? false) {
$body['content'][$consumes[0]]['schema']['properties'][$name]['x-global'] = true;
}
}
$url = \str_replace(':'.$name, '{'.$name.'}', $url);
$url = \str_replace(':' . $name, '{' . $name . '}', $url);
}
if(!empty($bodyRequired)) {
if (!empty($bodyRequired)) {
$body['content'][$consumes[0]]['schema']['required'] = $bodyRequired;
}
if(!empty($body['content'][$consumes[0]]['schema']['properties'])) {
if (!empty($body['content'][$consumes[0]]['schema']['properties'])) {
$temp['requestBody'] = $body;
}
//$temp['consumes'] = $consumes;
$output['paths'][$url][\strtolower($route->getMethod())] = $temp;
}
foreach ($this->models as $model) {
foreach ($model->getRules() as $rule) {
if (
in_array($model->getType(), $usedModels)
&& !in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float'])
) {
$usedModels[] = $rule['type'];
}
}
$this->getNestedModels($model, $usedModels);
}
foreach ($this->models as $model) {
@@ -434,19 +428,19 @@ class OpenAPI3 extends Format
'type' => 'object',
];
if(!empty($rules)) {
if (!empty($rules)) {
$output['components']['schemas'][$model->getType()]['properties'] = [];
}
if($model->isAny()) {
if ($model->isAny()) {
$output['components']['schemas'][$model->getType()]['additionalProperties'] = true;
}
if(!empty($required)) {
if (!empty($required)) {
$output['components']['schemas'][$model->getType()]['required'] = $required;
}
foreach($model->getRules() as $name => $rule) {
foreach ($model->getRules() as $name => $rule) {
$type = '';
$format = null;
$items = null;
@@ -484,29 +478,29 @@ class OpenAPI3 extends Format
$type = 'object';
$rule['type'] = ($rule['type']) ? $rule['type'] : 'none';
if(\is_array($rule['type'])) {
if($rule['array']) {
if (\is_array($rule['type'])) {
if ($rule['array']) {
$items = [
'anyOf' => \array_map(function($type) {
return ['$ref' => '#/components/schemas/'.$type];
'anyOf' => \array_map(function ($type) {
return ['$ref' => '#/components/schemas/' . $type];
}, $rule['type'])
];
} else {
$items = [
'oneOf' => \array_map(function($type) {
return ['$ref' => '#/components/schemas/'.$type];
'oneOf' => \array_map(function ($type) {
return ['$ref' => '#/components/schemas/' . $type];
}, $rule['type'])
];
}
} else {
$items = [
'$ref' => '#/components/schemas/'.$rule['type'],
'$ref' => '#/components/schemas/' . $rule['type'],
];
}
break;
}
if($rule['array']) {
if ($rule['array']) {
$output['components']['schemas'][$model->getType()]['properties'][$name] = [
'type' => 'array',
'description' => $rule['description'] ?? '',
@@ -516,10 +510,9 @@ class OpenAPI3 extends Format
'x-example' => $rule['example'] ?? null,
];
if($format) {
if ($format) {
$output['components']['schemas'][$model->getType()]['properties'][$name]['items']['format'] = $format;
}
} else {
$output['components']['schemas'][$model->getType()]['properties'][$name] = [
'type' => $type,
@@ -527,12 +520,11 @@ class OpenAPI3 extends Format
'x-example' => $rule['example'] ?? null,
];
if($format) {
if ($format) {
$output['components']['schemas'][$model->getType()]['properties'][$name]['format'] = $format;
}
}
if($items) {
if ($items) {
$output['components']['schemas'][$model->getType()]['properties'][$name]['items'] = $items;
}
if (!in_array($name, $required)) {
+91 -95
View File
@@ -4,34 +4,39 @@ namespace Appwrite\Specification\Format;
use Appwrite\Specification\Format;
use Appwrite\Template\Template;
use Appwrite\Utopia\Response\Model;
use Utopia\Validator;
class Swagger2 extends Format
{
/**
* Get Name.
*
* Get format name
*
* @return string
*/
public function getName():string
public function getName(): string
{
return 'Swagger 2';
}
/**
* Parse
*
* Parses Appwrite App to given format
*
* @return array
*/
protected function getNestedModels(Model $model, array &$usedModels): void
{
foreach ($model->getRules() as $rule) {
if (
in_array($model->getType(), $usedModels)
&& !in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float', 'double'])
) {
$usedModels[] = $rule['type'];
foreach ($this->models as $m) {
if ($m->getType() === $rule['type']) {
$this->getNestedModels($m, $usedModels);
return;
}
}
}
}
}
public function parse(): array
{
/*
* Specifications (v3.0.0):
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
* Specifications (v2.0):
* https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md
*/
$output = [
'swagger' => '2.0',
@@ -87,7 +92,8 @@ class Swagger2 extends Format
$usedModels = [];
foreach ($this->routes as $route) { /** @var \Utopia\Route $route */
foreach ($this->routes as $route) {
/** @var \Utopia\Route $route */
$url = \str_replace('/v1', '', $route->getPath());
$scope = $route->getLabel('scope', '');
$hide = $route->getLabel('sdk.hide', false);
@@ -98,36 +104,36 @@ class Swagger2 extends Format
}
$id = $route->getLabel('sdk.method', \uniqid());
$desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__.'/../../../../'.$route->getLabel('sdk.description', '')) : null;
$desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__ . '/../../../../' . $route->getLabel('sdk.description', '')) : null;
$produces = $route->getLabel('sdk.response.type', null);
$model = $route->getLabel('sdk.response.model', 'none');
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlatofrms = [];
$sdkPlatforms = [];
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
$sdkPlatofrms[] = APP_PLATFORM_CONSOLE;
$sdkPlatforms[] = APP_PLATFORM_CONSOLE;
break;
}
}
if(empty($routeSecurity)) {
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
if (empty($routeSecurity)) {
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
}
$temp = [
'summary' => $route->getDesc(),
'operationId' => $route->getLabel('sdk.namespace', 'default').ucfirst($id),
'operationId' => $route->getLabel('sdk.namespace', 'default') . ucfirst($id),
'consumes' => [],
'produces' => [],
'tags' => [$route->getLabel('sdk.namespace', 'default')],
@@ -138,40 +144,35 @@ class Swagger2 extends Format
'weight' => $route->getOrder(),
'cookies' => $route->getLabel('sdk.cookies', false),
'type' => $route->getLabel('sdk.methodType', ''),
'demo' => Template::fromCamelCaseToDash($route->getLabel('sdk.namespace', 'default')).'/'.Template::fromCamelCaseToDash($id).'.md',
'demo' => Template::fromCamelCaseToDash($route->getLabel('sdk.namespace', 'default')) . '/' . Template::fromCamelCaseToDash($id) . '.md',
'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $route->getLabel('sdk.description', ''),
'rate-limit' => $route->getLabel('abuse-limit', 0),
'rate-time' => $route->getLabel('abuse-time', 3600),
'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'),
'scope' => $route->getLabel('scope', ''),
'platforms' => $sdkPlatofrms,
'platforms' => $sdkPlatforms,
'packaging' => $route->getLabel('sdk.packaging', false),
],
];
if($produces) {
if ($produces) {
$temp['produces'][] = $produces;
}
foreach ($this->models as $key => $value) {
if(\is_array($model)) {
$model = \array_map(function($m) use($value) {
if($m === $value->getType()) {
return $value;
}
return $m;
}, $model);
foreach ($this->models as $value) {
if (\is_array($model)) {
$model = \array_map(fn ($m) => $m === $value->getType() ? $value : $m, $model);
} else {
if($value->getType() === $model) {
if ($value->getType() === $model) {
$model = $value;
break;
}
}
}
if(!(\is_array($model)) && $model->isNone()) {
if (!(\is_array($model)) && $model->isNone()) {
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => (in_array($produces, [
'description' => in_array($produces, [
'image/*',
'image/jpeg',
'image/gif',
@@ -180,17 +181,14 @@ class Swagger2 extends Format
'image/svg-x',
'image/x-icon',
'image/bmp',
])) ? 'Image' : 'File',
]) ? 'Image' : 'File',
'schema' => [
'type' => 'file'
],
];
} else {
if(\is_array($model)) {
$modelDescription = \join(', or ', \array_map(function ($m) {
return $m->getName();
}, $model));
if (\is_array($model)) {
$modelDescription = \join(', or ', \array_map(fn ($m) => $m->getName(), $model));
// model has multiple possible responses, we will use oneOf
foreach ($model as $m) {
$usedModels[] = $m->getType();
@@ -198,8 +196,8 @@ class Swagger2 extends Format
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => $modelDescription,
'schema' => [
'x-oneOf' => \array_map(function($m) {
return ['$ref' => '#/definitions/'.$m->getType()];
'x-oneOf' => \array_map(function ($m) {
return ['$ref' => '#/definitions/' . $m->getType()];
}, $model)
],
];
@@ -209,13 +207,13 @@ class Swagger2 extends Format
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => $model->getName(),
'schema' => [
'$ref' => '#/definitions/'.$model->getType(),
'$ref' => '#/definitions/' . $model->getType(),
],
];
}
}
if(in_array($route->getLabel('sdk.response.code', 500), [204, 301, 302, 308], true)) {
if (in_array($route->getLabel('sdk.response.code', 500), [204, 301, 302, 308], true)) {
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')]['description'] = 'No content';
unset($temp['responses'][(string)$route->getLabel('sdk.response.code', '500')]['schema']);
}
@@ -223,8 +221,8 @@ class Swagger2 extends Format
if ((!empty($scope))) { // && 'public' != $scope
$securities = ['Project' => []];
foreach($route->getLabel('sdk.auth', []) as $security) {
if(array_key_exists($security, $this->keys)) {
foreach ($route->getLabel('sdk.auth', []) as $security) {
if (array_key_exists($security, $this->keys)) {
$securities[$security] = [];
}
}
@@ -245,7 +243,8 @@ class Swagger2 extends Format
$bodyRequired = [];
foreach ($route->getParams() as $name => $param) { // Set params
$validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator']; /** @var \Utopia\Validator $validator */
/** @var \Utopia\Validator $validator */
$validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator'];
$node = [
'name' => $name,
@@ -253,25 +252,31 @@ class Swagger2 extends Format
'required' => !$param['optional'],
];
foreach ($this->services as $service) {
if ($route->getLabel('sdk.namespace', 'default') === $service['name'] && in_array($name, $service['x-globalAttributes'] ?? [])) {
$node['x-global'] = true;
}
}
switch ((!empty($validator)) ? \get_class($validator) : '') {
case 'Utopia\Validator\Text':
$node['type'] = $validator->getType();
$node['x-example'] = '['.\strtoupper(Template::fromCamelCaseToSnake($node['name'])).']';
$node['x-example'] = '[' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . ']';
break;
case 'Utopia\Validator\Boolean':
$node['type'] = $validator->getType();
$node['x-example'] = false;
break;
case 'Appwrite\Utopia\Database\Validator\CustomId':
if($route->getLabel('sdk.methodType', '') === 'upload') {
if ($route->getLabel('sdk.methodType', '') === 'upload') {
$node['x-upload-id'] = true;
}
$node['type'] = $validator->getType();
$node['x-example'] = '['.\strtoupper(Template::fromCamelCaseToSnake($node['name'])).']';
$node['x-example'] = '[' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . ']';
break;
case 'Utopia\Database\Validator\UID':
$node['type'] = $validator->getType();
$node['x-example'] = '['.\strtoupper(Template::fromCamelCaseToSnake($node['name'])).']';
$node['x-example'] = '[' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . ']';
break;
case 'Appwrite\Network\Validator\Email':
$node['type'] = $validator->getType();
@@ -289,7 +294,6 @@ class Swagger2 extends Format
$node['type'] = 'object';
$node['default'] = (empty($param['default'])) ? new \stdClass() : $param['default'];
$node['x-example'] = '{}';
//$node['format'] = 'json';
break;
case 'Utopia\Storage\Validator\File':
$consumes = ['multipart/form-data'];
@@ -315,8 +319,9 @@ class Swagger2 extends Format
$node['format'] = 'password';
$node['x-example'] = 'password';
break;
case 'Utopia\Validator\Range': /** @var \Utopia\Validator\Range $validator */
$node['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number': $validator->getType();
case 'Utopia\Validator\Range':
/** @var \Utopia\Validator\Range $validator */
$node['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number' : $validator->getType();
$node['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float';
$node['x-example'] = $validator->getMin();
break;
@@ -337,7 +342,8 @@ class Swagger2 extends Format
$node['format'] = 'url';
$node['x-example'] = 'https://example.com';
break;
case 'Utopia\Validator\WhiteList': /** @var \Utopia\Validator\WhiteList $validator */
case 'Utopia\Validator\WhiteList':
/** @var \Utopia\Validator\WhiteList $validator */
$node['type'] = $validator->getType();
$node['x-example'] = $validator->getList()[0];
@@ -354,14 +360,13 @@ class Swagger2 extends Format
$node['default'] = $param['default'];
}
if (false !== \strpos($url, ':'.$name)) { // Param is in URL path
if (false !== \strpos($url, ':' . $name)) { // Param is in URL path
$node['in'] = 'path';
$temp['parameters'][] = $node;
} elseif ($route->getMethod() == 'GET') { // Param is in query
$node['in'] = 'query';
$temp['parameters'][] = $node;
} else { // Param is in payload
if (\in_array('multipart/form-data', $consumes)) {
$node['in'] = 'formData';
$temp['parameters'][] = $node;
@@ -380,19 +385,23 @@ class Swagger2 extends Format
'x-example' => $node['x-example'] ?? null,
];
if(\array_key_exists('items', $node)) {
if ($node['x-global'] ?? false) {
$body['schema']['properties'][$name]['x-global'] = true;
}
if (\array_key_exists('items', $node)) {
$body['schema']['properties'][$name]['items'] = $node['items'];
}
}
$url = \str_replace(':'.$name, '{'.$name.'}', $url);
$url = \str_replace(':' . $name, '{' . $name . '}', $url);
}
if(!empty($bodyRequired)) {
if (!empty($bodyRequired)) {
$body['schema']['required'] = $bodyRequired;
}
if(!empty($body['schema']['properties'])) {
if (!empty($body['schema']['properties'])) {
$temp['parameters'][] = $body;
}
@@ -402,14 +411,7 @@ class Swagger2 extends Format
}
foreach ($this->models as $model) {
foreach ($model->getRules() as $rule) {
if (
in_array($model->getType(), $usedModels)
&& !in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float'])
) {
$usedModels[] = $rule['type'];
}
}
$this->getNestedModels($model, $usedModels);
}
foreach ($this->models as $model) {
@@ -425,19 +427,19 @@ class Swagger2 extends Format
'type' => 'object',
];
if(!empty($rules)) {
if (!empty($rules)) {
$output['definitions'][$model->getType()]['properties'] = [];
}
if($model->isAny()) {
if ($model->isAny()) {
$output['definitions'][$model->getType()]['additionalProperties'] = true;
}
if(!empty($required)) {
if (!empty($required)) {
$output['definitions'][$model->getType()]['required'] = $required;
}
foreach($model->getRules() as $name => $rule) {
foreach ($model->getRules() as $name => $rule) {
$type = '';
$format = null;
$items = null;
@@ -474,24 +476,20 @@ class Swagger2 extends Format
$type = 'object';
$rule['type'] = ($rule['type']) ?: 'none';
if(\is_array($rule['type'])) {
if($rule['array']) {
if (\is_array($rule['type'])) {
if ($rule['array']) {
$items = [
'x-anyOf' => \array_map(function($type) {
return ['$ref' => '#/definitions/'.$type];
}, $rule['type'])
'x-anyOf' => \array_map(fn ($type) => ['$ref' => '#/definitions/' . $type], $rule['type'])
];
} else {
$items = [
'x-oneOf' => \array_map(function($type) {
return ['$ref' => '#/definitions/'.$type];
}, $rule['type'])
'x-oneOf' => \array_map(fn ($type) => ['$ref' => '#/definitions/' . $type], $rule['type'])
];
}
} else {
$items = [
'type' => $type,
'$ref' => '#/definitions/'.$rule['type'],
'$ref' => '#/definitions/' . $rule['type'],
];
}
break;
@@ -507,7 +505,7 @@ class Swagger2 extends Format
continue;
}
if($rule['array']) {
if ($rule['array']) {
$output['definitions'][$model->getType()]['properties'][$name] = [
'type' => 'array',
'description' => $rule['description'] ?? '',
@@ -517,10 +515,9 @@ class Swagger2 extends Format
'x-example' => $rule['example'] ?? null,
];
if($format) {
if ($format) {
$output['definitions'][$model->getType()]['properties'][$name]['items']['format'] = $format;
}
} else {
$output['definitions'][$model->getType()]['properties'][$name] = [
'type' => $type,
@@ -528,12 +525,11 @@ class Swagger2 extends Format
'x-example' => $rule['example'] ?? null,
];
if($format) {
if ($format) {
$output['definitions'][$model->getType()]['properties'][$name]['format'] = $format;
}
}
if($items) {
if ($items) {
$output['definitions'][$model->getType()]['properties'][$name]['items'] = $items;
}
if (!in_array($name, $required)) {
+3 -9
View File
@@ -4,14 +4,8 @@ namespace Appwrite\Specification;
class Specification
{
/**
* @var Format
*/
protected $format;
protected Format $format;
/**
* @param Format $format
*/
public function __construct(Format $format)
{
$this->format = $format;
@@ -24,7 +18,7 @@ class Specification
*
* @return string
*/
public function getName():string
public function getName(): string
{
return $this->format->getName();
}
@@ -40,4 +34,4 @@ class Specification
{
return $this->format->parse();
}
}
}
+14 -10
View File
@@ -113,20 +113,24 @@ class Stats
$this->statsd->count('network.all' . $tags, $networkRequestSize + $networkResponseSize);
$dbMetrics = [
'database.collections.create',
'database.collections.read',
'database.collections.update',
'database.collections.delete',
'database.documents.create',
'database.documents.read',
'database.documents.update',
'database.documents.delete',
'databases.create',
'databases.read',
'databases.update',
'databases.delete',
'databases.collections.create',
'databases.collections.read',
'databases.collections.update',
'databases.collections.delete',
'databases.documents.create',
'databases.documents.read',
'databases.documents.update',
'databases.documents.delete',
];
foreach ($dbMetrics as $metric) {
$value = $this->params[$metric] ?? 0;
if ($value >= 1) {
$tags = ",projectId={$projectId},collectionId=" . ($this->params['collectionId'] ?? '');
$tags = ",projectId={$projectId},collectionId=" . ($this->params['collectionId'] ?? '') . ",databaseId=" . ($this->params['databaseId'] ?? '');
$this->statsd->increment($metric . $tags);
}
}
@@ -173,7 +177,7 @@ class Stats
foreach ($sessionsMetrics as $metric) {
$value = $this->params[$metric] ?? 0;
if ($value >= 1) {
$tags = ",projectId={$projectId},provider=". ($this->params['provider'] ?? '');
$tags = ",projectId={$projectId},provider=" . ($this->params['provider'] ?? '');
$this->statsd->count($metric . $tags, $value);
}
}
+351
View File
@@ -0,0 +1,351 @@
<?php
namespace Appwrite\Stats;
use Utopia\Database\Database;
use Utopia\Database\Document;
use InfluxDB\Database as InfluxDatabase;
use DateTime;
class Usage
{
protected InfluxDatabase $influxDB;
protected Database $database;
protected $errorHandler;
private array $latestTime = [];
// all the mertics that we are collecting
protected array $metrics = [
'requests' => [
'table' => 'appwrite_usage_requests_all',
],
'network' => [
'table' => 'appwrite_usage_network_all',
],
'executions' => [
'table' => 'appwrite_usage_executions_all',
],
'databases.create' => [
'table' => 'appwrite_usage_databases_create',
],
'databases.read' => [
'table' => 'appwrite_usage_databases_read',
],
'databases.update' => [
'table' => 'appwrite_usage_databases_update',
],
'databases.delete' => [
'table' => 'appwrite_usage_databases_delete',
],
'databases.collections.create' => [
'table' => 'appwrite_usage_databases_collections_create',
],
'databases.collections.read' => [
'table' => 'appwrite_usage_databases_collections_read',
],
'databases.collections.update' => [
'table' => 'appwrite_usage_databases_collections_update',
],
'databases.collections.delete' => [
'table' => 'appwrite_usage_databases_collections_delete',
],
'databases.documents.create' => [
'table' => 'appwrite_usage_databases_documents_create',
],
'databases.documents.read' => [
'table' => 'appwrite_usage_databases_documents_read',
],
'databases.documents.update' => [
'table' => 'appwrite_usage_databases_documents_update',
],
'databases.documents.delete' => [
'table' => 'appwrite_usage_databases_documents_delete',
],
'databases.databaseId.collections.create' => [
'table' => 'appwrite_usage_databases_collections_create',
'groupBy' => ['databaseId'],
],
'databases.databaseId.collections.read' => [
'table' => 'appwrite_usage_databases_collections_read',
'groupBy' => ['databaseId'],
],
'databases.databaseId.collections.update' => [
'table' => 'appwrite_usage_databases_collections_update',
'groupBy' => ['databaseId'],
],
'databases.databaseId.collections.delete' => [
'table' => 'appwrite_usage_databases_collections_delete',
'groupBy' => ['databaseId'],
],
'databases.databaseId.documents.create' => [
'table' => 'appwrite_usage_databases_documents_create',
'groupBy' => ['databaseId'],
],
'databases.databaseId.documents.read' => [
'table' => 'appwrite_usage_databases_documents_read',
'groupBy' => ['databaseId'],
],
'database.databaseId.documents.update' => [
'table' => 'appwrite_usage_databases_documents_update',
'groupBy' => ['databaseId'],
],
'databases.databaseId.documents.delete' => [
'table' => 'appwrite_usage_databases_documents_delete',
'groupBy' => ['databaseId'],
],
'databases.databaseId.collections.collectionId.documents.create' => [
'table' => 'appwrite_usage_databases_documents_create',
'groupBy' => ['collectionId'],
],
'databases.databaseId.collections.collectionId.documents.read' => [
'table' => 'appwrite_usage_databases_documents_read',
'groupBy' => ['databaseId', 'collectionId'],
],
'databases.databaseId.collections.collectionId.documents.update' => [
'table' => 'appwrite_usage_databases_documents_update',
'groupBy' => ['databaseId', 'collectionId'],
],
'databases.databaseId.collections.collectionId.documents.delete' => [
'table' => 'appwrite_usage_databases_documents_delete',
'groupBy' => ['databaseId', 'collectionId'],
],
'storage.buckets.create' => [
'table' => 'appwrite_usage_storage_buckets_create',
],
'storage.buckets.read' => [
'table' => 'appwrite_usage_storage_buckets_read',
],
'storage.buckets.update' => [
'table' => 'appwrite_usage_storage_buckets_update',
],
'storage.buckets.delete' => [
'table' => 'appwrite_usage_storage_buckets_delete',
],
'storage.files.create' => [
'table' => 'appwrite_usage_storage_files_create',
],
'storage.files.read' => [
'table' => 'appwrite_usage_storage_files_read',
],
'storage.files.update' => [
'table' => 'appwrite_usage_storage_files_update',
],
'storage.files.delete' => [
'table' => 'appwrite_usage_storage_files_delete',
],
'storage.buckets.bucketId.files.create' => [
'table' => 'appwrite_usage_storage_files_create',
'groupBy' => ['bucketId'],
],
'storage.buckets.bucketId.files.read' => [
'table' => 'appwrite_usage_storage_files_read',
'groupBy' => ['bucketId'],
],
'storage.buckets.bucketId.files.update' => [
'table' => 'appwrite_usage_storage_files_update',
'groupBy' => ['bucketId'],
],
'storage.buckets.bucketId.files.delete' => [
'table' => 'appwrite_usage_storage_files_delete',
'groupBy' => ['bucketId'],
],
'users.create' => [
'table' => 'appwrite_usage_users_create',
],
'users.read' => [
'table' => 'appwrite_usage_users_read',
],
'users.update' => [
'table' => 'appwrite_usage_users_update',
],
'users.delete' => [
'table' => 'appwrite_usage_users_delete',
],
'users.sessions.create' => [
'table' => 'appwrite_usage_users_sessions_create',
],
'users.sessions.provider.create' => [
'table' => 'appwrite_usage_users_sessions_create',
'groupBy' => ['provider'],
],
'users.sessions.delete' => [
'table' => 'appwrite_usage_users_sessions_delete',
],
'functions.functionId.executions' => [
'table' => 'appwrite_usage_executions_all',
'groupBy' => ['functionId'],
],
'functions.functionId.compute' => [
'table' => 'appwrite_usage_executions_time',
'groupBy' => ['functionId'],
],
'functions.functionId.failures' => [
'table' => 'appwrite_usage_executions_all',
'groupBy' => ['functionId'],
'filters' => [
'functionStatus' => 'failed',
],
],
];
protected array $periods = [
[
'key' => '30m',
'multiplier' => 1800,
'startTime' => '-24 hours',
],
[
'key' => '1d',
'multiplier' => 86400,
'startTime' => '-90 days',
],
];
public function __construct(Database $database, InfluxDatabase $influxDB, callable $errorHandler = null)
{
$this->database = $database;
$this->influxDB = $influxDB;
$this->errorHandler = $errorHandler;
}
/**
* Create or Update Mertic
* Create or update each metric in the stats collection for the given project
*
* @param string $projectId
* @param int $time
* @param string $period
* @param string $metric
* @param int $value
* @param int $type
*
* @return void
*/
private function createOrUpdateMetric(string $projectId, int $time, string $period, string $metric, int $value, int $type): void
{
$id = \md5("{$time}_{$period}_{$metric}");
$this->database->setNamespace('_console');
$project = $this->database->getDocument('projects', $projectId);
$this->database->setNamespace('_' . $project->getInternalId());
try {
$document = $this->database->getDocument('stats', $id);
if ($document->isEmpty()) {
$this->database->createDocument('stats', new Document([
'$id' => $id,
'period' => $period,
'time' => $time,
'metric' => $metric,
'value' => $value,
'type' => $type,
]));
} else {
$this->database->updateDocument(
'stats',
$document->getId(),
$document->setAttribute('value', $value)
);
}
$this->latestTime[$metric][$period] = $time;
} catch (\Exception $e) { // if projects are deleted this might fail
if (is_callable($this->errorHandler)) {
call_user_func($this->errorHandler, $e, "sync_project_{$projectId}_metric_{$metric}");
} else {
throw $e;
}
}
}
/**
* Sync From InfluxDB
* Sync stats from influxDB to stats collection in the Appwrite database
*
* @param string $metric
* @param array $options
* @param array $period
*
* @return void
*/
private function syncFromInfluxDB(string $metric, array $options, array $period): void
{
$start = DateTime::createFromFormat('U', \strtotime($period['startTime']))->format(DateTime::RFC3339);
if (!empty($this->latestTime[$metric][$period['key']])) {
$start = DateTime::createFromFormat('U', $this->latestTime[$metric][$period['key']])->format(DateTime::RFC3339);
}
$end = DateTime::createFromFormat('U', \strtotime('now'))->format(DateTime::RFC3339);
$table = $options['table']; //Which influxdb table to query for this metric
$groupBy = empty($options['groupBy']) ? '' : ', ' . implode(', ', array_map(fn($groupBy) => '"' . $groupBy . '" ', $options['groupBy'])); //Some sub level metrics may be grouped by other tags like collectionId, bucketId, etc
$filters = $options['filters'] ?? []; // Some metrics might have additional filters, like function's status
if (!empty($filters)) {
$filters = ' AND ' . implode(' AND ', array_map(fn ($filter, $value) => "\"{$filter}\"='{$value}'", array_keys($filters), array_values($filters)));
} else {
$filters = '';
}
$query = "SELECT sum(value) AS \"value\" ";
$query .= "FROM \"{$table}\" ";
$query .= "WHERE \"time\" > '{$start}' ";
$query .= "AND \"time\" < '{$end}' ";
$query .= "AND \"metric_type\"='counter' {$filters} ";
$query .= "GROUP BY time({$period['key']}), \"projectId\" {$groupBy} ";
$query .= "FILL(null)";
$result = $this->influxDB->query($query);
$points = $result->getPoints();
foreach ($points as $point) {
$projectId = $point['projectId'];
if (!empty($projectId) && $projectId !== 'console') {
$metricUpdated = $metric;
if (!empty($groupBy)) {
foreach ($options['groupBy'] as $groupBy) {
$groupedBy = $point[$groupBy] ?? '';
if (empty($groupedBy)) {
continue;
}
$metricUpdated = str_replace($groupBy, $groupedBy, $metric);
}
}
$time = \strtotime($point['time']);
$value = (!empty($point['value'])) ? $point['value'] : 0;
$this->createOrUpdateMetric(
$projectId,
$time,
$period['key'],
$metricUpdated,
$value,
0
);
}
}
}
/**
* Collect Stats
* Collect all the stats from Influd DB to Database
*
* @return void
*/
public function collect(): void
{
foreach ($this->metrics as $metric => $options) { //for each metrics
foreach ($this->periods as $period) { // aggregate data for each period
try {
$this->syncFromInfluxDB($metric, $options, $period);
} catch (\Exception $e) {
if (is_callable($this->errorHandler)) {
call_user_func($this->errorHandler, $e);
} else {
throw $e;
}
}
}
}
}
}
+277
View File
@@ -0,0 +1,277 @@
<?php
namespace Appwrite\Stats;
use Utopia\Database\Database;
use Utopia\Database\Document;
class UsageDB extends Usage
{
public function __construct(Database $database, callable $errorHandler = null)
{
$this->database = $database;
$this->errorHandler = $errorHandler;
}
/**
* Create or Update Mertic
* Create or update each metric in the stats collection for the given project
*
* @param string $projectId
* @param string $metric
* @param int $value
*
* @return void
*/
private function createOrUpdateMetric(string $projectId, string $metric, int $value): void
{
foreach ($this->periods as $options) {
$period = $options['key'];
$time = (int) (floor(time() / $options['multiplier']) * $options['multiplier']);
$id = \md5("{$time}_{$period}_{$metric}");
$this->database->setNamespace('_' . $projectId);
try {
$document = $this->database->getDocument('stats', $id);
if ($document->isEmpty()) {
$this->database->createDocument('stats', new Document([
'$id' => $id,
'period' => $period,
'time' => $time,
'metric' => $metric,
'value' => $value,
'type' => 1,
]));
} else {
$this->database->updateDocument(
'stats',
$document->getId(),
$document->setAttribute('value', $value)
);
}
} catch (\Exception$e) { // if projects are deleted this might fail
if (is_callable($this->errorHandler)) {
call_user_func($this->errorHandler, $e, "sync_project_{$projectId}_metric_{$metric}");
} else {
throw $e;
}
}
}
}
/**
* Foreach Document
* Call provided callback for each document in the collection
*
* @param string $projectId
* @param string $collection
* @param array $queries
* @param callable $callback
*
* @return void
*/
private function foreachDocument(string $projectId, string $collection, array $queries, callable $callback): void
{
$limit = 50;
$results = [];
$sum = $limit;
$latestDocument = null;
$this->database->setNamespace('_' . $projectId);
while ($sum === $limit) {
try {
$results = $this->database->find($collection, $queries, $limit, cursor:$latestDocument);
} catch (\Exception $e) {
if (is_callable($this->errorHandler)) {
call_user_func($this->errorHandler, $e, "fetch_documents_project_{$projectId}_collection_{$collection}");
return;
} else {
throw $e;
}
}
if (empty($results)) {
return;
}
$sum = count($results);
foreach ($results as $document) {
if (is_callable($callback)) {
$callback($document);
}
}
$latestDocument = $results[array_key_last($results)];
}
}
/**
* Sum
* Calculate sum of a attribute of documents in collection
*
* @param string $projectId
* @param string $collection
* @param string $attribute
* @param string $metric
*
* @return int
*/
private function sum(string $projectId, string $collection, string $attribute, string $metric): int
{
$this->database->setNamespace('_' . $projectId);
try {
$sum = (int) $this->database->sum($collection, $attribute);
$this->createOrUpdateMetric($projectId, $metric, $sum);
return $sum;
} catch (\Exception $e) {
if (is_callable($this->errorHandler)) {
call_user_func($this->errorHandler, $e, "fetch_sum_project_{$projectId}_collection_{$collection}");
} else {
throw $e;
}
}
}
/**
* Count
* Count number of documents in collection
*
* @param string $projectId
* @param string $collection
* @param string $metric
*
* @return int
*/
private function count(string $projectId, string $collection, string $metric): int
{
$this->database->setNamespace('_' . $projectId);
try {
$count = $this->database->count($collection);
$this->createOrUpdateMetric($projectId, $metric, $count);
return $count;
} catch (\Exception $e) {
if (is_callable($this->errorHandler)) {
call_user_func($this->errorHandler, $e, "fetch_count_project_{$projectId}_collection_{$collection}");
} else {
throw $e;
}
}
}
/**
* Deployments Total
* Total sum of storage used by deployments
*
* @param string $projectId
*
* @return int
*/
private function deploymentsTotal(string $projectId): int
{
return $this->sum($projectId, 'deployments', 'size', 'stroage.deployments.total');
}
/**
* Users Stats
* Metric: users.count
*
* @param string $projectId
*
* @return void
*/
private function usersStats(string $projectId): void
{
$this->count($projectId, 'users', 'users.count');
}
/**
* Storage Stats
* Metrics: storage.total, storage.files.total, storage.buckets.{bucketId}.files.total,
* storage.buckets.count, storage.files.count, storage.buckets.{bucketId}.files.count
*
* @param string $projectId
*
* @return void
*/
private function storageStats(string $projectId): void
{
$deploymentsTotal = $this->deploymentsTotal($projectId);
$projectFilesTotal = 0;
$projectFilesCount = 0;
$metric = 'storage.buckets.count';
$this->count($projectId, 'buckets', $metric);
$this->foreachDocument($projectId, 'buckets', [], function ($bucket) use (&$projectFilesCount, &$projectFilesTotal, $projectId,) {
$metric = "storage.buckets.{$bucket->getId()}.files.count";
$count = $this->count($projectId, 'bucket_' . $bucket->getInternalId(), $metric);
$projectFilesCount += $count;
$metric = "storage.buckets.{$bucket->getId()}.files.total";
$sum = $this->sum($projectId, 'bucket_' . $bucket->getInternalId(), 'sizeOriginal', $metric);
$projectFilesTotal += $sum;
});
$this->createOrUpdateMetric($projectId, 'storage.files.count', $projectFilesCount);
$this->createOrUpdateMetric($projectId, 'storage.files.total', $projectFilesTotal);
$this->createOrUpdateMetric($projectId, 'storage.total', $projectFilesTotal + $deploymentsTotal);
}
/**
* Database Stats
* Collect all database stats
* Metrics: database.collections.count, database.collections.{collectionId}.documents.count,
* database.documents.count
*
* @param string $projectId
*
* @return void
*/
private function databaseStats(string $projectId): void
{
$projectDocumentsCount = 0;
$projectCollectionsCount = 0;
$this->count($projectId, 'databases', 'databases.count');
$this->foreachDocument($projectId, 'databases', [], function ($database) use (&$projectDocumentsCount, &$projectCollectionsCount, $projectId) {
$metric = "databases.{$database->getId()}.collections.count";
$count = $this->count($projectId, 'database_' . $database->getInternalId(), $metric);
$projectCollectionsCount += $count;
$databaseDocumentsCount = 0;
$this->foreachDocument($projectId, 'database_' . $database->getInternalId(), [], function ($collection) use (&$projectDocumentsCount, &$databaseDocumentsCount, $projectId, $database) {
$metric = "databases.{$database->getId()}.collections.{$collection->getId()}.documents.count";
$count = $this->count($projectId, 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $metric);
$projectDocumentsCount += $count;
$databaseDocumentsCount += $count;
});
$this->createOrUpdateMetric($projectId, "databases.{$database->getId()}.documents.count", $databaseDocumentsCount);
});
$this->createOrUpdateMetric($projectId, 'databases.collections.count', $projectCollectionsCount);
$this->createOrUpdateMetric($projectId, 'databases.documents.count', $projectDocumentsCount);
}
/**
* Collect Stats
* Collect all database related stats
*
* @return void
*/
public function collect(): void
{
$this->foreachDocument('console', 'projects', [], function (Document $project) {
$projectId = $project->getInternalId();
$this->usersStats($projectId);
$this->databaseStats($projectId);
$this->storageStats($projectId);
});
}
}
+7 -7
View File
@@ -74,7 +74,7 @@ class Template extends View
} elseif (!empty($this->content)) {
$template = $this->print($this->content, self::FILTER_NL2P);
} else {
throw new Exception('"'.$this->path.'" template is not readable or not found');
throw new Exception('"' . $this->path . '" template is not readable or not found');
}
// First replace the variables inside the params. Then replace the variables in the template
@@ -109,20 +109,20 @@ class Template extends View
*/
public static function unParseURL(array $url)
{
$scheme = isset($url['scheme']) ? $url['scheme'].'://' : '';
$scheme = isset($url['scheme']) ? $url['scheme'] . '://' : '';
$host = isset($url['host']) ? $url['host'] : '';
$port = isset($url['port']) ? ':'.$url['port'] : '';
$port = isset($url['port']) ? ':' . $url['port'] : '';
$user = isset($url['user']) ? $url['user'] : '';
$pass = isset($url['pass']) ? ':'.$url['pass'] : '';
$pass = isset($url['pass']) ? ':' . $url['pass'] : '';
$pass = ($user || $pass) ? "$pass@" : '';
$path = isset($url['path']) ? $url['path'] : '';
$query = isset($url['query']) && !empty($url['query']) ? '?'.$url['query'] : '';
$query = isset($url['query']) && !empty($url['query']) ? '?' . $url['query'] : '';
$fragment = isset($url['fragment']) ? '#'.$url['fragment'] : '';
$fragment = isset($url['fragment']) ? '#' . $url['fragment'] : '';
return $scheme.$user.$pass.$host.$port.$path.$query.$fragment;
return $scheme . $user . $pass . $host . $port . $path . $query . $fragment;
}
/**
+8 -8
View File
@@ -42,28 +42,28 @@ class URL
public static function unparse(array $url, array $ommit = []): string
{
if (isset($url['path']) && \mb_substr($url['path'], 0, 1) !== '/') {
$url['path'] = '/'.$url['path'];
$url['path'] = '/' . $url['path'];
}
$parts = [];
$parts['scheme'] = isset($url['scheme']) ? $url['scheme'].'://' : '';
$parts['scheme'] = isset($url['scheme']) ? $url['scheme'] . '://' : '';
$parts['host'] = isset($url['host']) ? $url['host'] : '';
$parts['port'] = isset($url['port']) ? ':'.$url['port'] : '';
$parts['port'] = isset($url['port']) ? ':' . $url['port'] : '';
$parts['user'] = isset($url['user']) ? $url['user'] : '';
$parts['pass'] = isset($url['pass']) ? ':'.$url['pass'] : '';
$parts['pass'] = isset($url['pass']) ? ':' . $url['pass'] : '';
$parts['pass'] = ($parts['user'] || $parts['pass']) ? $parts['pass'].'@' : '';
$parts['pass'] = ($parts['user'] || $parts['pass']) ? $parts['pass'] . '@' : '';
$parts['path'] = isset($url['path']) ? $url['path'] : '';
$parts['query'] = isset($url['query']) && !empty($url['query']) ? '?'.$url['query'] : '';
$parts['query'] = isset($url['query']) && !empty($url['query']) ? '?' . $url['query'] : '';
$parts['fragment'] = isset($url['fragment']) ? '#'.$url['fragment'] : '';
$parts['fragment'] = isset($url['fragment']) ? '#' . $url['fragment'] : '';
if ($ommit) {
foreach ($ommit as $key) {
@@ -73,7 +73,7 @@ class URL
}
}
return $parts['scheme'].$parts['user'].$parts['pass'].$parts['host'].$parts['port'].$parts['path'].$parts['query'].$parts['fragment'];
return $parts['scheme'] . $parts['user'] . $parts['pass'] . $parts['host'] . $parts['port'] . $parts['path'] . $parts['query'] . $parts['fragment'];
}
/**
@@ -1,9 +1,11 @@
<?php
namespace Appwrite\Utopia\Database\Validator;
use Utopia\Database\Validator\Key;
class CustomId extends Key {
class CustomId extends Key
{
/**
* Is valid.
*
@@ -18,4 +20,4 @@ class CustomId extends Key {
return $value == 'unique()' || parent::isValid($value);
}
}
}
@@ -0,0 +1,26 @@
<?php
namespace Appwrite\Utopia\Database\Validator;
use Utopia\Database\Document;
use Utopia\Database\Validator\OrderAttributes as ValidatorOrderAttributes;
class OrderAttributes extends ValidatorOrderAttributes
{
/**
* Expression constructor
*
* @param Document[] $attributes
* @param Document[] $indexes
* @param bool $strict
*/
public function __construct($attributes, $indexes, $strict)
{
// Remove failed/stuck/processing indexes
$indexes = \array_filter($indexes, function ($index) {
return $index->getAttribute('status') === 'available';
});
parent::__construct($attributes, $indexes, $strict);
}
}
@@ -0,0 +1,26 @@
<?php
namespace Appwrite\Utopia\Database\Validator;
use Utopia\Database\Document;
use Utopia\Database\Validator\Queries as ValidatorQueries;
class Queries extends ValidatorQueries
{
/**
* Expression constructor
*
* @param Document[] $attributes
* @param Document[] $indexes
* @param bool $strict
*/
public function __construct($attributes, $indexes, $strict)
{
// Remove failed/stuck/processing indexes
$indexes = \array_filter($indexes, function ($index) {
return $index->getAttribute('status') === 'available';
});
parent::__construct($attributes, $indexes, $strict);
}
}
+2 -3
View File
@@ -1,6 +1,5 @@
<?php
namespace Appwrite\Utopia;
use Appwrite\Utopia\Request\Filter;
@@ -39,7 +38,7 @@ class Request extends UtopiaRequest
{
$requestParameters = [];
switch($this->getMethod()) {
switch ($this->getMethod()) {
case self::METHOD_GET:
$requestParameters = (!empty($this->swoole->get)) ? $this->swoole->get : [];
break;
@@ -125,4 +124,4 @@ class Request extends UtopiaRequest
{
return self::$route != null;
}
}
}
-1
View File
@@ -4,7 +4,6 @@ namespace Appwrite\Utopia\Request;
abstract class Filter
{
/**
* Parse params to another format.
*
+12 -6
View File
@@ -93,9 +93,15 @@ class V12 extends Filter
protected function removeParentProperties(array $content): array
{
if (isset($content['parentDocument'])) unset($content['parentDocument']);
if (isset($content['parentProperty'])) unset($content['parentProperty']);
if (isset($content['parentPropertyType'])) unset($content['parentPropertyType']);
if (isset($content['parentDocument'])) {
unset($content['parentDocument']);
}
if (isset($content['parentProperty'])) {
unset($content['parentProperty']);
}
if (isset($content['parentPropertyType'])) {
unset($content['parentPropertyType']);
}
return $content;
}
@@ -128,7 +134,7 @@ class V12 extends Filter
{
$queries = [];
if(!empty($content['filters'])) {
if (!empty($content['filters'])) {
foreach ($content['filters'] as $filter) {
$operators = ['=' => 'equal', '!=' => 'notEqual', '>' => 'greater', '<' => 'lesser', '<=' => 'lesserEqual', '>=' => 'greaterEqual'];
foreach ($operators as $operator => $operatorVerbose) {
@@ -138,10 +144,10 @@ class V12 extends Filter
}
}
if(isset($usedOperator)) {
if (isset($usedOperator)) {
[ $attributeKey, $filterValue ] = \explode($usedOperator, $filter);
if($filterValue === 'true' || $filterValue === 'false') {
if ($filterValue === 'true' || $filterValue === 'false') {
// Let's keep it at true and false string, but without "" around
// No action needed
} else {
@@ -0,0 +1,34 @@
<?php
namespace Appwrite\Utopia\Request\Filters;
use Appwrite\Utopia\Request\Filter;
use Appwrite\Migration\Version\V13 as MigrationV13;
class V14 extends Filter
{
// Convert 0.13 params format to 0.14 format
public function parse(array $content, string $model): array
{
switch ($model) {
case "functions.create":
case "functions.update":
case "projects.createWebhook":
case "projects.updateWebhook":
$content = $this->convertEvents($content);
break;
}
return $content;
}
private function convertEvents($content)
{
$migration = new MigrationV13();
$events = $content['events'] ?? [];
$content['events'] = $migration->migrateEvents($events);
return $content;
}
}
+107 -100
View File
@@ -22,11 +22,11 @@ use Appwrite\Utopia\Response\Model\AttributeIP;
use Appwrite\Utopia\Response\Model\AttributeURL;
use Appwrite\Utopia\Response\Model\BaseList;
use Appwrite\Utopia\Response\Model\Collection;
use Appwrite\Utopia\Response\Model\Database;
use Appwrite\Utopia\Response\Model\Continent;
use Appwrite\Utopia\Response\Model\Country;
use Appwrite\Utopia\Response\Model\Currency;
use Appwrite\Utopia\Response\Model\Document as ModelDocument;
use Appwrite\Utopia\Response\Model\DocumentList;
use Appwrite\Utopia\Response\Model\Domain;
use Appwrite\Utopia\Response\Model\Error;
use Appwrite\Utopia\Response\Model\ErrorDev;
@@ -65,6 +65,7 @@ use Appwrite\Utopia\Response\Model\Runtime;
use Appwrite\Utopia\Response\Model\UsageBuckets;
use Appwrite\Utopia\Response\Model\UsageCollection;
use Appwrite\Utopia\Response\Model\UsageDatabase;
use Appwrite\Utopia\Response\Model\UsageDatabases;
use Appwrite\Utopia\Response\Model\UsageFunctions;
use Appwrite\Utopia\Response\Model\UsageProject;
use Appwrite\Utopia\Response\Model\UsageStorage;
@@ -76,116 +77,119 @@ use Appwrite\Utopia\Response\Model\UsageUsers;
class Response extends SwooleResponse
{
// General
const MODEL_NONE = 'none';
const MODEL_ANY = 'any';
const MODEL_LOG = 'log';
const MODEL_LOG_LIST = 'logList';
const MODEL_ERROR = 'error';
const MODEL_METRIC = 'metric';
const MODEL_METRIC_LIST = 'metricList';
const MODEL_ERROR_DEV = 'errorDev';
const MODEL_BASE_LIST = 'baseList';
const MODEL_USAGE_DATABASE = 'usageDatabase';
const MODEL_USAGE_COLLECTION = 'usageCollection';
const MODEL_USAGE_USERS = 'usageUsers';
const MODEL_USAGE_BUCKETS = 'usageBuckets';
const MODEL_USAGE_STORAGE = 'usageStorage';
const MODEL_USAGE_FUNCTIONS = 'usageFunctions';
const MODEL_USAGE_PROJECT = 'usageProject';
public const MODEL_NONE = 'none';
public const MODEL_ANY = 'any';
public const MODEL_LOG = 'log';
public const MODEL_LOG_LIST = 'logList';
public const MODEL_ERROR = 'error';
public const MODEL_METRIC = 'metric';
public const MODEL_METRIC_LIST = 'metricList';
public const MODEL_ERROR_DEV = 'errorDev';
public const MODEL_BASE_LIST = 'baseList';
public const MODEL_USAGE_DATABASES = 'usageDatabases';
public const MODEL_USAGE_DATABASE = 'usageDatabase';
public const MODEL_USAGE_COLLECTION = 'usageCollection';
public const MODEL_USAGE_USERS = 'usageUsers';
public const MODEL_USAGE_BUCKETS = 'usageBuckets';
public const MODEL_USAGE_STORAGE = 'usageStorage';
public const MODEL_USAGE_FUNCTIONS = 'usageFunctions';
public const MODEL_USAGE_PROJECT = 'usageProject';
// Database
const MODEL_COLLECTION = 'collection';
const MODEL_COLLECTION_LIST = 'collectionList';
const MODEL_INDEX = 'index';
const MODEL_INDEX_LIST = 'indexList';
const MODEL_DOCUMENT = 'document';
const MODEL_DOCUMENT_LIST = 'documentList';
public const MODEL_DATABASE = 'database';
public const MODEL_DATABASE_LIST = 'databaseList';
public const MODEL_COLLECTION = 'collection';
public const MODEL_COLLECTION_LIST = 'collectionList';
public const MODEL_INDEX = 'index';
public const MODEL_INDEX_LIST = 'indexList';
public const MODEL_DOCUMENT = 'document';
public const MODEL_DOCUMENT_LIST = 'documentList';
// Database Attributes
const MODEL_ATTRIBUTE = 'attribute';
const MODEL_ATTRIBUTE_LIST = 'attributeList';
const MODEL_ATTRIBUTE_STRING = 'attributeString';
const MODEL_ATTRIBUTE_INTEGER = 'attributeInteger';
const MODEL_ATTRIBUTE_FLOAT = 'attributeFloat';
const MODEL_ATTRIBUTE_BOOLEAN = 'attributeBoolean';
const MODEL_ATTRIBUTE_EMAIL = 'attributeEmail';
const MODEL_ATTRIBUTE_ENUM = 'attributeEnum';
const MODEL_ATTRIBUTE_IP = 'attributeIp';
const MODEL_ATTRIBUTE_URL= 'attributeUrl';
public const MODEL_ATTRIBUTE = 'attribute';
public const MODEL_ATTRIBUTE_LIST = 'attributeList';
public const MODEL_ATTRIBUTE_STRING = 'attributeString';
public const MODEL_ATTRIBUTE_INTEGER = 'attributeInteger';
public const MODEL_ATTRIBUTE_FLOAT = 'attributeFloat';
public const MODEL_ATTRIBUTE_BOOLEAN = 'attributeBoolean';
public const MODEL_ATTRIBUTE_EMAIL = 'attributeEmail';
public const MODEL_ATTRIBUTE_ENUM = 'attributeEnum';
public const MODEL_ATTRIBUTE_IP = 'attributeIp';
public const MODEL_ATTRIBUTE_URL = 'attributeUrl';
// Users
const MODEL_USER = 'user';
const MODEL_USER_LIST = 'userList';
const MODEL_SESSION = 'session';
const MODEL_SESSION_LIST = 'sessionList';
const MODEL_TOKEN = 'token';
const MODEL_JWT = 'jwt';
const MODEL_PREFERENCES = 'preferences';
public const MODEL_USER = 'user';
public const MODEL_USER_LIST = 'userList';
public const MODEL_SESSION = 'session';
public const MODEL_SESSION_LIST = 'sessionList';
public const MODEL_TOKEN = 'token';
public const MODEL_JWT = 'jwt';
public const MODEL_PREFERENCES = 'preferences';
// Storage
const MODEL_FILE = 'file';
const MODEL_FILE_LIST = 'fileList';
const MODEL_BUCKET = 'bucket';
const MODEL_BUCKET_LIST = 'bucketList';
public const MODEL_FILE = 'file';
public const MODEL_FILE_LIST = 'fileList';
public const MODEL_BUCKET = 'bucket';
public const MODEL_BUCKET_LIST = 'bucketList';
// Locale
const MODEL_LOCALE = 'locale';
const MODEL_COUNTRY = 'country';
const MODEL_COUNTRY_LIST = 'countryList';
const MODEL_CONTINENT = 'continent';
const MODEL_CONTINENT_LIST = 'continentList';
const MODEL_CURRENCY = 'currency';
const MODEL_CURRENCY_LIST = 'currencyList';
const MODEL_LANGUAGE = 'language';
const MODEL_LANGUAGE_LIST = 'languageList';
const MODEL_PHONE = 'phone';
const MODEL_PHONE_LIST = 'phoneList';
public const MODEL_LOCALE = 'locale';
public const MODEL_COUNTRY = 'country';
public const MODEL_COUNTRY_LIST = 'countryList';
public const MODEL_CONTINENT = 'continent';
public const MODEL_CONTINENT_LIST = 'continentList';
public const MODEL_CURRENCY = 'currency';
public const MODEL_CURRENCY_LIST = 'currencyList';
public const MODEL_LANGUAGE = 'language';
public const MODEL_LANGUAGE_LIST = 'languageList';
public const MODEL_PHONE = 'phone';
public const MODEL_PHONE_LIST = 'phoneList';
// Teams
const MODEL_TEAM = 'team';
const MODEL_TEAM_LIST = 'teamList';
const MODEL_MEMBERSHIP = 'membership';
const MODEL_MEMBERSHIP_LIST = 'membershipList';
public const MODEL_TEAM = 'team';
public const MODEL_TEAM_LIST = 'teamList';
public const MODEL_MEMBERSHIP = 'membership';
public const MODEL_MEMBERSHIP_LIST = 'membershipList';
// Functions
const MODEL_FUNCTION = 'function';
const MODEL_FUNCTION_LIST = 'functionList';
const MODEL_RUNTIME = 'runtime';
const MODEL_RUNTIME_LIST = 'runtimeList';
const MODEL_DEPLOYMENT = 'deployment';
const MODEL_DEPLOYMENT_LIST = 'deploymentList';
const MODEL_EXECUTION = 'execution';
const MODEL_EXECUTION_LIST = 'executionList';
const MODEL_BUILD = 'build';
const MODEL_BUILD_LIST = 'buildList'; // Not used anywhere yet
const MODEL_FUNC_PERMISSIONS = 'funcPermissions';
public const MODEL_FUNCTION = 'function';
public const MODEL_FUNCTION_LIST = 'functionList';
public const MODEL_RUNTIME = 'runtime';
public const MODEL_RUNTIME_LIST = 'runtimeList';
public const MODEL_DEPLOYMENT = 'deployment';
public const MODEL_DEPLOYMENT_LIST = 'deploymentList';
public const MODEL_EXECUTION = 'execution';
public const MODEL_EXECUTION_LIST = 'executionList';
public const MODEL_BUILD = 'build';
public const MODEL_BUILD_LIST = 'buildList'; // Not used anywhere yet
public const MODEL_FUNC_PERMISSIONS = 'funcPermissions';
// Project
const MODEL_PROJECT = 'project';
const MODEL_PROJECT_LIST = 'projectList';
const MODEL_WEBHOOK = 'webhook';
const MODEL_WEBHOOK_LIST = 'webhookList';
const MODEL_KEY = 'key';
const MODEL_KEY_LIST = 'keyList';
const MODEL_PLATFORM = 'platform';
const MODEL_PLATFORM_LIST = 'platformList';
const MODEL_DOMAIN = 'domain';
const MODEL_DOMAIN_LIST = 'domainList';
public const MODEL_PROJECT = 'project';
public const MODEL_PROJECT_LIST = 'projectList';
public const MODEL_WEBHOOK = 'webhook';
public const MODEL_WEBHOOK_LIST = 'webhookList';
public const MODEL_KEY = 'key';
public const MODEL_KEY_LIST = 'keyList';
public const MODEL_PLATFORM = 'platform';
public const MODEL_PLATFORM_LIST = 'platformList';
public const MODEL_DOMAIN = 'domain';
public const MODEL_DOMAIN_LIST = 'domainList';
// Health
const MODEL_HEALTH_STATUS = 'healthStatus';
const MODEL_HEALTH_VERSION = 'healthVersion';
const MODEL_HEALTH_QUEUE = 'healthQueue';
const MODEL_HEALTH_TIME = 'healthTime';
const MODEL_HEALTH_ANTIVIRUS = 'healthAntivirus';
public const MODEL_HEALTH_STATUS = 'healthStatus';
public const MODEL_HEALTH_VERSION = 'healthVersion';
public const MODEL_HEALTH_QUEUE = 'healthQueue';
public const MODEL_HEALTH_TIME = 'healthTime';
public const MODEL_HEALTH_ANTIVIRUS = 'healthAntivirus';
// Deprecated
const MODEL_PERMISSIONS = 'permissions';
const MODEL_RULE = 'rule';
const MODEL_TASK = 'task';
public const MODEL_PERMISSIONS = 'permissions';
public const MODEL_RULE = 'rule';
public const MODEL_TASK = 'task';
// Tests (keep last)
const MODEL_MOCK = 'mock';
public const MODEL_MOCK = 'mock';
/**
* @var Filter
@@ -213,6 +217,7 @@ class Response extends SwooleResponse
// Lists
->setModel(new BaseList('Documents List', self::MODEL_DOCUMENT_LIST, 'documents', self::MODEL_DOCUMENT))
->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION))
->setModel(new BaseList('Databases List', self::MODEL_DATABASE_LIST, 'databases', self::MODEL_DATABASE))
->setModel(new BaseList('Indexes List', self::MODEL_INDEX_LIST, 'indexes', self::MODEL_INDEX))
->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER))
->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION))
@@ -238,6 +243,7 @@ class Response extends SwooleResponse
->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE))
->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false))
// Entities
->setModel(new Database())
->setModel(new Collection())
->setModel(new Attribute())
->setModel(new AttributeList())
@@ -283,6 +289,7 @@ class Response extends SwooleResponse
->setModel(new HealthTime())
->setModel(new HealthVersion())
->setModel(new Metric())
->setModel(new UsageDatabases())
->setModel(new UsageDatabase())
->setModel(new UsageCollection())
->setModel(new UsageUsers())
@@ -302,7 +309,7 @@ class Response extends SwooleResponse
/**
* HTTP content types
*/
const CONTENT_TYPE_YAML = 'application/x-yaml';
public const CONTENT_TYPE_YAML = 'application/x-yaml';
/**
* List of defined output objects
@@ -329,7 +336,7 @@ class Response extends SwooleResponse
public function getModel(string $key): Model
{
if (!isset($this->models[$key])) {
throw new Exception('Undefined model: '.$key);
throw new Exception('Undefined model: ' . $key);
}
return $this->models[$key];
@@ -392,13 +399,13 @@ class Response extends SwooleResponse
if (!is_null($rule['default'])) {
$document->setAttribute($key, $rule['default']);
} else {
throw new Exception('Model '.$model->getName().' is missing response key: '.$key);
throw new Exception('Model ' . $model->getName() . ' is missing response key: ' . $key);
}
}
if ($rule['array']) {
if (!is_array($data[$key])) {
throw new Exception($key.' must be an array of type '.$rule['type']);
throw new Exception($key . ' must be an array of type ' . $rule['type']);
}
foreach ($data[$key] as &$item) {
@@ -408,7 +415,7 @@ class Response extends SwooleResponse
$condition = false;
foreach ($this->getModel($type)->conditions as $attribute => $val) {
$condition = $item->getAttribute($attribute) === $val;
if(!$condition) {
if (!$condition) {
break;
}
}
@@ -422,7 +429,7 @@ class Response extends SwooleResponse
}
if (!array_key_exists($ruleType, $this->models)) {
throw new Exception('Missing model for rule: '. $ruleType);
throw new Exception('Missing model for rule: ' . $ruleType);
}
$item = $this->output($item, $ruleType);
@@ -465,7 +472,7 @@ class Response extends SwooleResponse
/**
* @return array
*/
public function getPayload():array
public function getPayload(): array
{
return $this->payload;
}
-1
View File
@@ -4,7 +4,6 @@ namespace Appwrite\Utopia\Response;
abstract class Filter
{
/**
* Parse the content to another format.
*
+20 -21
View File
@@ -4,7 +4,6 @@ namespace Appwrite\Utopia\Response\Filters;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Filter;
use Exception;
class V11 extends Filter
{
@@ -28,21 +27,21 @@ class V11 extends Filter
case Response::MODEL_FILE_LIST:
$parsedResponse = $this->parseFileList($content);
break;
case Response::MODEL_EXECUTION:
$parsedResponse = $this->parseExecutionPermissions($content);
break;
case Response::MODEL_EXECUTION_LIST:
$parsedResponse = $this->parseExecutionsList($content);
break;
case Response::MODEL_FUNCTION:
$parsedResponse = $this->parseFunctionPermissions($content);
break;
case Response::MODEL_FUNCTION_LIST:
$parsedResponse = $this->parseFunctionsList($content);
break;
// Convert status from boolean to int
case Response::MODEL_USER:
$parsedResponse = $this->parseStatus($content);
@@ -50,7 +49,7 @@ class V11 extends Filter
case Response::MODEL_USER_LIST:
$parsedResponse = $this->parseUserList($content);
break;
// Convert all Health responses back to original
case Response::MODEL_HEALTH_STATUS:
$parsedResponse = $this->parseHealthStatus($content);
@@ -94,7 +93,7 @@ class V11 extends Filter
return $parsedResponse;
}
protected function parseDocumentList(array $content)
protected function parseDocumentList(array $content)
{
$documents = $content['documents'];
$parsedResponse = [];
@@ -105,7 +104,7 @@ class V11 extends Filter
return $content;
}
protected function parseFileList(array $content)
protected function parseFileList(array $content)
{
$files = $content['files'];
$parsedResponse = [];
@@ -116,7 +115,7 @@ class V11 extends Filter
return $content;
}
protected function parseExecutionsList(array $content)
protected function parseExecutionsList(array $content)
{
$executions = $content['executions'];
$parsedResponse = [];
@@ -127,7 +126,7 @@ class V11 extends Filter
return $content;
}
protected function parseFunctionsList(array $content)
protected function parseFunctionsList(array $content)
{
$functions = $content['functions'];
$parsedResponse = [];
@@ -138,7 +137,7 @@ class V11 extends Filter
return $content;
}
protected function parseUserList(array $content)
protected function parseUserList(array $content)
{
$users = $content['users'];
$parsedResponse = [];
@@ -149,7 +148,7 @@ class V11 extends Filter
return $content;
}
protected function parseCollection(array $content)
protected function parseCollection(array $content)
{
$parsedResponse = [];
$parsedResponse = $this->parsePermissions($content);
@@ -163,7 +162,7 @@ class V11 extends Filter
return $parsedResponse;
}
protected function parseCollectionList(array $content)
protected function parseCollectionList(array $content)
{
$collections = $content['collections'];
$parsedResponse = [];
@@ -174,7 +173,7 @@ class V11 extends Filter
return $content;
}
protected function parseLog(array $content)
protected function parseLog(array $content)
{
$parsedResponse = [];
$parsedResponse = $this->removeRule($content, 'userId');
@@ -185,7 +184,7 @@ class V11 extends Filter
return $parsedResponse;
}
protected function parseLogList(array $content)
protected function parseLogList(array $content)
{
$logs = $content['logs'];
$parsedResponse = [];
@@ -207,7 +206,7 @@ class V11 extends Filter
return $parsedResponse;
}
protected function parseProjectList(array $content)
protected function parseProjectList(array $content)
{
$projects = $content['projects'];
$parsedResponse = [];
@@ -220,11 +219,11 @@ class V11 extends Filter
protected function parseHealthAntivirus(array $content)
{
if($content['status'] === 'pass') {
if ($content['status'] === 'pass') {
$content['status'] = 'online';
}
if($content['status'] === 'fail') {
if ($content['status'] === 'fail') {
$content['status'] = 'offline';
}
@@ -276,7 +275,7 @@ class V11 extends Filter
protected function parseAttributes(array $content)
{
$content['rules'] = \array_map(function($attribute) use($content) {
$content['rules'] = \array_map(function ($attribute) use ($content) {
return [
'$id' => $attribute['key'],
'$collection' => $content['$id'],
@@ -308,7 +307,7 @@ class V11 extends Filter
foreach ($content as $key => $value) {
\preg_match_all($regexPattern, $key, $regexGroups);
if(\count($regexGroups[1]) > 0 && \count($regexGroups[2]) > 0) {
if (\count($regexGroups[1]) > 0 && \count($regexGroups[2]) > 0) {
$providerName = $regexGroups[1][0];
$valueKey = $regexGroups[2][0];
$content['usersOauth2' . $providerName . $valueKey] = $value;
@@ -325,7 +324,7 @@ class V11 extends Filter
foreach ($content as $key => $value) {
\preg_match_all($regexPattern, $key, $regexGroups);
if(\count($regexGroups[1]) > 0) {
if (\count($regexGroups[1]) > 0) {
$providerName = $regexGroups[1][0];
$content[$providerName] = $value;
@@ -341,7 +340,7 @@ class V11 extends Filter
// Such a key is part of new response, but is not part of old one. We simply remove it, older version never
// expected it anyway.
foreach ($content as $key => $value) {
if(\str_starts_with($key, 'serviceStatusFor')) {
if (\str_starts_with($key, 'serviceStatusFor')) {
unset($content[$key]);
}
}
+11 -7
View File
@@ -4,7 +4,6 @@ namespace Appwrite\Utopia\Response\Filters;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Filter;
use Exception;
class V12 extends Filter
{
@@ -220,14 +219,16 @@ class V12 extends Filter
return $content;
}
protected function parseExecution($content) {
protected function parseExecution($content)
{
$content['exitCode'] = $content['statusCode'];
unset($content['statusCode']);
return $content;
}
protected function parseExecutionList($content) {
protected function parseExecutionList($content)
{
$executions = $content['executions'];
$parsedResponse = [];
foreach ($executions as $document) {
@@ -239,13 +240,15 @@ class V12 extends Filter
return $content;
}
protected function parseTeam($content) {
protected function parseTeam($content)
{
$content['sum'] = $content['total'];
unset($content['total']);
return $content;
}
protected function parseTeamList($content) {
protected function parseTeamList($content)
{
$teams = $content['teams'];
$parsedResponse = [];
foreach ($teams as $document) {
@@ -257,9 +260,10 @@ class V12 extends Filter
return $content;
}
protected function parseList($content) {
protected function parseList($content)
{
$content['sum'] = $content['total'];
unset($content['total']);
return $content;
}
}
}
@@ -0,0 +1,103 @@
<?php
namespace Appwrite\Utopia\Response\Filters;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Filter;
class V13 extends Filter
{
// Convert 0.14 Data format to 0.13 format
public function parse(array $content, string $model): array
{
$parsedResponse = $content;
switch ($model) {
case Response::MODEL_PROJECT:
$parsedResponse = $this->parseProject($content);
break;
case Response::MODEL_PROJECT_LIST:
$parsedResponse = $this->parseProjectList($content);
break;
case Response::MODEL_MEMBERSHIP:
$parsedResponse = $this->parseMembership($content);
break;
case Response::MODEL_MEMBERSHIP_LIST:
$parsedResponse = $this->parseMembershipList($content);
break;
case Response::MODEL_EXECUTION:
$parsedResponse = $this->parseExecution($content);
break;
case Response::MODEL_EXECUTION_LIST:
$parsedResponse = $this->parseExecutionList($content);
break;
}
return $parsedResponse;
}
protected function parseExecution($content)
{
$content['stdout'] = $content['response'];
unset($content['response']);
return $content;
}
protected function parseExecutionList($content)
{
$executions = $content['executions'];
$parsedResponse = [];
foreach ($executions as $document) {
$parsedResponse[] = $this->parseExecution($document);
}
$content['executions'] = $parsedResponse;
return $content;
}
protected function parseProject($content)
{
$content['providers'] = $content['authProviders'];
unset($content['authProviders']);
return $content;
}
protected function parseProjectList($content)
{
$projects = $content['projects'];
$parsedResponse = [];
foreach ($projects as $document) {
$parsedResponse[] = $this->parseProject($document);
}
$content['projects'] = $parsedResponse;
return $content;
}
protected function parseMembership($content)
{
$content['name'] = $content['userName'];
unset($content['userName']);
$content['email'] = $content['userEmail'];
unset($content['userEmail']);
unset($content['teamName']);
return $content;
}
protected function parseMembershipList($content)
{
$memberships = $content['memberships'];
$parsedResponse = [];
foreach ($memberships as $document) {
$parsedResponse[] = $this->parseMembership($document);
}
$content['memberships'] = $parsedResponse;
return $content;
}
}
@@ -0,0 +1,176 @@
<?php
namespace Appwrite\Utopia\Response\Filters;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Filter;
class V14 extends Filter
{
// Convert 0.15 Data format to 0.14 format
public function parse(array $content, string $model): array
{
$parsedResponse = $content;
switch ($model) {
case Response::MODEL_SESSION:
case Response::MODEL_TOKEN:
$parsedResponse = $this->parseRemoveAttributes($content, ['$createdAt']);
break;
case Response::MODEL_SESSION_LIST:
$parsedResponse = $this->parseRemoveAttributesList($content, 'domains', ['$createdAt']);
break;
case Response::MODEL_DOCUMENT:
case Response::MODEL_DOMAIN:
case Response::MODEL_FUNCTION:
case Response::MODEL_TEAM:
case Response::MODEL_MEMBERSHIP:
case Response::MODEL_PLATFORM:
case Response::MODEL_PROJECT:
case Response::MODEL_USER:
case Response::MODEL_WEBHOOK:
$parsedResponse = $this->parseRemoveAttributes($content, ['$createdAt', '$updatedAt']);
break;
case Response::MODEL_DOCUMENT_LIST:
$parsedResponse = $this->parseRemoveAttributesList($content, 'documents', ['$createdAt', '$updatedAt']);
break;
case Response::MODEL_DOMAIN_LIST:
$parsedResponse = $this->parseRemoveAttributesList($content, 'domains', ['$createdAt', '$updatedAt']);
break;
case Response::MODEL_FUNCTION_LIST:
$parsedResponse = $this->parseRemoveAttributesList($content, 'functions', ['$createdAt', '$updatedAt']);
break;
case Response::MODEL_TEAM_LIST:
$parsedResponse = $this->parseRemoveAttributesList($content, 'teams', ['$createdAt', '$updatedAt']);
break;
case Response::MODEL_MEMBERSHIP_LIST:
$parsedResponse = $this->parseRemoveAttributesList($content, 'memberships', ['$createdAt', '$updatedAt']);
break;
case Response::MODEL_PLATFORM_LIST:
$parsedResponse = $this->parseRemoveAttributesList($content, 'platforms', ['$createdAt', '$updatedAt']);
break;
case Response::MODEL_PROJECT_LIST:
$parsedResponse = $this->parseRemoveAttributesList($content, 'projects', ['$createdAt', '$updatedAt']);
break;
case Response::MODEL_USER_LIST:
$parsedResponse = $this->parseRemoveAttributesList($content, 'users', ['$createdAt', '$updatedAt']);
break;
case Response::MODEL_WEBHOOK_LIST:
$parsedResponse = $this->parseRemoveAttributesList($content, 'webhooks', ['$createdAt', '$updatedAt']);
break;
case Response::MODEL_TEAM:
case Response::MODEL_EXECUTION:
case Response::MODEL_FILE:
$parsedResponse = $this->parseCreatedAt($content);
break;
case Response::MODEL_TEAM_LIST:
$parsedResponse = $this->parseCreatedAtList($content, 'teams');
break;
case Response::MODEL_EXECUTION_LIST:
$parsedResponse = $this->parseCreatedAtList($content, 'executions');
break;
case Response::MODEL_FILE_LIST:
$parsedResponse = $this->parseCreatedAtList($content, 'files');
break;
case Response::MODEL_FUNCTION:
case Response::MODEL_DEPLOYMENT:
case Response::MODEL_BUCKET:
$parsedResponse = $this->parseCreatedAtAndUpdatedAt($content);
break;
case Response::MODEL_FUNCTION_LIST:
$parsedResponse = $this->parseCreatedAtAndUpdatedAtList($content, 'functions');
break;
case Response::MODEL_DEPLOYMENT_LIST:
$parsedResponse = $this->parseCreatedAtAndUpdatedAtList($content, 'deployments');
break;
case Response::MODEL_BUCKET_LIST:
$parsedResponse = $this->parseCreatedAtAndUpdatedAtList($content, 'buckets');
break;
}
return $parsedResponse;
}
protected function parseRemoveAttributes(array $content, array $attributes)
{
foreach ($attributes as $attribute) {
unset($content[$attribute]);
}
return $content;
}
protected function parseRemoveAttributesList(array $content, string $property, array $attributes)
{
$documents = $content[$property];
$parsedResponse = [];
foreach ($documents as $document) {
$parsedResponse[] = $this->parseRemoveAttributes($document, $attributes);
}
$content[$property] = $parsedResponse;
return $content;
}
protected function parseCreatedAt(array $content)
{
$content['dateCreated'] = $content['$createdAt'];
unset($content['$createdAt']);
unset($content['$updatedAt']);
return $content;
}
protected function parseCreatedAtList(array $content, string $property)
{
$documents = $content[$property];
$parsedResponse = [];
foreach ($documents as $document) {
$parsedResponse[] = $this->parseCreatedAt($document);
}
$content[$property] = $parsedResponse;
return $content;
}
protected function parseCreatedAtAndUpdatedAt(array $content)
{
$content['dateCreated'] = $content['$createdAt'];
$content['dateUpdated'] = $content['$updatedAt'];
unset($content['$createdAt']);
unset($content['$updatedAt']);
return $content;
}
protected function parseCreatedAtAndUpdatedAtList(array $content, string $property)
{
$documents = $content[$property];
$parsedResponse = [];
foreach ($documents as $document) {
$parsedResponse[] = $this->parseCreatedAtAndUpdatedAt($document);
}
$content[$property] = $parsedResponse;
return $content;
}
}
+6 -6
View File
@@ -6,11 +6,11 @@ use Utopia\Database\Document;
abstract class Model
{
const TYPE_STRING = 'string';
const TYPE_INTEGER = 'integer';
const TYPE_FLOAT = 'double';
const TYPE_BOOLEAN = 'boolean';
const TYPE_JSON = 'json';
public const TYPE_STRING = 'string';
public const TYPE_INTEGER = 'integer';
public const TYPE_FLOAT = 'double';
public const TYPE_BOOLEAN = 'boolean';
public const TYPE_JSON = 'json';
/**
* @var bool
@@ -34,7 +34,7 @@ abstract class Model
/**
* Filter Document Structure
*
*
* @return Document
*/
public function filter(Document $document): Document
+2 -2
View File
@@ -17,7 +17,7 @@ class Any extends Model
*
* @return string
*/
public function getName():string
public function getName(): string
{
return 'Any';
}
@@ -27,7 +27,7 @@ class Any extends Model
*
* @return string
*/
public function getType():string
public function getType(): string
{
return Response::MODEL_ANY;
}
@@ -48,20 +48,20 @@ class Attribute extends Model
/**
* Get Name
*
*
* @return string
*/
public function getName():string
public function getName(): string
{
return 'Attribute';
}
/**
* Get Collection
*
*
* @return string
*/
public function getType():string
public function getType(): string
{
return Response::MODEL_ATTRIBUTE;
}
@@ -12,6 +12,18 @@ class AttributeBoolean extends Attribute
parent::__construct();
$this
->addRule('key', [
'type' => self::TYPE_STRING,
'description' => 'Attribute Key.',
'default' => '',
'example' => 'isEnabled',
])
->addRule('type', [
'type' => self::TYPE_STRING,
'description' => 'Attribute type.',
'default' => '',
'example' => 'boolean',
])
->addRule('default', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
@@ -29,21 +41,21 @@ class AttributeBoolean extends Attribute
/**
* Get Name
*
*
* @return string
*/
public function getName():string
public function getName(): string
{
return 'AttributeBoolean';
}
/**
* Get Type
*
*
* @return string
*/
public function getType():string
public function getType(): string
{
return Response::MODEL_ATTRIBUTE_BOOLEAN;
}
}
}
@@ -12,6 +12,18 @@ class AttributeEmail extends Attribute
parent::__construct();
$this
->addRule('key', [
'type' => self::TYPE_STRING,
'description' => 'Attribute Key.',
'default' => '',
'example' => 'userEmail',
])
->addRule('type', [
'type' => self::TYPE_STRING,
'description' => 'Attribute type.',
'default' => '',
'example' => 'string',
])
->addRule('format', [
'type' => self::TYPE_STRING,
'description' => 'String format.',
@@ -38,21 +50,21 @@ class AttributeEmail extends Attribute
/**
* Get Name
*
*
* @return string
*/
public function getName():string
public function getName(): string
{
return 'AttributeEmail';
}
/**
* Get Type
*
*
* @return string
*/
public function getType():string
public function getType(): string
{
return Response::MODEL_ATTRIBUTE_EMAIL;
}
}
}

Some files were not shown because too many files have changed in this diff Show More