diff --git a/app/cli.php b/app/cli.php
index d2c6496dc3..8902aad886 100644
--- a/app/cli.php
+++ b/app/cli.php
@@ -5,6 +5,9 @@ require_once __DIR__.'/controllers/general.php';
use Utopia\App;
use Utopia\CLI\CLI;
use Utopia\CLI\Console;
+use Utopia\Database\Validator\Authorization;
+
+Authorization::disable();
$cli = new CLI();
diff --git a/app/config/collections.php b/app/config/collections.php
index 35aa37d352..487f893423 100644
--- a/app/config/collections.php
+++ b/app/config/collections.php
@@ -1054,9 +1054,9 @@ $collections = [
'size' => 16384,
'signed' => true,
'required' => false,
- 'default' => [],
- 'array' => true,
- 'filters' => ['json'],
+ 'default' => null,
+ 'array' => false,
+ 'filters' => ['subQuerySessions'],
],
[
'$id' => 'tokens',
@@ -1478,6 +1478,13 @@ $collections = [
'lengths' => [100, 100],
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
],
+ [
+ '$id' => '_key_user',
+ 'type' => Database::INDEX_KEY,
+ 'attributes' => ['userId'],
+ 'lengths' => [Database::LENGTH_KEY],
+ 'orders' => [Database::ORDER_ASC],
+ ],
],
],
diff --git a/app/config/errors.php b/app/config/errors.php
index b7b5c1ca31..8dadcf079c 100644
--- a/app/config/errors.php
+++ b/app/config/errors.php
@@ -78,6 +78,11 @@ return [
'description' => 'An internal server error occurred.',
'code' => 500,
],
+ Exception::GENERAL_PROTOCOL_UNSUPPORTED => [
+ 'name' => Exception::GENERAL_PROTOCOL_UNSUPPORTED,
+ 'description' => 'The request cannot be fulfilled with the current protocol. Please check the value of the _APP_OPTIONS_FORCE_HTTPS environment variable.',
+ 'code' => 500,
+ ],
/** User Errors */
Exception::USER_COUNT_EXCEEDED => [
diff --git a/app/config/locale/translations/en.json b/app/config/locale/translations/en.json
index c00740b130..ab765fe47e 100644
--- a/app/config/locale/translations/en.json
+++ b/app/config/locale/translations/en.json
@@ -27,6 +27,12 @@
"emails.invitation.footer": "If you are not interested, you can ignore this message.",
"emails.invitation.thanks": "Thanks",
"emails.invitation.signature": "{{project}} team",
+ "emails.certificate.subject": "Certificate failure for %s",
+ "emails.certificate.hello": "Hello",
+ "emails.certificate.body": "Certificate for your domain '{{domain}}' could not be generated. This is attempt no. {{attempt}}, and the failure was caused by: {{error}}",
+ "emails.certificate.footer": "Your previous certificate willl be valid for 30 days since the first failure. We highly recommend investigating this case, otherwise your domain will end up without a valid SSL communication.",
+ "emails.certificate.thanks": "Thanks",
+ "emails.certificate.signature": "{{project}} team",
"locale.country.unknown": "Unknown",
"countries.af": "Afghanistan",
"countries.ao": "Angola",
diff --git a/app/config/providers.php b/app/config/providers.php
index 1d364a3fcf..c0acceb0fc 100644
--- a/app/config/providers.php
+++ b/app/config/providers.php
@@ -241,16 +241,6 @@ return [ // Ordered by ABC.
'beta' => false,
'mock' => false,
],
- 'vk' => [
- 'name' => 'VK',
- 'developers' => 'https://vk.com/dev',
- 'icon' => 'icon-vk',
- 'enabled' => true,
- 'sandbox' => false,
- 'form' => false,
- 'beta' => false,
- 'mock' => false,
- ],
'zoom' => [
'name' => 'Zoom',
'developers' => 'https://marketplace.zoom.us/docs/guides/auth/oauth/',
diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php
index 3462eec2cb..eedc8f6dfe 100644
--- a/app/controllers/api/account.php
+++ b/app/controllers/api/account.php
@@ -104,7 +104,7 @@ App::post('/v1/account')
'reset' => false,
'name' => $name,
'prefs' => new \stdClass(),
- 'sessions' => [],
+ 'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
@@ -167,7 +167,10 @@ App::post('/v1/account/sessions')
$email = \strtolower($email);
$protocol = $request->getProtocol();
- $profile = $dbForProject->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
+ $profile = $dbForProject->findOne('users', [
+ new Query('deleted', Query::TYPE_EQUAL, [false]),
+ new Query('email', Query::TYPE_EQUAL, [$email])]
+ );
if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) {
$audits
@@ -208,8 +211,7 @@ App::post('/v1/account/sessions')
->setAttribute('$write', ['user:' . $profile->getId()])
);
- $profile->setAttribute('sessions', $session, Document::SET_TYPE_APPEND);
- $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile);
+ $dbForProject->deleteCachedDocument('users', $profile->getId());
$audits
->setParam('userId', $profile->getId())
@@ -458,13 +460,10 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$current = Auth::sessionVerify($sessions, Auth::$secret);
if ($current) { // Delete current session of new one.
- foreach ($sessions as $key => $session) {/** @var Document $session */
- if ($current === $session['$id']) {
- unset($sessions[$key]);
-
- $dbForProject->deleteDocument('sessions', $session->getId());
- $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $sessions));
- }
+ $currentDocument = $dbForProject->getDocument('sessions', $current);
+ if(!$currentDocument->isEmpty()) {
+ $dbForProject->deleteDocument('sessions', $currentDocument->getId());
+ $dbForProject->deleteCachedDocument('users', $user->getId());
}
}
@@ -476,14 +475,21 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
if ($user === false || $user->isEmpty()) { // No user logged in or with OAuth2 provider ID, create new one or connect with account with same email
$name = $oauth2->getUserName($accessToken);
$email = $oauth2->getUserEmail($accessToken);
+ $isVerified = $oauth2->isEmailVerified($accessToken);
- $user = $dbForProject->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
+ if ($isVerified === true) {
+ // Get user by email address
+ $user = $dbForProject->findOne('users', [
+ new Query('deleted', Query::TYPE_EQUAL, [false]),
+ new Query('email', Query::TYPE_EQUAL, [$email])]
+ );
+ }
if ($user === false || $user->isEmpty()) { // Last option -> create the user, generate random password
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
if ($limit !== 0) {
- $total = $dbForProject->count('users', [ new Query('deleted', Query::TYPE_EQUAL, [false]),], APP_LIMIT_USERS);
+ $total = $dbForProject->count('users', [new Query('deleted', Query::TYPE_EQUAL, [false])], APP_LIMIT_USERS);
if ($total >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED);
@@ -497,7 +503,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'$read' => ['role:all'],
'$write' => ['user:' . $userId],
'email' => $email,
- 'emailVerification' => true,
+ 'emailVerification' => $isVerified,
'status' => true, // Email should already be authenticated by OAuth2 provider
'password' => Auth::passwordHash(Auth::passwordGenerator()),
'passwordUpdate' => 0,
@@ -505,7 +511,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'reset' => false,
'name' => $name,
'prefs' => new \stdClass(),
- 'sessions' => [],
+ 'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
@@ -522,7 +528,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
}
// Create session token, verify user account and update OAuth2 ID and Access Token
-
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$secret = Auth::tokenGenerator();
@@ -553,17 +558,18 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$user
->setAttribute('status', true)
- ->setAttribute('sessions', $session, Document::SET_TYPE_APPEND)
;
Authorization::setRole('user:' . $user->getId());
+ $dbForProject->updateDocument('users', $user->getId(), $user);
+
$session = $dbForProject->createDocument('sessions', $session
->setAttribute('$read', ['user:' . $user->getId()])
->setAttribute('$write', ['user:' . $user->getId()])
);
- $user = $dbForProject->updateDocument('users', $user->getId(), $user);
+ $dbForProject->deleteCachedDocument('users', $user->getId());
$audits
->setParam('userId', $user->getId())
@@ -679,7 +685,7 @@ App::post('/v1/account/sessions/magic-url')
'registration' => \time(),
'reset' => false,
'prefs' => new \stdClass(),
- 'sessions' => [],
+ 'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email]),
@@ -824,6 +830,10 @@ App::put('/v1/account/sessions/magic-url')
->setAttribute('$write', ['user:' . $user->getId()])
);
+ $dbForProject->deleteCachedDocument('users', $user->getId());
+
+ $tokens = $user->getAttribute('tokens', []);
+
/**
* We act like we're updating and validating
* the recovery token but actually we don't need it anymore.
@@ -831,7 +841,10 @@ App::put('/v1/account/sessions/magic-url')
$dbForProject->deleteDocument('tokens', $token);
$dbForProject->deleteCachedDocument('users', $user->getId());
- $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND));
+ $user
+ ->setAttribute('emailVerification', true);
+
+ $user = $dbForProject->updateDocument('users', $user->getId(), $user);
if (false === $user) {
throw new Exception('Failed saving user to DB', 500, Exception::GENERAL_SERVER_ERROR);
@@ -938,7 +951,7 @@ App::post('/v1/account/sessions/anonymous')
'reset' => false,
'name' => null,
'prefs' => new \stdClass(),
- 'sessions' => [],
+ 'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => $userId,
@@ -974,8 +987,7 @@ App::post('/v1/account/sessions/anonymous')
->setAttribute('$write', ['user:' . $user->getId()])
);
- $user = $dbForProject->updateDocument('users', $user->getId(),
- $user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND));
+ $dbForProject->deleteCachedDocument('users', $user->getId());
$audits
->setParam('userId', $user->getId())
@@ -1026,16 +1038,17 @@ App::post('/v1/account/jwt')
->label('abuse-key', 'url:{url},userId:{userId}')
->inject('response')
->inject('user')
- ->action(function ($response, $user) {
+ ->inject('dbForProject')
+ ->action(function ($response, $user, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
+ /** @var Utopia\Database\Database $dbForProject */
+
$sessions = $user->getAttribute('sessions', []);
$current = new Document();
- foreach ($sessions as $session) {
- /** @var Utopia\Database\Document $session */
-
+ foreach ($sessions as $session) { /** @var Utopia\Database\Document $session */
if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$current = $session;
}
@@ -1619,8 +1632,8 @@ App::delete('/v1/account/sessions/:sessionId')
->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
;
}
-
- $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $sessions));
+
+ $dbForProject->deleteCachedDocument('users', $user->getId());
$events
->setParam('eventData', $response->output($session, Response::MODEL_SESSION))
@@ -1714,8 +1727,7 @@ App::patch('/v1/account/sessions/:sessionId')
$dbForProject->updateDocument('sessions', $sessionId, $session);
- $user->setAttribute("sessions", $sessions);
- $user = $dbForProject->updateDocument('users', $user->getId(), $user);
+ $dbForProject->deleteCachedDocument('users', $user->getId());
$audits
->setParam('userId', $user->getId())
@@ -1801,7 +1813,7 @@ App::delete('/v1/account/sessions')
}
}
- $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', []));
+ $dbForProject->deleteCachedDocument('users', $user->getId());
$numOfSessions = count($sessions);
@@ -1864,7 +1876,11 @@ App::post('/v1/account/recovery')
$isAppUser = Auth::isAppUser($roles);
$email = \strtolower($email);
- $profile = $dbForProject->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
+
+ $profile = $dbForProject->findOne('users', [
+ new Query('deleted', Query::TYPE_EQUAL, [false]),
+ new Query('email', Query::TYPE_EQUAL, [$email])
+ ]);
if (!$profile) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php
index 38e3fc31c5..a9267da138 100644
--- a/app/controllers/api/projects.php
+++ b/app/controllers/api/projects.php
@@ -23,7 +23,7 @@ use Utopia\Registry\Registry;
use Appwrite\Extend\Exception;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
-use Utopia\Validator\Integer;
+use Utopia\Validator\Hostname;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
@@ -972,6 +972,14 @@ App::post('/v1/projects/:projectId/platforms')
->inject('dbForConsole')
->action(function (string $projectId, string $type, string $name, string $key, string $store, string $hostname, Response $response, Database $dbForConsole) {
+ // Ensure hostname has proper structure (no port, protocol..)
+ if(!empty($hostname)) {
+ $validator = new Hostname();
+ if (!is_null($hostname) && !$validator->isValid($hostname)) {
+ throw new Exception($validator->getDescription(), 400, Exception::ATTRIBUTE_VALUE_INVALID);
+ }
+ }
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1085,6 +1093,14 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
->inject('dbForConsole')
->action(function (string $projectId, string $platformId, string $name, string $key, string $store, string $hostname, Response $response, Database $dbForConsole) {
+ // Ensure hostname has proper structure (no port, protocol..)
+ if(!empty($hostname)) {
+ $validator = new Hostname();
+ if (!is_null($hostname) && !$validator->isValid($hostname)) {
+ throw new Exception($validator->getDescription(), 400, Exception::ATTRIBUTE_VALUE_INVALID);
+ }
+ }
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1331,8 +1347,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification')
$dbForConsole->deleteCachedDocument('projects', $project->getId());
// Issue a TLS certificate when domain is verified
- Resque::enqueue('v1-certificates', 'CertificatesV1', [
- 'document' => $domain->getArrayCopy(),
+ Resque::enqueue(Event::CERTIFICATES_QUEUE_NAME, Event::CERTIFICATES_CLASS_NAME, [
'domain' => $domain->getAttribute('domain'),
]);
diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php
index 9ad25bda69..91727bb30b 100644
--- a/app/controllers/api/storage.php
+++ b/app/controllers/api/storage.php
@@ -57,7 +57,7 @@ App::post('/v1/storage/buckets')
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('enabled', true, new Boolean(true), 'Is bucket enabled?', true)
- ->param('maximumFileSize', (int) App::getEnv('_APP_STORAGE_LIMIT', 0), new Integer(), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '. For self-hosted setups you can change the max limit by changing the `_APP_STORAGE_LIMIT` environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)', true)
+ ->param('maximumFileSize', (int) App::getEnv('_APP_STORAGE_LIMIT', 0), new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '. For self-hosted setups you can change the max limit by changing the `_APP_STORAGE_LIMIT` environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)', true)
->param('allowedFileExtensions', [], new ArrayList(new Text(64)), 'Allowed file extensions', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
@@ -223,7 +223,7 @@ App::put('/v1/storage/buckets/:bucketId')
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true)
->param('enabled', true, new Boolean(true), 'Is bucket enabled?', true)
- ->param('maximumFileSize', null, new Integer(), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human((int)App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '. For self hosted version you can change the limit by changing _APP_STORAGE_LIMIT environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)', true)
+ ->param('maximumFileSize', null, new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human((int)App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '. For self hosted version you can change the limit by changing _APP_STORAGE_LIMIT environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)', true)
->param('allowedFileExtensions', [], new ArrayList(new Text(64)), 'Allowed file extensions', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
@@ -397,7 +397,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
$maximumFileSize = $bucket->getAttribute('maximumFileSize', 0);
if ($maximumFileSize > (int) App::getEnv('_APP_STORAGE_LIMIT', 0)) {
- throw new Exception('Error bucket maximum file size is larger than _APP_STORAGE_LIMIT', 500, Exception::GENERAL_SERVER_ERROR);
+ throw new Exception('Maximum bucket file size is larger than _APP_STORAGE_LIMIT', 500, Exception::GENERAL_SERVER_ERROR);
}
$file = $request->getFiles('file');
diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php
index 501096e349..a6bb58ba3d 100644
--- a/app/controllers/api/teams.php
+++ b/app/controllers/api/teams.php
@@ -341,7 +341,7 @@ App::post('/v1/teams/:teamId/memberships')
'reset' => false,
'name' => $name,
'prefs' => new \stdClass(),
- 'sessions' => [],
+ 'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
@@ -708,11 +708,10 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->setAttribute('$write', ['user:'.$user->getId()])
);
- $user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND);
+ $dbForProject->deleteCachedDocument('users', $user->getId());
Authorization::setRole('user:'.$userId);
- $user = $dbForProject->updateDocument('users', $user->getId(), $user);
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
$dbForProject->deleteCachedDocument('users', $user->getId());
diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php
index d4b2109798..2f33098bcb 100644
--- a/app/controllers/api/users.php
+++ b/app/controllers/api/users.php
@@ -63,7 +63,7 @@ App::post('/v1/users')
'reset' => false,
'name' => $name,
'prefs' => new \stdClass(),
- 'sessions' => [],
+ 'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
@@ -632,25 +632,20 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
- $sessions = $user->getAttribute('sessions', []);
+ $session = $dbForProject->getDocument('sessions', $sessionId);
- foreach ($sessions as $key => $session) { /** @var Document $session */
-
- if ($sessionId == $session->getId()) {
- unset($sessions[$key]);
-
- $dbForProject->deleteDocument('sessions', $session->getId());
-
- $user->setAttribute('sessions', $sessions);
-
- $events
- ->setParam('eventData', $response->output($user, Response::MODEL_USER))
- ;
-
- $dbForProject->updateDocument('users', $user->getId(), $user);
- }
+ if($session->isEmpty()) {
+ throw new Exception('User not found', 404, Exception::USER_SESSION_NOT_FOUND);
}
+ $dbForProject->deleteDocument('sessions', $session->getId());
+
+ $dbForProject->deleteCachedDocument('users', $user->getId());
+
+ $events
+ ->setParam('eventData', $response->output($user, Response::MODEL_USER))
+ ;
+
$usage
->setParam('users.update', 1)
->setParam('users.sessions.delete', 1)
@@ -693,7 +688,7 @@ App::delete('/v1/users/:userId/sessions')
$dbForProject->deleteDocument('sessions', $session->getId());
}
- $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', []));
+ $dbForProject->deleteCachedDocument('users', $user->getId());
$events
->setParam('eventData', $response->output($user, Response::MODEL_USER))
diff --git a/app/controllers/general.php b/app/controllers/general.php
index eacf926955..b9c45443b6 100644
--- a/app/controllers/general.php
+++ b/app/controllers/general.php
@@ -20,6 +20,7 @@ use Utopia\CLI\Console;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
+use Utopia\Validator\Hostname;
use Appwrite\Utopia\Request\Filters\V12 as RequestV12;
use Appwrite\Utopia\Request\Filters\V13 as RequestV13;
use Utopia\Validator\Text;
@@ -99,14 +100,11 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
]);
$domainDocument = $dbForConsole->createDocument('domains', $domainDocument);
-
+
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
-
+
Resque::enqueue(Event::CERTIFICATES_QUEUE_NAME, Event::CERTIFICATES_CLASS_NAME, [
- 'document' => $domainDocument,
- 'domain' => $domain->get(),
- 'validateTarget' => false,
- 'validateCNAME' => false,
+ 'domain' => $domain->get()
]);
}
}
@@ -135,8 +133,13 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
$protocol = \parse_url($request->getOrigin($referrer), PHP_URL_SCHEME);
$port = \parse_url($request->getOrigin($referrer), PHP_URL_PORT);
- $refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()).'://'.((\in_array($origin, $clients))
- ? $origin : 'localhost').(!empty($port) ? ':'.$port : '');
+ $refDomainOrigin = 'localhost';
+ $validator = new Hostname($clients);
+ if ($validator->isValid($origin)) {
+ $refDomainOrigin = $origin;
+ }
+
+ $refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $refDomainOrigin . (!empty($port) ? ':' . $port : '');
$refDomain = (!$route->getLabel('origin', false)) // This route is publicly accessible
? $refDomain
@@ -185,6 +188,10 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
*/
if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
if ($request->getProtocol() !== 'https') {
+ if($request->getMethod() !== Request::METHOD_GET) {
+ throw new Exception('Method unsupported over HTTP.', 500, Exception::GENERAL_PROTOCOL_UNSUPPORTED);
+ }
+
return $response->redirect('https://'.$request->getHostname().$request->getURI());
}
diff --git a/app/init.php b/app/init.php
index e693906167..269d860fe6 100644
--- a/app/init.php
+++ b/app/init.php
@@ -127,6 +127,7 @@ const MAIL_TYPE_VERIFICATION = 'verification';
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
const MAIL_TYPE_RECOVERY = 'recovery';
const MAIL_TYPE_INVITATION = 'invitation';
+const MAIL_TYPE_CERTIFICATE = 'certificate';
// Auth Types
const APP_AUTH_TYPE_SESSION = 'Session';
const APP_AUTH_TYPE_JWT = 'JWT';
@@ -301,6 +302,19 @@ Database::addFilter('subQueryWebhooks',
}
);
+Database::addFilter('subQuerySessions',
+ function($value) {
+ return null;
+ },
+ function($value, Document $document, Database $database) {
+ $sessions = Authorization::skip(fn () => $database->find('sessions', [
+ new Query('userId', Query::TYPE_EQUAL, [$document->getId()])
+ ], $database->getIndexLimit(), 0, []));
+
+ return $sessions;
+ }
+);
+
Database::addFilter('subQueryTokens',
function($value) {
return null;
diff --git a/app/tasks/maintenance.php b/app/tasks/maintenance.php
index a6f37ff71b..056ee59fcd 100644
--- a/app/tasks/maintenance.php
+++ b/app/tasks/maintenance.php
@@ -1,10 +1,42 @@
get('cache')));
+ $database = new Database(new MariaDB($register->get('db')), $cache);
+ $database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
+ $database->setNamespace('_console'); // Main DB
+ break; // leave loop if successful
+ } 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());
+ }
+ sleep(DATABASE_RECONNECT_SLEEP);
+ }
+ } while ($attempts < DATABASE_RECONNECT_MAX_ATTEMPTS);
+
+ return $database;
+}
$cli
->task('maintenance')
@@ -54,6 +86,29 @@ $cli
]);
}
+ function renewCertificates($dbForConsole)
+ {
+ $time = date('d-m-Y H:i:s', time());
+ /** @var Utopia\Database\Database $dbForConsole */
+
+ $certificates = $dbForConsole->find('certificates', [
+ new Query('attempts', Query::TYPE_LESSEREQUAL, [5]), // Maximum 5 attempts
+ new Query('renewDate', Query::TYPE_LESSEREQUAL, [\time()]) // includes 60 days cooldown (we have 30 days to renew)
+ ], 200); // Limit 200 comes from LetsEncrypt (300 orders per 3 hours, keeping some for new domains)
+
+ if(\count($certificates) > 0) {
+ Console::info("[{$time}] Found " . \count($certificates) . " certificates for renewal, scheduling jobs.");
+
+ foreach ($certificates as $certificate) {
+ Resque::enqueue(Event::CERTIFICATES_QUEUE_NAME, Event::CERTIFICATES_CLASS_NAME, [
+ 'domain' => $certificate->getAttribute('domain'),
+ ]);
+ }
+ } else {
+ Console::info("[{$time}] No certificates for renewal.");
+ }
+ }
+
// # of days in seconds (1 day = 86400s)
$interval = (int) App::getEnv('_APP_MAINTENANCE_INTERVAL', '86400');
$executionLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', '1209600');
@@ -62,13 +117,17 @@ $cli
$usageStatsRetention30m = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_30M', '129600');//36 hours
$usageStatsRetention1d = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_1D', '8640000'); // 100 days
- Console::loop(function() use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d) {
+ Console::loop(function() use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d) {
+ $database = getConsoleDB();
+
$time = date('d-m-Y H:i:s', time());
- Console::info("[{$time}] Notifying deletes workers every {$interval} seconds");
+ Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds");
notifyDeleteExecutionLogs($executionLogsRetention);
notifyDeleteAbuseLogs($abuseLogsRetention);
notifyDeleteAuditLogs($auditLogRetention);
notifyDeleteUsageStats($usageStatsRetention30m, $usageStatsRetention1d);
notifyDeleteConnections();
+
+ renewCertificates($database);
}, $interval);
});
\ No newline at end of file
diff --git a/app/tasks/ssl.php b/app/tasks/ssl.php
index 2c32324fa3..743be92a96 100644
--- a/app/tasks/ssl.php
+++ b/app/tasks/ssl.php
@@ -2,22 +2,21 @@
global $cli;
+use Appwrite\Event\Event;
use Utopia\App;
use Utopia\CLI\Console;
+use Utopia\Validator\Hostname;
$cli
->task('ssl')
->desc('Validate server certificates')
- ->action(function () {
- $domain = App::getEnv('_APP_DOMAIN', '');
+ ->param('domain', App::getEnv('_APP_DOMAIN', ''), new Hostname(), 'Domain to generate certificate for. If empty, main domain will be used.', true)
+ ->action(function ($domain) {
+ Console::success('Scheduling a job to issue a TLS certificate for domain: ' . $domain);
- Console::log('Issue a TLS certificate for master domain ('.$domain.') in 30 seconds.
- Make sure your domain points to your server or restart to try again.');
-
- ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [
- 'document' => [],
+ // Scheduje a job
+ Resque::enqueue(Event::CERTIFICATES_QUEUE_NAME, Event::CERTIFICATES_CLASS_NAME, [
'domain' => $domain,
- 'validateTarget' => false,
- 'validateCNAME' => false,
+ 'skipCheck' => true
]);
});
\ No newline at end of file
diff --git a/app/views/console/home/index.phtml b/app/views/console/home/index.phtml
index 028e7f3b79..82be016b61 100644
--- a/app/views/console/home/index.phtml
+++ b/app/views/console/home/index.phtml
@@ -299,8 +299,9 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
-
-
+
+
+
You can use * to allow wildcard hostnames or subdomains.
Next Steps
@@ -329,7 +330,8 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
data-success="alert,trigger"
data-success-param-alert-text="Updated platform successfully"
data-success-param-trigger-events="projects.updatePlatform"
- data-failure="alert"
+ data-failure="alert,trigger"
+ data-failure-param-trigger-events="projects.updatePlatform"
data-failure-param-alert-text="Failed to update platform"
data-failure-param-alert-classname="error">
@@ -340,7 +342,8 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
-
+
+
You can use * to allow wildcard hostnames or subdomains.
@@ -714,7 +717,8 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
data-success="alert,trigger"
data-success-param-alert-text="Updated platform successfully"
data-success-param-trigger-events="projects.updatePlatform"
- data-failure="alert"
+ data-failure="alert,trigger"
+ data-failure-param-trigger-events="projects.updatePlatform"
data-failure-param-alert-text="Failed to update platform"
data-failure-param-alert-classname="error">
@@ -746,7 +750,8 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
data-success="alert,trigger"
data-success-param-alert-text="Updated platform successfully"
data-success-param-trigger-events="projects.updatePlatform"
- data-failure="alert"
+ data-failure="alert,trigger"
+ data-failure-param-trigger-events="projects.updatePlatform"
data-failure-param-alert-text="Failed to update platform"
data-failure-param-alert-classname="error">
@@ -777,7 +782,8 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
data-success="alert,trigger"
data-success-param-alert-text="Updated platform successfully"
data-success-param-trigger-events="projects.updatePlatform"
- data-failure="alert"
+ data-failure="alert,trigger"
+ data-failure-param-trigger-events="projects.updatePlatform"
data-failure-param-alert-text="Failed to update platform"
data-failure-param-alert-classname="error">
@@ -808,7 +814,8 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
data-success="alert,trigger"
data-success-param-alert-text="Updated platform successfully"
data-success-param-trigger-events="projects.updatePlatform"
- data-failure="alert"
+ data-failure="alert,trigger"
+ data-failure-param-trigger-events="projects.updatePlatform"
data-failure-param-alert-text="Failed to update platform"
data-failure-param-alert-classname="error">
@@ -841,7 +848,8 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
data-success="alert,trigger"
data-success-param-alert-text="Updated platform successfully"
data-success-param-trigger-events="projects.updatePlatform"
- data-failure="alert"
+ data-failure="alert,trigger"
+ data-failure-param-trigger-events="projects.updatePlatform"
data-failure-param-alert-text="Failed to update platform"
data-failure-param-alert-classname="error">
@@ -873,7 +881,8 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
data-success="alert,trigger"
data-success-param-alert-text="Updated platform successfully"
data-success-param-trigger-events="projects.updatePlatform"
- data-failure="alert"
+ data-failure="alert,trigger"
+ data-failure-param-trigger-events="projects.updatePlatform"
data-failure-param-alert-text="Failed to update platform"
data-failure-param-alert-classname="error">
diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml
index 7fc5b2b727..c6f7726058 100644
--- a/app/views/install/compose.phtml
+++ b/app/views/install/compose.phtml
@@ -342,6 +342,7 @@ services:
environment:
- _APP_ENV
- _APP_OPENSSL_KEY_V1
+ - _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
@@ -473,6 +474,8 @@ services:
environment:
- _APP_ENV
- _APP_OPENSSL_KEY_V1
+ - _APP_DOMAIN
+ - _APP_DOMAIN_TARGET
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
diff --git a/app/workers/certificates.php b/app/workers/certificates.php
index 9f9ce33dbd..f932e4b8f8 100644
--- a/app/workers/certificates.php
+++ b/app/workers/certificates.php
@@ -1,9 +1,11 @@
getConsoleDB();
-
- /**
- * 1. Get new domain document - DONE
- * 1.1. Validate domain is valid, public suffix is known and CNAME records are verified - DONE
- * 2. Check if a certificate already exists - DONE
- * 3. Check if certificate is about to expire, if not - skip it
- * 3.1. Create / renew certificate
- * 3.2. Update loadblancer
- * 3.3. Update database (domains, change date, expiry)
- * 3.4. Set retry on failure
- * 3.5. Schedule to renew certificate in 60 days
- */
-
Authorization::disable();
- // Args
- $document = $this->args['document'];
- $domain = $this->args['domain'];
+ $this->dbForConsole = $this->getConsoleDB();
- // Validation Args
- $validateTarget = $this->args['validateTarget'] ?? true;
- $validateCNAME = $this->args['validateCNAME'] ?? true;
+ /**
+ * 1. Read arguments and validate domain
+ * 2. Get main domain
+ * 3. Validate CNAME DNS if parameter is not main domain (meaning it's custom domain)
+ * 4. Validate security email. Cannot be empty, required by LetsEncrypt
+ * 5. Validate renew date with certificate file, unless requested to skip by parameter
+ * 6. Issue a certificate using certbot CLI
+ * 7. Update 'log' attribute on certificate document with Certbot message
+ * 8. Create storage folder for certificate, if not ready already
+ * 9. Move certificates from Certbot location to our Storage
+ * 10. Create/Update our Storage with new Traefik config with new certificate paths
+ * 11. Read certificate file and update 'renewDate' on certificate document
+ * 12. Update 'issueDate' and 'attempts' on certificate
+ *
+ * If at any point unexpected error occurs, program stops without applying changes to document, and error is thrown into worker
+ *
+ * If code stops with expected error:
+ * 1. 'log' attribute on document is updated with error message
+ * 2. 'attempts' amount is increased
+ * 3. Console log is shown
+ * 4. Email is sent to security email
+ *
+ * Unless unexpected error occurs, at the end, we:
+ * 1. Update 'updated' attribute on document
+ * 2. Save document to database
+ * 3. Update all domains documents with current certificate ID
+ *
+ * Note: Renewals are checked and scheduled from maintenence worker
+ */
- // Options
- $domain = new Domain((!empty($domain)) ? $domain : '');
- $expiry = 60 * 60 * 24 * 30 * 2; // 60 days
- $safety = 60 * 60; // 1 hour
- $renew = (\time() + $expiry);
+ try {
+ // Read arguments
+ $domain = $this->args['domain']; // String of domain (hostname)
+ $skipCheck = $this->args['skipCheck'] ?? false; // If true, we won't double-check expiry from cert file
- if (empty($domain->get())) {
- throw new Exception('Missing domain');
- }
+ $domain = new Domain((!empty($domain)) ? $domain : '');
- if (!$domain->isKnown() || $domain->isTest()) {
- throw new Exception('Unknown public suffix for domain');
- }
-
- if ($validateTarget) {
- $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
-
- if(!$target->isKnown() || $target->isTest()) {
- throw new Exception('Unreachable CNAME target ('.$target->get().'), please use a domain with a public suffix.');
- }
- }
-
- if ($validateCNAME) {
- $validator = new CNAME($target->get()); // Verify Domain with DNS records
-
- if(!$validator->isValid($domain->get())) {
- throw new Exception('Failed to verify domain DNS records');
- }
- }
-
- $certificate = $dbForConsole->findOne('certificates', [
- new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()])
- ]);
-
- // $condition = ($certificate
- // && $certificate instanceof Document
- // && isset($certificate['issueDate'])
- // && (($certificate['issueDate'] + ($expiry)) > time())) ? 'true' : 'false';
-
- // throw new Exception('cert issued at'.date('d.m.Y H:i', $certificate['issueDate']).' | renew date is: '.date('d.m.Y H:i', ($certificate['issueDate'] + ($expiry))).' | condition is '.$condition);
-
- $certificate = (!empty($certificate) && $certificate instanceof $certificate) ? $certificate->getArrayCopy() : [];
-
- if (
- !empty($certificate)
- && isset($certificate['issueDate'])
- && (($certificate['issueDate'] + ($expiry)) > \time())
- ) { // Check last issue time
- throw new Exception('Renew isn\'t required');
- }
-
- $staging = (App::isProduction()) ? '' : ' --dry-run';
- $email = App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS');
-
- if (empty($email)) {
- throw new Exception('You must set a valid security email address (_APP_SYSTEM_SECURITY_EMAIL_ADDRESS) to issue an SSL certificate');
- }
-
- $stdout = '';
- $stderr = '';
-
- $exit = Console::execute("certbot certonly --webroot --noninteractive --agree-tos{$staging}"
- . " --email " . $email
- . " -w " . APP_STORAGE_CERTIFICATES
- . " -d {$domain->get()}", '', $stdout, $stderr);
-
- if ($exit !== 0) {
- throw new Exception('Failed to issue a certificate with message: ' . $stderr);
- }
-
- $path = APP_STORAGE_CERTIFICATES . '/' . $domain->get();
-
- if (!\is_readable($path)) {
- if (!\mkdir($path, 0755, true)) {
- throw new Exception('Failed to create path...');
- }
- }
-
- if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/cert.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/cert.pem')) {
- throw new Exception('Failed to rename certificate cert.pem: '.\json_encode($stdout));
- }
-
- if (!@\rename('/etc/letsencrypt/live/' . $domain->get() . '/chain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain->get() . '/chain.pem')) {
- throw new Exception('Failed to rename certificate chain.pem: ' . \json_encode($stdout));
- }
-
- if (!@\rename('/etc/letsencrypt/live/' . $domain->get() . '/fullchain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain->get() . '/fullchain.pem')) {
- throw new Exception('Failed to rename certificate fullchain.pem: ' . \json_encode($stdout));
- }
-
- if (!@\rename('/etc/letsencrypt/live/' . $domain->get() . '/privkey.pem', APP_STORAGE_CERTIFICATES . '/' . $domain->get() . '/privkey.pem')) {
- throw new Exception('Failed to rename certificate privkey.pem: ' . \json_encode($stdout));
- }
-
- $certificate = new Document(\array_merge($certificate, [
- 'domain' => $domain->get(),
- 'issueDate' => \time(),
- 'renewDate' => $renew,
- 'attempts' => 0,
- 'log' => \json_encode($stdout),
- ]));
-
- $certificate = $dbForConsole->createDocument('certificates', $certificate);
-
- if (!$certificate) {
- throw new Exception('Failed saving certificate to DB');
- }
-
- if(!empty($document)) {
- $certificate = new Document(\array_merge($document, [
- 'updated' => \time(),
- 'certificateId' => $certificate->getId(),
- ]));
-
- $certificate = $dbForConsole->updateDocument('domains', $certificate->getId(), $certificate);
+ // Get current certificate
+ $certificate = $this->dbForConsole->findOne('certificates', [ new Query('domain', Query::TYPE_EQUAL, [$domain->get()]) ]);
+ // If we don't have certificate for domain yet, let's create new document. At the end we save it
if(!$certificate) {
- throw new Exception('Failed saving domain to DB');
+ $certificate = new Document();
+ $certificate->setAttribute('domain', $domain->get());
}
+
+ // Email for alerts is required by LetsEncrypt
+ $email = App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS');
+ if (empty($email)) {
+ throw new Exception('You must set a valid security email address (_APP_SYSTEM_SECURITY_EMAIL_ADDRESS) to issue an SSL certificate.');
+ }
+
+ // Validate domain and DNS records. Skip if job is forced
+ if(!$skipCheck) {
+ $mainDomain = $this->getMainDomain();
+ $isMainDomain = !isset($mainDomain) || $domain->get() === $mainDomain;
+ $this->validateDomain($domain, $isMainDomain);
+ }
+
+ // If certificate exists already, double-check expiry date. Skip if job is forced
+ if(!$skipCheck && !$this->isRenewRequired($domain->get())) {
+ throw new Exception('Renew isn\'t required.');
+ }
+
+ // Generate certificate files using Let's Encrypt
+ $letsEncryptData = $this->issueCertificate($domain->get(), $email);
+
+ // Command succeeded, store all data into document
+ // We store stderr too, because it may include warnings
+ $certificate->setAttribute('log', \json_encode([
+ 'stdout' => $letsEncryptData['stdout'],
+ 'stderr' => $letsEncryptData['stderr'],
+ ]));
+
+ // Give certificates to Traefik
+ $this->applyCertificateFiles($domain->get(), $letsEncryptData);
+
+ // Update certificate info stored in database
+ $certificate->setAttribute('renewDate', $this->getRenewDate($domain->get()));
+ $certificate->setAttribute('attempts', 0);
+ $certificate->setAttribute('issueDate', \time());
+ } catch(Throwable $e) {
+ // Set exception as log in certificate document
+ $certificate->setAttribute('log', $e->getMessage());
+
+ // Increase attempts count
+ $attempts = $certificate->getAttribute('attempts', 0) + 1;
+ $certificate->setAttribute('attempts', $attempts);
+
+ // Send email to security email
+ $this->notifyError($domain->get(), $e->getMessage(), $attempts);
+ } finally {
+ // All actions result in new updatedAt date
+ $certificate->setAttribute('updated', \time());
+
+ // Save all changes we made to certificate document into database
+ $this->saveCertificateDocument($domain->get(), $certificate);
+
+ Authorization::reset();
}
-
- $config =
-"tls:
- certificates:
- - certFile: /storage/certificates/{$domain->get()}/fullchain.pem
- keyFile: /storage/certificates/{$domain->get()}/privkey.pem";
-
- if (!\file_put_contents(APP_STORAGE_CONFIG . '/' . $domain->get() . '.yml', $config)) {
- throw new Exception('Failed to save SSL configuration');
- }
-
- ResqueScheduler::enqueueAt($renew + $safety, 'v1-certificates', 'CertificatesV1', [
- 'document' => [],
- 'domain' => $domain->get(),
- 'validateTarget' => $validateTarget,
- 'validateCNAME' => $validateCNAME,
- ]); // Async task rescheduale
-
- Authorization::reset();
}
public function shutdown(): void
{
}
+
+ /**
+ * Save certificate data into database.
+ *
+ * @param string $domain Domain name that certificate is for
+ * @param Document $certificate Certificate document that we need to save
+ *
+ * @return void
+ */
+ private function saveCertificateDocument(string $domain, Document $certificate): void {
+ // Check if update or insert required
+ $certificateDocument = $this->dbForConsole->findOne('certificates', [ new Query('domain', Query::TYPE_EQUAL, [$domain]) ]);
+ if (!empty($certificateDocument) && !$certificateDocument->isEmpty()) {
+ // Merge new data with current data
+ $certificate = new Document(\array_merge($certificateDocument->getArrayCopy(), $certificate->getArrayCopy()));
+
+ $certificate = $this->dbForConsole->updateDocument('certificates', $certificate->getId(), $certificate);
+ } else {
+ $certificate = $this->dbForConsole->createDocument('certificates', $certificate);
+ }
+
+ $certificateId = $certificate->getId();
+ $this->updateDomainDocuments($certificateId, $domain);
+ }
+
+ /**
+ * Get main domain. Needed as we do different checks for main and non-main domains.
+ *
+ * @return null|string Returns main domain. If null, there is no main domain yet.
+ */
+ private function getMainDomain(): ?string {
+ if (!empty(App::getEnv('_APP_DOMAIN', ''))) {
+ return App::getEnv('_APP_DOMAIN', '');
+ } else {
+ $domainDocument = $this->dbForConsole->findOne('domains', [], 0, ['_id'], ['ASC']);
+ if($domainDocument) {
+ return $domainDocument->getAttribute('domain');
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Internal domain validation functionality to prevent unnecessary attempts failed from Let's Encrypt side. We check:
+ * - Domain needs to be public and valid (prevents NFT domains that are not supported by Let's Encrypt)
+ * - Domain must have proper DNS record
+ *
+ * @param Domain $domain Domain which we validate
+ * @param bool $isMainDomain In case of master domain, we look for different DNS configurations
+ *
+ * @return void
+ */
+ private function validateDomain(Domain $domain, bool $isMainDomain): void {
+ if (empty($domain->get())) {
+ throw new Exception('Missing certificate domain.');
+ }
+
+ if (!$domain->isKnown() || $domain->isTest()) {
+ throw new Exception('Unknown public suffix for domain.');
+ }
+
+ if (!$isMainDomain) {
+ // TODO: Would be awesome to also support A/AAAA records here. Maybe dry run?
+
+ // Validate if domain target is properly configured
+ $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
+
+ if (!$target->isKnown() || $target->isTest()) {
+ throw new Exception('Unreachable CNAME target ('.$target->get().'), please use a domain with a public suffix.');
+ }
+
+ // Verify domain with DNS records
+ $validator = new CNAME($target->get());
+ if (!$validator->isValid($domain->get())) {
+ throw new Exception('Failed to verify domain DNS records.');
+ }
+ } else {
+ // Main domain validation
+ // TODO: Would be awesome to check A/AAAA record here. Maybe dry run?
+ }
+ }
+
+ /**
+ * Reads expiry date of certificate from file and decides if renewal is required or not.
+ *
+ * @param string $domain Domain for which we check certificate file
+ *
+ * @return bool True, if certificate needs to be renewed
+ */
+ private function isRenewRequired(string $domain): bool {
+ $certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem';
+ if (\file_exists($certPath)) {
+ $validTo = null;
+
+ $certData = openssl_x509_parse(file_get_contents($certPath));
+ $validTo = $certData['validTo_time_t'] ?? 0;
+
+ if (empty($validTo)) {
+ throw new Exception('Unable to read certificate file (cert.pem).');
+ }
+
+ // LetsEncrypt allows renewal 30 days before expiry
+ $expiryInAdvance = (60*60*24*30);
+ if ($validTo - $expiryInAdvance > \time()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * LetsEncrypt communication to issue certificate (using certbot CLI)
+ *
+ * @param string $domain Domain to generate certificate for
+ *
+ * @return array Named array with keys 'stdout' and 'stderr', both string
+ */
+ private function issueCertificate(string $domain, string $email): array {
+ $staging = (App::isProduction()) ? '' : ' --dry-run';
+
+ $stdout = '';
+ $stderr = '';
+
+ $staging = (App::isProduction()) ? '' : ' --dry-run';
+ $exit = Console::execute("certbot certonly --webroot --noninteractive --agree-tos{$staging}"
+ . " --email " . $email
+ . " -w " . APP_STORAGE_CERTIFICATES
+ . " -d {$domain}", '', $stdout, $stderr);
+
+ // Unexpected error, usually 5XX, API limits, ...
+ if ($exit !== 0) {
+ throw new Exception('Failed to issue a certificate with message: ' . $stderr);
+ }
+
+ return [
+ 'stdout' => $stdout,
+ 'stderr' => $stderr
+ ];
+ }
+
+ /**
+ * Read new renew date from certificate file generated by Let's Encrypt
+ *
+ * @param string $domain Domain which certificate was generated for
+ *
+ * @return int
+ */
+ private function getRenewDate(string $domain): int {
+ $certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem';
+ $certData = openssl_x509_parse(file_get_contents($certPath));
+ $validTo = $certData['validTo_time_t'] ?? 0;
+ $expiryInAdvance = (60*60*24*30); // 30 days
+ return $validTo - $expiryInAdvance;
+ }
+
+ /**
+ * Method to take files from Let's Encrypt, and put it into Traefik.
+ *
+ * @param string $domain Domain which certificate was generated for
+ * @param array $letsEncryptData Let's Encrypt logs to use for additional info when throwing error
+ *
+ * @return void
+ */
+ private function applyCertificateFiles(string $domain, array $letsEncryptData): void {
+ // Prepare folder in storage for domain
+ $path = APP_STORAGE_CERTIFICATES . '/' . $domain;
+ if (!\is_readable($path)) {
+ if (!\mkdir($path, 0755, true)) {
+ throw new Exception('Failed to create path for certificate.');
+ }
+ }
+
+ // Move generated files from certbot into our storage
+ if(!@\rename('/etc/letsencrypt/live/'.$domain.'/cert.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem')) {
+ throw new Exception('Failed to rename certificate cert.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
+ }
+
+ if (!@\rename('/etc/letsencrypt/live/' . $domain . '/chain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/chain.pem')) {
+ throw new Exception('Failed to rename certificate chain.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
+ }
+
+ if (!@\rename('/etc/letsencrypt/live/' . $domain . '/fullchain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/fullchain.pem')) {
+ throw new Exception('Failed to rename certificate fullchain.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
+ }
+
+ if (!@\rename('/etc/letsencrypt/live/' . $domain . '/privkey.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/privkey.pem')) {
+ throw new Exception('Failed to rename certificate privkey.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
+ }
+
+ $config = \implode(PHP_EOL, [
+ "tls:",
+ " certificates:",
+ " - certFile: /storage/certificates/{$domain}/fullchain.pem",
+ " keyFile: /storage/certificates/{$domain}/privkey.pem"
+ ]);
+
+ // Save configuration into Traefik using our new cert files
+ if (!\file_put_contents(APP_STORAGE_CONFIG . '/' . $domain . '.yml', $config)) {
+ throw new Exception('Failed to save Traefik configuration.');
+ }
+ }
+
+ /**
+ * Method to make sure information about error is delivered to admnistrator.
+ *
+ * @param string $domain Domain that caused the error
+ * @param string $errorMessage Verbose error message
+ * @param int $attempt How many times it failed already
+ *
+ * @return void
+ */
+ private function notifyError(string $domain, string $errorMessage, int $attempt): void {
+ // Log error into console
+ Console::warning('Cannot renew domain (' . $domain . ') on attempt no. ' . $attempt . ' certificate: ' . $errorMessage);
+
+ // Send mail to administratore mail
+ Resque::enqueue(Event::MAILS_QUEUE_NAME, Event::MAILS_CLASS_NAME, [
+ 'from' => 'console',
+ 'project' => 'console',
+ 'name' => 'Appwrite Administrator',
+ 'recipient' => App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'),
+ 'url' => 'https://' . $domain,
+ 'locale' => App::getEnv('_APP_LOCALE', 'en'),
+ 'type' => MAIL_TYPE_CERTIFICATE,
+
+ 'domain' => $domain,
+ 'error' => $errorMessage,
+ 'attempt' => $attempt
+ ]);
+ }
+
+ /**
+ * Update all existing domain documents so they have relation to correct certificate document.
+ * This solved issues:
+ * - when adding a domain for which there is already a certificate
+ * - when renew creates new document? It might?
+ * - overall makes it more reliable
+ *
+ * @param string $certificateId ID of a new or updated certificate document
+ * @param string $domain Domain that is affected by new certificate
+ *
+ * @return void
+ */
+ private function updateDomainDocuments(string $certificateId, string $domain): void {
+ $domains = $this->dbForConsole->find('domains', [
+ new Query('domain', Query::TYPE_EQUAL, [$domain])
+ ], 1000);
+
+ foreach ($domains as $domainDocument) {
+ $domainDocument->setAttribute('updated', \time());
+ $domainDocument->setAttribute('certificateId', $certificateId);
+
+ $this->dbForConsole->updateDocument('domains', $domainDocument->getId(), $domainDocument);
+
+ if($domainDocument->getAttribute('projectId')) {
+ $this->dbForConsole->deleteCachedDocument('projects', $domainDocument->getAttribute('projectId'));
+ }
+ }
+ }
}
diff --git a/app/workers/deletes.php b/app/workers/deletes.php
index 892d417d9c..29b9bc5c49 100644
--- a/app/workers/deletes.php
+++ b/app/workers/deletes.php
@@ -41,6 +41,7 @@ class DeletesV1 extends Worker
public function run(): void
{
+
$projectId = $this->args['projectId'] ?? '';
$type = $this->args['type'] ?? '';
@@ -208,13 +209,14 @@ class DeletesV1 extends Worker
*/
$userId = $document->getId();
- $user = $this->getProjectDB($projectId)->getDocument('users', $userId);
// Delete all sessions of this user from the sessions table and update the sessions field of the user record
$this->deleteByGroup('sessions', [
new Query('userId', Query::TYPE_EQUAL, [$userId])
], $this->getProjectDB($projectId));
-
+
+ $this->getProjectDB($projectId)->deleteCachedDocument('users', $userId);
+
// Delete Memberships and decrement team membership counts
$this->deleteByGroup('memberships', [
new Query('userId', Query::TYPE_EQUAL, [$userId])
@@ -529,11 +531,40 @@ class DeletesV1 extends Worker
*/
protected function deleteCertificates(Document $document): void
{
+ $consoleDB = $this->getConsoleDB();
+
+ // If domain has certificate generated
+ if(isset($document['certificateId'])) {
+ $domainUsingCertificate = $consoleDB->findOne('domains', [
+ new Query('certificateId', Query::TYPE_EQUAL, [$document['certificateId']])
+ ]);
+
+ if(!$domainUsingCertificate) {
+ $mainDomain = App::getEnv('_APP_DOMAIN_TARGET', '');
+ if($mainDomain === $document->getAttribute('domain')) {
+ $domainUsingCertificate = $mainDomain;
+ }
+ }
+
+ // If certificate is still used by some domain, mark we can't delete.
+ // Current domain should not be found, because we only have copy. Original domain is already deleted from database.
+ if($domainUsingCertificate) {
+ Console::warning("Skipping certificate deletion, because a domain is still using it.");
+ return;
+ }
+ }
+
$domain = $document->getAttribute('domain');
$directory = APP_STORAGE_CERTIFICATES . '/' . $domain;
$checkTraversal = realpath($directory) === $directory;
if ($domain && $checkTraversal && is_dir($directory)) {
+ // Delete certificate document, so Appwrite is aware of change
+ if(isset($document['certificateId'])) {
+ $consoleDB->deleteDocument('certificates', $document['certificateId']);
+ }
+
+ // Delete files, so Traefik is aware of change
array_map('unlink', glob($directory . '/*.*'));
rmdir($directory);
Console::info("Deleted certificate files for {$domain}");
diff --git a/app/workers/mails.php b/app/workers/mails.php
index 25ddb438c0..6905593a8c 100644
--- a/app/workers/mails.php
+++ b/app/workers/mails.php
@@ -46,6 +46,16 @@ class MailsV1 extends Worker
$body = Template::fromFile(__DIR__ . '/../config/locale/templates/email-base.tpl');
$subject = '';
switch ($type) {
+ case MAIL_TYPE_CERTIFICATE:
+ $domain = $this->args['domain'];
+ $error = $this->args['error'];
+ $attempt = $this->args['attempt'];
+
+ $subject = \sprintf($locale->getText("$prefix.subject"), $domain);
+ $body->setParam('{{domain}}', $domain);
+ $body->setParam('{{error}}', $error);
+ $body->setParam('{{attempt}}', $attempt);
+ break;
case MAIL_TYPE_INVITATION:
$subject = \sprintf($locale->getText("$prefix.subject"), $this->args['team'], $project);
$body->setParam('{{owner}}', $this->args['owner']);
@@ -126,6 +136,8 @@ class MailsV1 extends Worker
switch ($type) {
case MAIL_TYPE_RECOVERY:
return 'emails.recovery';
+ case MAIL_TYPE_CERTIFICATE:
+ return 'emails.certificate';
case MAIL_TYPE_INVITATION:
return 'emails.invitation';
case MAIL_TYPE_VERIFICATION:
diff --git a/composer.lock b/composer.lock
index ea07e51fea..c2b51c52d9 100644
--- a/composer.lock
+++ b/composer.lock
@@ -2299,25 +2299,25 @@
},
{
"name": "utopia-php/image",
- "version": "0.5.3",
+ "version": "0.5.4",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/image.git",
- "reference": "4a8429b62dcf56562b038d6712375f75166f0c02"
+ "reference": "ca5f436f9aa22dedaa6648f24f3687733808e336"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/image/zipball/4a8429b62dcf56562b038d6712375f75166f0c02",
- "reference": "4a8429b62dcf56562b038d6712375f75166f0c02",
+ "url": "https://api.github.com/repos/utopia-php/image/zipball/ca5f436f9aa22dedaa6648f24f3687733808e336",
+ "reference": "ca5f436f9aa22dedaa6648f24f3687733808e336",
"shasum": ""
},
"require": {
"ext-imagick": "*",
- "php": ">=7.4"
+ "php": ">=8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.3",
- "vimeo/psalm": "4.0.1"
+ "vimeo/psalm": "4.13.1"
},
"type": "library",
"autoload": {
@@ -2345,9 +2345,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/image/issues",
- "source": "https://github.com/utopia-php/image/tree/0.5.3"
+ "source": "https://github.com/utopia-php/image/tree/0.5.4"
},
- "time": "2021-11-02T05:47:16+00:00"
+ "time": "2022-05-11T12:30:41+00:00"
},
{
"name": "utopia-php/locale",
@@ -3551,16 +3551,16 @@
},
{
"name": "matthiasmullie/minify",
- "version": "1.3.67",
+ "version": "1.3.68",
"source": {
"type": "git",
"url": "https://github.com/matthiasmullie/minify.git",
- "reference": "acaee1b7ca3cd67a39d7f98673cacd7e4739a8d9"
+ "reference": "c00fb02f71b2ef0a5f53fe18c5a8b9aa30f48297"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/acaee1b7ca3cd67a39d7f98673cacd7e4739a8d9",
- "reference": "acaee1b7ca3cd67a39d7f98673cacd7e4739a8d9",
+ "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/c00fb02f71b2ef0a5f53fe18c5a8b9aa30f48297",
+ "reference": "c00fb02f71b2ef0a5f53fe18c5a8b9aa30f48297",
"shasum": ""
},
"require": {
@@ -3609,7 +3609,7 @@
],
"support": {
"issues": "https://github.com/matthiasmullie/minify/issues",
- "source": "https://github.com/matthiasmullie/minify/tree/1.3.67"
+ "source": "https://github.com/matthiasmullie/minify/tree/1.3.68"
},
"funding": [
{
@@ -3617,7 +3617,7 @@
"type": "github"
}
],
- "time": "2022-03-24T08:54:59+00:00"
+ "time": "2022-04-19T08:28:56+00:00"
},
{
"name": "matthiasmullie/path-converter",
@@ -5711,16 +5711,16 @@
},
{
"name": "symfony/console",
- "version": "v6.0.7",
+ "version": "v6.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "70dcf7b2ca2ea08ad6ebcc475f104a024fb5632e"
+ "reference": "0d00aa289215353aa8746a31d101f8e60826285c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/70dcf7b2ca2ea08ad6ebcc475f104a024fb5632e",
- "reference": "70dcf7b2ca2ea08ad6ebcc475f104a024fb5632e",
+ "url": "https://api.github.com/repos/symfony/console/zipball/0d00aa289215353aa8746a31d101f8e60826285c",
+ "reference": "0d00aa289215353aa8746a31d101f8e60826285c",
"shasum": ""
},
"require": {
@@ -5786,7 +5786,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v6.0.7"
+ "source": "https://github.com/symfony/console/tree/v6.0.8"
},
"funding": [
{
@@ -5802,7 +5802,7 @@
"type": "tidelift"
}
],
- "time": "2022-03-31T17:18:25+00:00"
+ "time": "2022-04-20T15:01:42+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
@@ -6136,16 +6136,16 @@
},
{
"name": "symfony/string",
- "version": "v6.0.3",
+ "version": "v6.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2"
+ "reference": "ac0aa5c2282e0de624c175b68d13f2c8f2e2649d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/522144f0c4c004c80d56fa47e40e17028e2eefc2",
- "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2",
+ "url": "https://api.github.com/repos/symfony/string/zipball/ac0aa5c2282e0de624c175b68d13f2c8f2e2649d",
+ "reference": "ac0aa5c2282e0de624c175b68d13f2c8f2e2649d",
"shasum": ""
},
"require": {
@@ -6201,7 +6201,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v6.0.3"
+ "source": "https://github.com/symfony/string/tree/v6.0.8"
},
"funding": [
{
@@ -6217,7 +6217,7 @@
"type": "tidelift"
}
],
- "time": "2022-01-02T09:55:41+00:00"
+ "time": "2022-04-22T08:18:02+00:00"
},
{
"name": "textalk/websocket",
diff --git a/docker-compose.yml b/docker-compose.yml
index 0667f5afc5..1a00132914 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -389,6 +389,7 @@ services:
environment:
- _APP_ENV
- _APP_OPENSSL_KEY_V1
+ - _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
@@ -543,6 +544,11 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_ABUSE
diff --git a/public/images/users/vk.png b/public/images/users/vk.png
deleted file mode 100644
index e3cf2d74ef..0000000000
Binary files a/public/images/users/vk.png and /dev/null differ
diff --git a/src/Appwrite/Auth/OAuth2.php b/src/Appwrite/Auth/OAuth2.php
index 4eafba7610..99b40019ac 100644
--- a/src/Appwrite/Auth/OAuth2.php
+++ b/src/Appwrite/Auth/OAuth2.php
@@ -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);
diff --git a/src/Appwrite/Auth/OAuth2/Amazon.php b/src/Appwrite/Auth/OAuth2/Amazon.php
index 7ec2769c9a..2e72975fd1 100644
--- a/src/Appwrite/Auth/OAuth2/Amazon.php
+++ b/src/Appwrite/Auth/OAuth2/Amazon.php
@@ -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;
diff --git a/src/Appwrite/Auth/OAuth2/Apple.php b/src/Appwrite/Auth/OAuth2/Apple.php
index f1ff4b724d..1ea352e685 100644
--- a/src/Appwrite/Auth/OAuth2/Apple.php
+++ b/src/Appwrite/Auth/OAuth2/Apple.php
@@ -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);
}
}
diff --git a/src/Appwrite/Auth/OAuth2/Auth0.php b/src/Appwrite/Auth/OAuth2/Auth0.php
index b1c9c8ce1f..4775139a0b 100644
--- a/src/Appwrite/Auth/OAuth2/Auth0.php
+++ b/src/Appwrite/Auth/OAuth2/Auth0.php
@@ -8,27 +8,27 @@ use Appwrite\Auth\OAuth2;
// https://auth0.com/docs/api/authentication
class Auth0 extends OAuth2
-{
- /**
+{
+ /**
* @var array
*/
- protected $scopes = [
+ protected array $scopes = [
'openid',
'profile',
'email',
'offline_access'
];
-
+
/**
* @var array
*/
- protected $user = [];
-
+ protected array $user = [];
+
/**
* @var array
*/
- protected $tokens = [];
-
+ protected array $tokens = [];
+
/**
* @return string
*/
@@ -42,11 +42,11 @@ class Auth0 extends OAuth2
*/
public function getLoginURL(): string
{
- return 'https://'.$this->getAuth0Domain().'/authorize?'.\http_build_query([
+ 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()),
+ 'state' => \json_encode($this->state),
+ 'scope' => \implode(' ', $this->getScopes()),
'response_type' => 'code'
]);
}
@@ -58,11 +58,11 @@ class Auth0 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://'.$this->getAuth0Domain().'/oauth/token',
+ 'https://' . $this->getAuth0Domain() . '/oauth/token',
$headers,
\http_build_query([
'code' => $code,
@@ -77,8 +77,8 @@ class Auth0 extends OAuth2
return $this->tokens;
}
-
-
+
+
/**
* @param string $refreshToken
*
@@ -89,7 +89,7 @@ class Auth0 extends OAuth2
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
- 'https://'.$this->getAuth0Domain().'/oauth/token',
+ 'https://' . $this->getAuth0Domain() . '/oauth/token',
$headers,
\http_build_query([
'refresh_token' => $refreshToken,
@@ -99,7 +99,7 @@ class Auth0 extends OAuth2
])
), true);
- if(empty($this->tokens['refresh_token'])) {
+ if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -114,12 +114,8 @@ class Auth0 extends OAuth2
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
-
- if (isset($user['sub'])) {
- return $user['sub'];
- }
-
- return '';
+
+ return $user['sub'] ?? '';
}
/**
@@ -130,12 +126,28 @@ class Auth0 extends OAuth2
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
-
- if (isset($user['email'])) {
- return $user['email'];
+
+ 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 '';
+
+ return false;
}
/**
@@ -146,15 +158,11 @@ class Auth0 extends OAuth2
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
-
- if (isset($user['name'])) {
- return $user['name'];
- }
-
- return '';
+
+ return $user['name'] ?? '';
}
-
- /**
+
+ /**
* @param string $accessToken
*
* @return array
@@ -162,8 +170,8 @@ class Auth0 extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
- $headers = ['Authorization: Bearer '. \urlencode($accessToken)];
- $user = $this->request('GET', 'https://'.$this->getAuth0Domain().'/userinfo', $headers);
+ $headers = ['Authorization: Bearer ' . \urlencode($accessToken)];
+ $user = $this->request('GET', 'https://' . $this->getAuth0Domain() . '/userinfo', $headers);
$this->user = \json_decode($user, true);
}
@@ -179,10 +187,10 @@ class Auth0 extends OAuth2
{
$secret = $this->getAppSecret();
- return (isset($secret['clientSecret'])) ? $secret['clientSecret'] : '';
+ return $secret['clientSecret'] ?? '';
}
- /**
+ /**
* Extracts the Auth0 Domain from the JSON stored in appSecret
*
* @return string
@@ -190,7 +198,8 @@ class Auth0 extends OAuth2
protected function getAuth0Domain(): string
{
$secret = $this->getAppSecret();
- return (isset($secret['auth0Domain'])) ? $secret['auth0Domain'] : '';
+
+ return $secret['auth0Domain'] ?? '';
}
/**
@@ -199,7 +208,7 @@ class Auth0 extends OAuth2
* @return array
*/
protected function getAppSecret(): array
- {
+ {
try {
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
} catch (\Throwable $th) {
@@ -207,4 +216,4 @@ class Auth0 extends OAuth2
}
return $secret;
}
-}
\ No newline at end of file
+}
diff --git a/src/Appwrite/Auth/OAuth2/Bitbucket.php b/src/Appwrite/Auth/OAuth2/Bitbucket.php
index 74d684c845..bbefe144c1 100644
--- a/src/Appwrite/Auth/OAuth2/Bitbucket.php
+++ b/src/Appwrite/Auth/OAuth2/Bitbucket.php
@@ -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;
}
diff --git a/src/Appwrite/Auth/OAuth2/Bitly.php b/src/Appwrite/Auth/OAuth2/Bitly.php
index 02a21dc29e..08350bea6f 100644
--- a/src/Appwrite/Auth/OAuth2/Bitly.php
+++ b/src/Appwrite/Auth/OAuth2/Bitly.php
@@ -3,7 +3,6 @@
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
-use Utopia\Exception;
// Reference Material
// https://dev.bitly.com/v4_documentation.html
@@ -14,32 +13,32 @@ class Bitly extends OAuth2
/**
* @var string
*/
- private $endpoint = 'https://bitly.com/oauth/';
+ private string $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 +46,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 +63,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 +90,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 +108,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 +116,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 +181,7 @@ class Bitly extends OAuth2
protected function getUser(string $accessToken)
{
$headers = [
- 'Authorization: Bearer '. \urlencode($accessToken),
+ 'Authorization: Bearer ' . \urlencode($accessToken),
"Accept: application/json"
];
@@ -180,7 +189,6 @@ class Bitly extends OAuth2
$this->user = \json_decode($this->request('GET', $this->resourceEndpoint . "v4/user", $headers), true);
}
-
return $this->user;
}
}
diff --git a/src/Appwrite/Auth/OAuth2/Box.php b/src/Appwrite/Auth/OAuth2/Box.php
index 80d59e9198..da925eed1a 100644
--- a/src/Appwrite/Auth/OAuth2/Box.php
+++ b/src/Appwrite/Auth/OAuth2/Box.php
@@ -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(
diff --git a/src/Appwrite/Auth/OAuth2/Discord.php b/src/Appwrite/Auth/OAuth2/Discord.php
index dece646e55..7cf2ef1b7b 100644
--- a/src/Appwrite/Auth/OAuth2/Discord.php
+++ b/src/Appwrite/Auth/OAuth2/Discord.php
@@ -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 = [];
+ protected array $user = [];
/**
* @var array
*/
- protected $tokens = [];
+ protected array $tokens = [];
/**
* @var array
*/
- protected $scopes = [
+ protected array $scopes = [
'identify',
'email'
];
@@ -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'] ?? '';
}
/**
diff --git a/src/Appwrite/Auth/OAuth2/Dropbox.php b/src/Appwrite/Auth/OAuth2/Dropbox.php
index f67e10b01d..5025cb3dc8 100644
--- a/src/Appwrite/Auth/OAuth2/Dropbox.php
+++ b/src/Appwrite/Auth/OAuth2/Dropbox.php
@@ -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);
}
diff --git a/src/Appwrite/Auth/OAuth2/Facebook.php b/src/Appwrite/Auth/OAuth2/Facebook.php
index 7759a54042..555daedc58 100644
--- a/src/Appwrite/Auth/OAuth2/Facebook.php
+++ b/src/Appwrite/Auth/OAuth2/Facebook.php
@@ -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);
}
diff --git a/src/Appwrite/Auth/OAuth2/Github.php b/src/Appwrite/Auth/OAuth2/Github.php
index dddd4a5181..1130134f68 100644
--- a/src/Appwrite/Auth/OAuth2/Github.php
+++ b/src/Appwrite/Auth/OAuth2/Github.php
@@ -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;
diff --git a/src/Appwrite/Auth/OAuth2/Gitlab.php b/src/Appwrite/Auth/OAuth2/Gitlab.php
index 19ee1363a7..fa0a93df92 100644
--- a/src/Appwrite/Auth/OAuth2/Gitlab.php
+++ b/src/Appwrite/Auth/OAuth2/Gitlab.php
@@ -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 'https://gitlab.com/oauth/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
@@ -55,7 +55,7 @@ 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([
@@ -76,7 +76,7 @@ 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',
@@ -88,7 +88,7 @@ class Gitlab extends OAuth2
])
), 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,7 +163,7 @@ 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', 'https://gitlab.com/api/v4/user?access_token=' . \urlencode($accessToken));
$this->user = \json_decode($user, true);
}
diff --git a/src/Appwrite/Auth/OAuth2/Google.php b/src/Appwrite/Auth/OAuth2/Google.php
index 5b3ab938b3..e675a1f861 100644
--- a/src/Appwrite/Auth/OAuth2/Google.php
+++ b/src/Appwrite/Auth/OAuth2/Google.php
@@ -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['id'] ?? '';
}
/**
@@ -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);
}
diff --git a/src/Appwrite/Auth/OAuth2/Linkedin.php b/src/Appwrite/Auth/OAuth2/Linkedin.php
index 2519859a1b..3ada765319 100644
--- a/src/Appwrite/Auth/OAuth2/Linkedin.php
+++ b/src/Appwrite/Auth/OAuth2/Linkedin.php
@@ -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;
diff --git a/src/Appwrite/Auth/OAuth2/Microsoft.php b/src/Appwrite/Auth/OAuth2/Microsoft.php
index ebfd2e4e83..84f128a08e 100644
--- a/src/Appwrite/Auth/OAuth2/Microsoft.php
+++ b/src/Appwrite/Auth/OAuth2/Microsoft.php
@@ -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,7 +58,7 @@ 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',
@@ -83,7 +83,7 @@ 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(
@@ -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);
}
@@ -175,7 +179,7 @@ class Microsoft extends OAuth2
* @return array
*/
protected function getAppSecret(): array
- {
+ {
try {
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
} catch (\Throwable $th) {
@@ -192,7 +196,8 @@ class Microsoft extends OAuth2
protected function getClientSecret(): string
{
$secret = $this->getAppSecret();
- return (isset($secret['clientSecret'])) ? $secret['clientSecret'] : '';
+
+ return $secret['clientSecret'] ?? '';
}
/**
@@ -203,6 +208,7 @@ class Microsoft extends OAuth2
protected function getTenantID(): string
{
$secret = $this->getAppSecret();
- return (isset($secret['tenantID'])) ? $secret['tenantID'] : 'common';
+
+ return $secret['tenantID'] ?? 'common';
}
}
diff --git a/src/Appwrite/Auth/OAuth2/Mock.php b/src/Appwrite/Auth/OAuth2/Mock.php
index aef92dac23..f80287947e 100644
--- a/src/Appwrite/Auth/OAuth2/Mock.php
+++ b/src/Appwrite/Auth/OAuth2/Mock.php
@@ -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);
}
diff --git a/src/Appwrite/Auth/OAuth2/Notion.php b/src/Appwrite/Auth/OAuth2/Notion.php
index 5c117caa3c..5d9d75bffd 100644
--- a/src/Appwrite/Auth/OAuth2/Notion.php
+++ b/src/Appwrite/Auth/OAuth2/Notion.php
@@ -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)) {
diff --git a/src/Appwrite/Auth/OAuth2/Okta.php b/src/Appwrite/Auth/OAuth2/Okta.php
index 7b1b0d19e1..3de3df96d5 100644
--- a/src/Appwrite/Auth/OAuth2/Okta.php
+++ b/src/Appwrite/Auth/OAuth2/Okta.php
@@ -8,27 +8,27 @@ use Appwrite\Auth\OAuth2;
// https://developer.okta.com/docs/guides/sign-into-web-app-redirect/php/main/
class Okta extends OAuth2
-{
- /**
+{
+ /**
* @var array
*/
- protected $scopes = [
+ protected array $scopes = [
'openid',
'profile',
'email',
'offline_access'
];
-
+
/**
* @var array
*/
- protected $user = [];
-
+ protected array $user = [];
+
/**
* @var array
*/
- protected $tokens = [];
-
+ protected array $tokens = [];
+
/**
* @return string
*/
@@ -42,11 +42,11 @@ class Okta extends OAuth2
*/
public function getLoginURL(): string
{
- return 'https://'.$this->getOktaDomain().'/oauth2/'.$this->getAuthorizationServerId().'/v1/authorize?'.\http_build_query([
+ 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()),
+ 'state' => \json_encode($this->state),
+ 'scope' => \implode(' ', $this->getScopes()),
'response_type' => 'code'
]);
}
@@ -58,11 +58,11 @@ class Okta 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://'.$this->getOktaDomain().'/oauth2/'.$this->getAuthorizationServerId().'/v1/token',
+ 'https://' . $this->getOktaDomain() . '/oauth2/' . $this->getAuthorizationServerId() . '/v1/token',
$headers,
\http_build_query([
'code' => $code,
@@ -77,8 +77,8 @@ class Okta extends OAuth2
return $this->tokens;
}
-
-
+
+
/**
* @param string $refreshToken
*
@@ -89,7 +89,7 @@ class Okta extends OAuth2
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
- 'https://'.$this->getOktaDomain().'/oauth2/'.$this->getAuthorizationServerId().'/v1/token',
+ 'https://' . $this->getOktaDomain() . '/oauth2/' . $this->getAuthorizationServerId() . '/v1/token',
$headers,
\http_build_query([
'refresh_token' => $refreshToken,
@@ -99,7 +99,7 @@ class Okta extends OAuth2
])
), true);
- if(empty($this->tokens['refresh_token'])) {
+ if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -114,12 +114,8 @@ class Okta extends OAuth2
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
-
- if (isset($user['sub'])) {
- return $user['sub'];
- }
-
- return '';
+
+ return $user['sub'] ?? '';
}
/**
@@ -130,12 +126,28 @@ class Okta extends OAuth2
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
-
- if (isset($user['email'])) {
- return $user['email'];
+
+ 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 '';
+
+ return false;
}
/**
@@ -146,15 +158,11 @@ class Okta extends OAuth2
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
-
- if (isset($user['name'])) {
- return $user['name'];
- }
-
- return '';
+
+ return $user['name'] ?? '';
}
-
- /**
+
+ /**
* @param string $accessToken
*
* @return array
@@ -162,8 +170,8 @@ class Okta extends OAuth2
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);
+ $headers = ['Authorization: Bearer ' . \urlencode($accessToken)];
+ $user = $this->request('GET', 'https://' . $this->getOktaDomain() . '/oauth2/' . $this->getAuthorizationServerId() . '/v1/userinfo', $headers);
$this->user = \json_decode($user, true);
}
@@ -179,10 +187,10 @@ class Okta extends OAuth2
{
$secret = $this->getAppSecret();
- return (isset($secret['clientSecret'])) ? $secret['clientSecret'] : '';
+ return $secret['clientSecret'] ?? '';
}
- /**
+ /**
* Extracts the Okta Domain from the JSON stored in appSecret
*
* @return string
@@ -190,7 +198,8 @@ class Okta extends OAuth2
protected function getOktaDomain(): string
{
$secret = $this->getAppSecret();
- return (isset($secret['oktaDomain'])) ? $secret['oktaDomain'] : '';
+
+ return $secret['oktaDomain'] ?? '';
}
/**
@@ -201,7 +210,8 @@ class Okta extends OAuth2
protected function getAuthorizationServerId(): string
{
$secret = $this->getAppSecret();
- return (isset($secret['authorizationServerId'])) ? $secret['authorizationServerId'] : 'default';
+
+ return $secret['authorizationServerId'] ?? 'default';
}
/**
@@ -210,7 +220,7 @@ class Okta extends OAuth2
* @return array
*/
protected function getAppSecret(): array
- {
+ {
try {
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
} catch (\Throwable $th) {
diff --git a/src/Appwrite/Auth/OAuth2/Paypal.php b/src/Appwrite/Auth/OAuth2/Paypal.php
index 39544c6bd8..74f4291594 100644
--- a/src/Appwrite/Auth/OAuth2/Paypal.php
+++ b/src/Appwrite/Auth/OAuth2/Paypal.php
@@ -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(
diff --git a/src/Appwrite/Auth/OAuth2/PaypalSandbox.php b/src/Appwrite/Auth/OAuth2/PaypalSandbox.php
index 82698555d4..0f56a09c21 100644
--- a/src/Appwrite/Auth/OAuth2/PaypalSandbox.php
+++ b/src/Appwrite/Auth/OAuth2/PaypalSandbox.php
@@ -6,7 +6,7 @@ use Appwrite\Auth\OAuth2\Paypal;
class PaypalSandbox extends Paypal
{
- protected $environment = 'sandbox';
+ protected string $environment = 'sandbox';
/**
* @return string
diff --git a/src/Appwrite/Auth/OAuth2/Salesforce.php b/src/Appwrite/Auth/OAuth2/Salesforce.php
index 04ef1901b5..636c3f4c3c 100644
--- a/src/Appwrite/Auth/OAuth2/Salesforce.php
+++ b/src/Appwrite/Auth/OAuth2/Salesforce.php
@@ -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;
diff --git a/src/Appwrite/Auth/OAuth2/Slack.php b/src/Appwrite/Auth/OAuth2/Slack.php
index 92c210151b..c8adfdd697 100644
--- a/src/Appwrite/Auth/OAuth2/Slack.php
+++ b/src/Appwrite/Auth/OAuth2/Slack.php
@@ -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);
diff --git a/src/Appwrite/Auth/OAuth2/Spotify.php b/src/Appwrite/Auth/OAuth2/Spotify.php
index 5c1a43299a..98f4226d84 100644
--- a/src/Appwrite/Auth/OAuth2/Spotify.php
+++ b/src/Appwrite/Auth/OAuth2/Spotify.php
@@ -13,34 +13,34 @@ class Spotify extends OAuth2
/**
* @var string
*/
- private $endpoint = 'https://accounts.spotify.com/';
+ private string $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 +48,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 +67,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 +89,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 +102,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 +110,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 +171,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);
}
diff --git a/src/Appwrite/Auth/OAuth2/Stripe.php b/src/Appwrite/Auth/OAuth2/Stripe.php
index 589e6b7b81..e2ca3a92ba 100644
--- a/src/Appwrite/Auth/OAuth2/Stripe.php
+++ b/src/Appwrite/Auth/OAuth2/Stripe.php
@@ -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;
diff --git a/src/Appwrite/Auth/OAuth2/Tradeshift.php b/src/Appwrite/Auth/OAuth2/Tradeshift.php
index 38b58432a0..8fdcde5f29 100644
--- a/src/Appwrite/Auth/OAuth2/Tradeshift.php
+++ b/src/Appwrite/Auth/OAuth2/Tradeshift.php
@@ -12,35 +12,37 @@ class Tradeshift extends OAuth2
const TRADESHIFT_SANDBOX_API_DOMAIN = 'api-sandbox.tradeshift.com';
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
*
diff --git a/src/Appwrite/Auth/OAuth2/TradeshiftBox.php b/src/Appwrite/Auth/OAuth2/TradeshiftBox.php
index 6ba3c29f0a..27a4c0a456 100644
--- a/src/Appwrite/Auth/OAuth2/TradeshiftBox.php
+++ b/src/Appwrite/Auth/OAuth2/TradeshiftBox.php
@@ -6,7 +6,7 @@ use Appwrite\Auth\OAuth2\Tradeshift;
class TradeshiftBox extends Tradeshift
{
- protected $environment = 'sandbox';
+ protected string $environment = 'sandbox';
/**
* @return string
diff --git a/src/Appwrite/Auth/OAuth2/Twitch.php b/src/Appwrite/Auth/OAuth2/Twitch.php
index 0deeb088e2..04e542ffb9 100644
--- a/src/Appwrite/Auth/OAuth2/Twitch.php
+++ b/src/Appwrite/Auth/OAuth2/Twitch.php
@@ -13,34 +13,34 @@ class Twitch extends OAuth2
/**
* @var string
*/
- private $endpoint = 'https://id.twitch.tv/oauth2/';
+ private string $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 +48,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 +68,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 +89,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 +101,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 +109,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 +174,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);
diff --git a/src/Appwrite/Auth/OAuth2/Vk.php b/src/Appwrite/Auth/OAuth2/Vk.php
deleted file mode 100644
index 5e54b14394..0000000000
--- a/src/Appwrite/Auth/OAuth2/Vk.php
+++ /dev/null
@@ -1,191 +0,0 @@
- $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;
- }
-}
diff --git a/src/Appwrite/Auth/OAuth2/WordPress.php b/src/Appwrite/Auth/OAuth2/WordPress.php
index 5b957d9495..6c1aade1de 100644
--- a/src/Appwrite/Auth/OAuth2/WordPress.php
+++ b/src/Appwrite/Auth/OAuth2/WordPress.php
@@ -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
- *
- * @return string
+ * Check if the OAuth email is verified
+ *
+ * @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;
diff --git a/src/Appwrite/Auth/OAuth2/Yahoo.php b/src/Appwrite/Auth/OAuth2/Yahoo.php
index ffefbe9c63..d8abbbfd69 100644
--- a/src/Appwrite/Auth/OAuth2/Yahoo.php
+++ b/src/Appwrite/Auth/OAuth2/Yahoo.php
@@ -13,17 +13,17 @@ class Yahoo extends OAuth2
/**
* @var string
*/
- private $endpoint = 'https://api.login.yahoo.com/oauth2/';
+ private string $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 +31,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 +60,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 +79,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 +105,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 +122,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 +130,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 +192,7 @@ class Yahoo extends OAuth2
$this->user = \json_decode($this->request(
'GET',
$this->resourceEndpoint,
- ['Authorization: Bearer '.\urlencode($accessToken)]
+ ['Authorization: Bearer ' . \urlencode($accessToken)]
), true);
}
diff --git a/src/Appwrite/Auth/OAuth2/Yammer.php b/src/Appwrite/Auth/OAuth2/Yammer.php
index 6e9f4fb86b..80bd44b244 100644
--- a/src/Appwrite/Auth/OAuth2/Yammer.php
+++ b/src/Appwrite/Auth/OAuth2/Yammer.php
@@ -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);
}
diff --git a/src/Appwrite/Auth/OAuth2/Yandex.php b/src/Appwrite/Auth/OAuth2/Yandex.php
index 1f6dda0d1b..efab408275 100644
--- a/src/Appwrite/Auth/OAuth2/Yandex.php
+++ b/src/Appwrite/Auth/OAuth2/Yandex.php
@@ -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
]));
diff --git a/src/Appwrite/Auth/OAuth2/Zoom.php b/src/Appwrite/Auth/OAuth2/Zoom.php
index 9aa77ceb91..ee4185934c 100644
--- a/src/Appwrite/Auth/OAuth2/Zoom.php
+++ b/src/Appwrite/Auth/OAuth2/Zoom.php
@@ -9,34 +9,34 @@ class Zoom extends OAuth2
/**
* @var string
*/
- private $endpoint = 'https://zoom.us';
+ private string $endpoint = 'https://zoom.us';
/**
* @var string
*/
- private $version = '2022-03-26';
+ private string $version = '2022-03-26';
/**
* @var array
*/
- protected $user = [];
-
- /**
- * @var array
- */
- protected $tokens = [];
+ protected array $user = [];
/**
* @var array
*/
- protected $scopes = [
+ protected array $tokens = [];
+
+ /**
+ * @var array
+ */
+ protected array $scopes = [
'user_profile'
];
/**
* @return string
*/
- public function getName():string
+ public function getName(): string
{
return 'zoom';
}
@@ -44,9 +44,9 @@ class Zoom 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',
@@ -62,7 +62,7 @@ class Zoom 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'];
$this->tokens = \json_decode($this->request(
'POST',
@@ -84,7 +84,7 @@ class Zoom 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), 'Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
@@ -97,7 +97,7 @@ class Zoom extends OAuth2
])
), true);
- if(empty($this->tokens['refresh_token'])) {
+ if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@@ -105,35 +105,58 @@ class Zoom 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);
+
return $response['id'] ?? '';
}
/**
- * @param $accessToken
+ * @param string $accessToken
*
* @return string
*/
- public function getUserEmail(string $accessToken):string
+ public function getUserEmail(string $accessToken): string
{
$response = $this->getUser($accessToken);
+
return $response['email'] ?? '';
}
/**
- * @param $accessToken
+ * 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
+ public function getUserName(string $accessToken): string
{
$response = $this->getUser($accessToken);
+
return ($response['first_name'] ?? '') . ' ' . ($response['last_name'] ?? '');
}
@@ -145,7 +168,7 @@ class Zoom extends OAuth2
protected function getUser(string $accessToken)
{
$headers = [
- 'Authorization: Bearer '.\urlencode($accessToken)
+ 'Authorization: Bearer ' . \urlencode($accessToken)
];
if (empty($this->user)) {
diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php
index 1099190cb6..95c51ed991 100644
--- a/src/Appwrite/Extend/Exception.php
+++ b/src/Appwrite/Extend/Exception.php
@@ -46,6 +46,7 @@ class Exception extends \Exception
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';
+ const GENERAL_PROTOCOL_UNSUPPORTED = 'general_protocol_unsupported';
/** Users */
const USER_COUNT_EXCEEDED = 'user_count_exceeded';
diff --git a/src/Appwrite/Network/Validator/Host.php b/src/Appwrite/Network/Validator/Host.php
index 703907c3d3..c81a931f37 100644
--- a/src/Appwrite/Network/Validator/Host.php
+++ b/src/Appwrite/Network/Validator/Host.php
@@ -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);
}
/**
diff --git a/src/Appwrite/Network/Validator/Origin.php b/src/Appwrite/Network/Validator/Origin.php
index 8831707cef..30efe50c4c 100644
--- a/src/Appwrite/Network/Validator/Origin.php
+++ b/src/Appwrite/Network/Validator/Origin.php
@@ -2,6 +2,7 @@
namespace Appwrite\Network\Validator;
+use Utopia\Validator\Hostname;
use Utopia\Validator;
class Origin extends Validator
@@ -122,11 +123,9 @@ class Origin extends Validator
return true;
}
- if (\in_array($host, $this->clients)) {
- return true;
- }
-
- return false;
+ $validator = new Hostname($this->clients);
+
+ return $validator->isValid($host);
}
/**
diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php
index 4796270d68..525c4144db 100644
--- a/tests/e2e/Services/Storage/StorageBase.php
+++ b/tests/e2e/Services/Storage/StorageBase.php
@@ -188,6 +188,24 @@ trait StorageBase
$this->assertEquals('File extension not allowed', $res['body']['message']);
return ['bucketId' => $bucketId, 'fileId' => $file['body']['$id'], 'largeFileId' => $largeFile['body']['$id'], 'largeBucketId' => $bucket2['body']['$id']];
+
+ /**
+ * Test for FAILURE create bucket with too high limit (bigger then _APP_STORAGE_LIMIT)
+ */
+ $failedBucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey'],
+ ], [
+ 'bucketId' => 'unique()',
+ 'name' => 'Test Bucket 2',
+ 'permission' => 'file',
+ 'maximumFileSize' => 200000000, //200MB
+ 'allowedFileExtensions' => ["jpg", "png"],
+ 'read' => ['role:all'],
+ 'write' => ['role:all'],
+ ]);
+ $this->assertEquals(400, $failedBucket['headers']['status-code']);
}
/**
diff --git a/tests/e2e/Services/Teams/TeamsBaseServer.php b/tests/e2e/Services/Teams/TeamsBaseServer.php
index 41dcc6c84c..5db4b628f6 100644
--- a/tests/e2e/Services/Teams/TeamsBaseServer.php
+++ b/tests/e2e/Services/Teams/TeamsBaseServer.php
@@ -204,7 +204,7 @@ trait TeamsBaseServer
$this->assertEquals(1, $response['body']['total']);
$this->assertIsInt($response['body']['total']);
$this->assertIsInt($response['body']['dateCreated']);
-
+
/** Delete User */
$user = $this->client->call(Client::METHOD_DELETE, '/users/' . $userUid, array_merge([