1
0
Fork 0
mirror of synced 2024-06-26 18:20:43 +12:00

Merge branch '0.14.x' of https://github.com/appwrite/appwrite into feat-use-build-timeout

This commit is contained in:
Torsten Dittmann 2022-05-12 19:18:31 +02:00
commit 4508158fe5
60 changed files with 1911 additions and 1454 deletions

View file

@ -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();

View file

@ -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],
],
],
],

View file

@ -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 => [

View file

@ -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",

View file

@ -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/',

View file

@ -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);

View file

@ -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'),
]);

View file

@ -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');

View file

@ -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());

View file

@ -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))

View file

@ -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());
}

View file

@ -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;

View file

@ -1,10 +1,42 @@
<?php
global $cli;
global $register;
use Appwrite\Event\Event;
use Utopia\App;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Database\Adapter\MariaDB;
use Utopia\Database\Database;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\Database\Query;
function getConsoleDB(): Database
{
global $register;
$attempts = 0;
do {
try {
$attempts++;
$cache = new Cache(new RedisCache($register->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);
});

View file

@ -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
]);
});

View file

@ -299,8 +299,9 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<label for="name">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different apps."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" name="name" required autocomplete="off" placeholder="My Web App" maxlength="128" />
<label for="hostname">Hostname <span class="tooltip large" data-tooltip="The hostname that your website will use to interact with the <?php echo APP_NAME; ?> APIs in production or development environments. No port number required."><i class="icon-question"></i></span></label>
<input name="hostname" type="text" class="margin-bottom" autocomplete="off" placeholder="localhost" required>
<label for="hostname">Hostname <span class="tooltip large" data-tooltip="The hostname that your website will use to interact with the <?php echo APP_NAME; ?> APIs in production or development environments. No protocol or port number required."><i class="icon-question"></i></span></label>
<input name="hostname" type="text" class="margin-bottom" autocomplete="off" placeholder="yourapp.com" required>
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">You can use * to allow wildcard hostnames or subdomains.</div>
<div class="info margin-top margin-bottom">
<div class="text-bold margin-bottom-small">Next Steps</div>
@ -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);
<input type="text" class="full-width" data-ls-attrs="id=name-{{platform.$id}}" name="name" required autocomplete="off" data-ls-bind="{{platform.name}}" placeholder="My Web App" maxlength="128" />
<label for="hostname">Hostname <span class="tooltip large" data-tooltip="The hostname that your website will use to interact with the <?php echo APP_NAME; ?> APIs in production or development environments. No port number required."><i class="icon-question"></i></span></label>
<input name="hostname" type="text" class="margin-bottom" autocomplete="off" placeholder="localhost" data-ls-bind="{{platform.hostname}}" required />
<input name="hostname" type="text" class="margin-bottom" autocomplete="off" placeholder="yourapp.com" data-ls-bind="{{platform.hostname}}" required />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">You can use * to allow wildcard hostnames or subdomains.</div>
<hr />
@ -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">

View file

@ -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

View file

@ -1,9 +1,11 @@
<?php
use Appwrite\Event\Event;
use Appwrite\Network\Validator\CNAME;
use Appwrite\Resque\Worker;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
@ -16,182 +18,393 @@ Console::success(APP_NAME . ' certificates worker v1 has started');
class CertificatesV1 extends Worker
{
/**
* Database connection shared across all methods of this file
*
* @var Database
*/
private Database $dbForConsole;
public function getName(): string {
return "certificates";
}
public function init(): void
{
}
public function run(): void
{
$dbForConsole = $this->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'));
}
}
}
}

View file

@ -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}");

View file

@ -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:

52
composer.lock generated
View file

@ -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",

View file

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -7,27 +7,27 @@ abstract class OAuth2
/**
* @var string
*/
protected $appID;
protected string $appID;
/**
* @var string
*/
protected $appSecret;
protected string $appSecret;
/**
* @var string
*/
protected $callback;
protected string $callback;
/**
* @var array
*/
protected $state;
protected array $state;
/**
* @var array
*/
protected $scopes;
protected array $scopes;
/**
* OAuth2 constructor.
@ -52,66 +52,69 @@ abstract class OAuth2
/**
* @return string
*/
abstract public function getName():string;
abstract public function getName(): string;
/**
* @return string
*/
abstract public function getLoginURL():string;
abstract public function getLoginURL(): string;
/**
* @param string $code
*
* @return array
*/
abstract protected function getTokens(string $code):array;
abstract protected function getTokens(string $code): array;
/**
* @param string $refreshToken
*
* @return array
*/
abstract public function refreshTokens(string $refreshToken):array;
abstract public function refreshTokens(string $refreshToken): array;
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
abstract public function getUserID(string $accessToken):string;
abstract public function getUserEmail(string $accessToken): string;
/**
* @param $accessToken
* Check if the OAuth email is verified
*
* @param string $accessToken
*
* @return bool
*/
abstract public function isEmailVerified(string $accessToken): bool;
/**
* @param string $accessToken
*
* @return string
*/
abstract public function getUserEmail(string $accessToken):string;
/**
* @param $accessToken
*
* @return string
*/
abstract public function getUserName(string $accessToken):string;
abstract public function getUserName(string $accessToken): string;
/**
* @param $scope
*
* @return $this
*/
protected function addScope(string $scope):OAuth2
protected function addScope(string $scope): OAuth2
{
// Add a scope to the scopes array if it isn't already present
if (!\in_array($scope, $this->scopes)) {
$this->scopes[] = $scope;
}
return $this;
}
/**
* @return array
*/
protected function getScopes():array
protected function getScopes(): array
{
return $this->scopes;
}
@ -121,9 +124,10 @@ abstract class OAuth2
*
* @return string
*/
public function getAccessToken(string $code):string
public function getAccessToken(string $code): string
{
$tokens = $this->getTokens($code);
return $tokens['access_token'] ?? '';
}
@ -132,9 +136,10 @@ abstract class OAuth2
*
* @return string
*/
public function getRefreshToken(string $code):string
public function getRefreshToken(string $code): string
{
$tokens = $this->getTokens($code);
return $tokens['refresh_token'] ?? '';
}
@ -143,9 +148,10 @@ abstract class OAuth2
*
* @return string
*/
public function getAccessTokenExpiry(string $code):string
public function getAccessTokenExpiry(string $code): string
{
$tokens = $this->getTokens($code);
return $tokens['expires_in'] ?? '';
}
@ -170,7 +176,7 @@ abstract class OAuth2
*
* @return string
*/
protected function request(string $method, string $url = '', array $headers = [], string $payload = ''):string
protected function request(string $method, string $url = '', array $headers = [], string $payload = ''): string
{
$ch = \curl_init($url);

View file

@ -14,17 +14,17 @@ class Amazon extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
"profile"
];
@ -37,7 +37,7 @@ class Amazon extends OAuth2
}
/**
* @param $state
* @param string $state
*
* @return array
*/
@ -52,13 +52,13 @@ class Amazon extends OAuth2
*/
public function getLoginURL(): string
{
return 'https://www.amazon.com/ap/oa?'.\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
'redirect_uri' => $this->callback
]);
return 'https://www.amazon.com/ap/oa?' . \http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
'redirect_uri' => $this->callback
]);
}
/**
@ -68,7 +68,7 @@ class Amazon extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded;charset=UTF-8'];
$this->tokens = \json_decode($this->request(
'POST',
@ -92,7 +92,7 @@ class Amazon extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded;charset=UTF-8'];
$this->tokens = \json_decode($this->request(
@ -107,7 +107,7 @@ class Amazon extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -123,11 +123,7 @@ class Amazon extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['user_id'])) {
return $user['user_id'];
}
return '';
return $user['user_id'] ?? '';
}
/**
@ -139,11 +135,23 @@ class Amazon extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return $user['email'] ?? '';
}
return '';
/**
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Amazon sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
@ -155,11 +163,7 @@ class Amazon extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@ -170,7 +174,7 @@ class Amazon extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request('GET', 'https://api.amazon.com/user/profile?access_token='.\urlencode($accessToken));
$user = $this->request('GET', 'https://api.amazon.com/user/profile?access_token=' . \urlencode($accessToken));
$this->user = \json_decode($user, true);
}
return $this->user;

View file

@ -13,17 +13,17 @@ class Apple extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
"name",
"email"
];
@ -31,7 +31,7 @@ class Apple extends OAuth2
/**
* @var array
*/
protected $claims = [];
protected array $claims = [];
/**
* @return string
@ -40,13 +40,13 @@ class Apple extends OAuth2
{
return 'apple';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://appleid.apple.com/auth/authorize?'.\http_build_query([
return 'https://appleid.apple.com/auth/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state),
@ -63,7 +63,7 @@ class Apple extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
@ -90,7 +90,7 @@ class Apple extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
@ -105,7 +105,7 @@ class Apple extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -122,11 +122,7 @@ class Apple extends OAuth2
*/
public function getUserID(string $accessToken): string
{
if (isset($this->claims['sub']) && !empty($this->claims['sub'])) {
return $this->claims['sub'];
}
return '';
return $this->claims['sub'] ?? '';
}
/**
@ -136,14 +132,25 @@ class Apple extends OAuth2
*/
public function getUserEmail(string $accessToken): string
{
if (isset($this->claims['email']) &&
!empty($this->claims['email']) &&
isset($this->claims['email_verified']) &&
$this->claims['email_verified'] === 'true') {
return $this->claims['email'];
return $this->claims['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://developer.apple.com/forums/thread/121411
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
if ($this->claims['email_verified'] ?? false) {
return true;
}
return '';
return false;
}
/**
@ -153,17 +160,19 @@ class Apple extends OAuth2
*/
public function getUserName(string $accessToken): string
{
if (isset($this->claims['email']) &&
if (
isset($this->claims['email']) &&
!empty($this->claims['email']) &&
isset($this->claims['email_verified']) &&
$this->claims['email_verified'] === 'true') {
$this->claims['email_verified'] === 'true'
) {
return $this->claims['email'];
}
return '';
}
protected function getAppSecret():string
protected function getAppSecret(): string
{
try {
$secret = \json_decode($this->appSecret, true);
@ -180,18 +189,18 @@ class Apple extends OAuth2
'alg' => 'ES256',
'kid' => $keyID,
];
$claims = [
'iss' => $teamID,
'iat' => \time(),
'exp' => \time() + 86400*180,
'exp' => \time() + 86400 * 180,
'aud' => 'https://appleid.apple.com',
'sub' => $bundleID,
];
$pkey = \openssl_pkey_get_private($keyfile);
$payload = $this->encode(\json_encode($headers)).'.'.$this->encode(\json_encode($claims));
$payload = $this->encode(\json_encode($headers)) . '.' . $this->encode(\json_encode($claims));
$signature = '';
@ -201,7 +210,7 @@ class Apple extends OAuth2
return '';
}
return $payload.'.'.$this->encode($this->fromDER($signature, 64));
return $payload . '.' . $this->encode($this->fromDER($signature, 64));
}
/**
@ -230,10 +239,10 @@ class Apple extends OAuth2
* @param string $der
* @param int $partLength
*/
protected function fromDER(string $der, int $partLength):string
protected function fromDER(string $der, int $partLength): string
{
$hex = \unpack('H*', $der)[1];
if ('30' !== \mb_substr($hex, 0, 2, '8bit')) { // SEQUENCE
throw new \RuntimeException();
}
@ -252,7 +261,7 @@ class Apple extends OAuth2
$R = \str_pad($R, $partLength, '0', STR_PAD_LEFT);
$hex = \mb_substr($hex, 4 + $Rl * 2, null, '8bit');
if ('02' !== \mb_substr($hex, 0, 2, '8bit')) { // INTEGER
throw new \RuntimeException();
}
@ -261,6 +270,6 @@ class Apple extends OAuth2
$S = $this->retrievePositiveInteger(\mb_substr($hex, 4, $Sl * 2, '8bit'));
$S = \str_pad($S, $partLength, '0', STR_PAD_LEFT);
return \pack('H*', $R.$S);
return \pack('H*', $R . $S);
}
}

View file

@ -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;
}
}
}

View file

@ -12,17 +12,17 @@ class Bitbucket extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [];
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [];
/**
* @return string
@ -37,12 +37,12 @@ class Bitbucket extends OAuth2
*/
public function getLoginURL(): string
{
return 'https://bitbucket.org/site/oauth2/authorize?'.\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
]);
return 'https://bitbucket.org/site/oauth2/authorize?' . \http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
]);
}
/**
@ -52,7 +52,7 @@ class Bitbucket extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
// Required as per Bitbucket Spec.
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
@ -76,7 +76,7 @@ class Bitbucket extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
@ -91,7 +91,7 @@ class Bitbucket extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -107,11 +107,7 @@ class Bitbucket extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['uuid'])) {
return $user['uuid'];
}
return '';
return $user['uuid'] ?? '';
}
/**
@ -123,11 +119,25 @@ class Bitbucket extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['is_confirmed'] ?? false) {
return true;
}
return '';
return false;
}
/**
@ -139,11 +149,7 @@ class Bitbucket extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['display_name'])) {
return $user['display_name'];
}
return '';
return $user['display_name'] ?? '';
}
/**
@ -154,11 +160,20 @@ class Bitbucket extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request('GET', 'https://api.bitbucket.org/2.0/user?access_token='.\urlencode($accessToken));
$user = $this->request('GET', 'https://api.bitbucket.org/2.0/user?access_token=' . \urlencode($accessToken));
$this->user = \json_decode($user, true);
$email = $this->request('GET', 'https://api.bitbucket.org/2.0/user/emails?access_token='.\urlencode($accessToken));
$this->user['email'] = \json_decode($email, true)['values'][0]['email'];
$emails = $this->request('GET', 'https://api.bitbucket.org/2.0/user/emails?access_token=' . \urlencode($accessToken));
$emails = \json_decode($emails, true);
if (isset($emails['values'])) {
foreach ($emails['values'] as $email) {
if ($email['is_confirmed']) {
$this->user['email'] = $email['email'];
$this->user['is_confirmed'] = $email['is_confirmed'];
break;
}
}
}
}
return $this->user;
}

View file

@ -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;
}
}

View file

@ -12,27 +12,27 @@ class Box extends OAuth2
/**
* @var string
*/
private $endpoint = 'https://account.box.com/api/oauth2/';
private string $endpoint = 'https://account.box.com/api/oauth2/';
/**
* @var string
*/
private $resourceEndpoint = 'https://api.box.com/2.0/';
private string $resourceEndpoint = 'https://api.box.com/2.0/';
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'manage_app_users',
];
@ -49,7 +49,7 @@ class Box extends OAuth2
*/
public function getLoginURL(): string
{
$url = $this->endpoint . 'authorize?'.
$url = $this->endpoint . 'authorize?' .
\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
@ -68,7 +68,7 @@ class Box extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
@ -93,7 +93,7 @@ class Box extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
@ -108,7 +108,7 @@ class Box extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -124,11 +124,7 @@ class Box extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
@ -140,11 +136,23 @@ class Box extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['login'])) {
return $user['login'];
}
return $user['login'] ?? '';
}
return '';
/**
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Box sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
@ -156,11 +164,7 @@ class Box extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@ -171,7 +175,7 @@ class Box extends OAuth2
protected function getUser(string $accessToken): array
{
$header = [
'Authorization: Bearer '.\urlencode($accessToken),
'Authorization: Bearer ' . \urlencode($accessToken),
];
if (empty($this->user)) {
$user = $this->request(

View file

@ -12,22 +12,22 @@ class Discord extends OAuth2
/**
* @var string
*/
private $endpoint = 'https://discordapp.com/api';
private string $endpoint = 'https://discordapp.com/api';
/**
* @var array
*/
protected $user = [];
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'] ?? '';
}
/**

View file

@ -13,17 +13,17 @@ class Dropbox extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [];
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [];
/**
* @return string
@ -32,17 +32,17 @@ class Dropbox extends OAuth2
{
return 'dropbox';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://www.dropbox.com/oauth2/authorize?'.\http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state),
'response_type' => 'code'
return 'https://www.dropbox.com/oauth2/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state),
'response_type' => 'code'
]);
}
@ -53,7 +53,7 @@ class Dropbox extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
@ -77,7 +77,7 @@ class Dropbox extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
@ -92,7 +92,7 @@ class Dropbox extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -108,11 +108,7 @@ class Dropbox extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['account_id'])) {
return $user['account_id'];
}
return '';
return $user['account_id'] ?? '';
}
/**
@ -124,11 +120,27 @@ class Dropbox extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://www.dropbox.com/developers/documentation/http/documentation#users-get_current_account
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['email_verified'] ?? false) {
return true;
}
return '';
return false;
}
/**
@ -140,11 +152,7 @@ class Dropbox extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name']['display_name'];
}
return '';
return $user['name']['display_name'] ?? '';
}
/**
@ -155,7 +163,7 @@ class Dropbox extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$headers = ['Authorization: Bearer '. \urlencode($accessToken)];
$headers = ['Authorization: Bearer ' . \urlencode($accessToken)];
$user = $this->request('POST', 'https://api.dropboxapi.com/2/users/get_current_account', $headers);
$this->user = \json_decode($user, true);
}

View file

@ -3,36 +3,35 @@
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
use Utopia\Exception;
class Facebook extends OAuth2
{
/**
* @var string
*/
protected $version = 'v2.8';
protected string $version = 'v2.8';
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'email'
];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'facebook';
}
@ -40,10 +39,10 @@ class Facebook extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return 'https://www.facebook.com/'.$this->version.'/dialog/oauth?'.\http_build_query([
'client_id'=> $this->appID,
return 'https://www.facebook.com/' . $this->version . '/dialog/oauth?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state)
@ -57,7 +56,7 @@ class Facebook extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'GET',
'https://graph.facebook.com/' . $this->version . '/oauth/access_token?' . \http_build_query([
@ -77,7 +76,7 @@ class Facebook extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'GET',
@ -90,7 +89,7 @@ class Facebook extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -102,15 +101,11 @@ class Facebook extends OAuth2
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
@ -118,15 +113,27 @@ class Facebook extends OAuth2
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return $user['email'] ?? '';
}
return '';
/**
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Facebook sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
@ -134,15 +141,11 @@ class Facebook extends OAuth2
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@ -150,10 +153,10 @@ class Facebook extends OAuth2
*
* @return array
*/
protected function getUser(string $accessToken):array
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request('GET', 'https://graph.facebook.com/'.$this->version.'/me?fields=email,name&access_token='.\urlencode($accessToken));
$user = $this->request('GET', 'https://graph.facebook.com/' . $this->version . '/me?fields=email,name&access_token=' . \urlencode($accessToken));
$this->user = \json_decode($user, true);
}

View file

@ -3,31 +3,30 @@
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
use Utopia\Exception;
class Github extends OAuth2
{
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'user:email',
];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'github';
}
@ -35,9 +34,9 @@ class Github extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return 'https://github.com/login/oauth/authorize?'. \http_build_query([
return 'https://github.com/login/oauth/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
@ -52,7 +51,7 @@ class Github extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$response = $this->request(
'POST',
'https://github.com/login/oauth/access_token',
@ -78,7 +77,7 @@ class Github extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$response = $this->request(
'POST',
@ -96,7 +95,7 @@ class Github extends OAuth2
\parse_str($response, $output);
$this->tokens = $output;
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -104,53 +103,59 @@ class Github extends OAuth2
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken):string
{
$emails = \json_decode($this->request('GET', 'https://api.github.com/user/emails', ['Authorization: token '.\urlencode($accessToken)]), true);
foreach ($emails as $email) {
if ($email['primary'] && $email['verified']) {
return $email['email'];
}
}
return '';
}
/**
* @param $accessToken
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://docs.github.com/en/rest/users/emails#list-email-addresses-for-the-authenticated-user
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['verified'] ?? false) {
return true;
}
return '';
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['name'] ?? '';
}
/**
@ -161,7 +166,18 @@ class Github extends OAuth2
protected function getUser(string $accessToken)
{
if (empty($this->user)) {
$this->user = \json_decode($this->request('GET', 'https://api.github.com/user', ['Authorization: token '.\urlencode($accessToken)]), true);
$this->user = \json_decode($this->request('GET', 'https://api.github.com/user', ['Authorization: token ' . \urlencode($accessToken)]), true);
$emails = $this->request('GET', 'https://api.github.com/user/emails', ['Authorization: token ' . \urlencode($accessToken)]);
$emails = \json_decode($emails, true);
foreach ($emails as $email) {
if (isset($email['verified']) && $email['verified'] === true) {
$this->user['email'] = $email['email'];
$this->user['verified'] = $email['verified'];
break;
}
}
}
return $this->user;

View file

@ -12,17 +12,17 @@ class Gitlab extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'read_user'
];
@ -39,7 +39,7 @@ class Gitlab extends OAuth2
*/
public function getLoginURL(): string
{
return 'https://gitlab.com/oauth/authorize?'.\http_build_query([
return '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);
}

View file

@ -14,12 +14,12 @@ class Google extends OAuth2
/**
* @var string
*/
protected $version = 'v4';
protected string $version = 'v4';
/**
* @var array
*/
protected $scopes = [
protected array $scopes = [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'openid'
@ -28,12 +28,12 @@ class Google extends OAuth2
/**
* @var array
*/
protected $user = [];
protected array $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $tokens = [];
/**
* @return string
@ -48,7 +48,7 @@ class Google extends OAuth2
*/
public function getLoginURL(): string
{
return 'https://accounts.google.com/o/oauth2/v2/auth?'. \http_build_query([
return 'https://accounts.google.com/o/oauth2/v2/auth?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
@ -64,7 +64,7 @@ class Google extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
'https://oauth2.googleapis.com/token?' . \http_build_query([
@ -86,7 +86,7 @@ class Google extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
@ -98,7 +98,7 @@ class Google extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -114,11 +114,7 @@ class Google extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['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);
}

View file

@ -9,17 +9,17 @@ class Linkedin extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'r_liteprofile',
'r_emailaddress',
];
@ -40,7 +40,7 @@ class Linkedin extends OAuth2
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'linkedin';
}
@ -48,15 +48,15 @@ class Linkedin extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return 'https://www.linkedin.com/oauth/v2/authorization?'.\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
]);
return 'https://www.linkedin.com/oauth/v2/authorization?' . \http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
]);
}
/**
@ -66,7 +66,7 @@ class Linkedin extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
'https://www.linkedin.com/oauth/v2/accessToken',
@ -89,7 +89,7 @@ class Linkedin extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
@ -104,7 +104,7 @@ class Linkedin extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -112,48 +112,51 @@ class Linkedin extends OAuth2
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$email = \json_decode($this->request('GET', 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))', ['Authorization: Bearer '.\urlencode($accessToken)]), true);
$email = \json_decode($this->request('GET', 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))', ['Authorization: Bearer ' . \urlencode($accessToken)]), true);
if (
isset($email['elements']) &&
isset($email['elements'][0]) &&
isset($email['elements'][0]['handle~']) &&
isset($email['elements'][0]['handle~']['emailAddress'])
) {
return $email['elements'][0]['handle~']['emailAddress'];
}
return '';
return $email['elements'][0]['handle~']['emailAddress'] ?? '';
}
/**
* @param $accessToken
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Linkedin sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
$name = '';
@ -163,7 +166,7 @@ class Linkedin extends OAuth2
}
if (isset($user['localizedLastName'])) {
$name = (empty($name)) ? $user['localizedLastName'] : $name.' '.$user['localizedLastName'];
$name = (empty($name)) ? $user['localizedLastName'] : $name . ' ' . $user['localizedLastName'];
}
return $name;
@ -177,7 +180,7 @@ class Linkedin extends OAuth2
protected function getUser(string $accessToken)
{
if (empty($this->user)) {
$this->user = \json_decode($this->request('GET', 'https://api.linkedin.com/v2/me', ['Authorization: Bearer '.\urlencode($accessToken)]), true);
$this->user = \json_decode($this->request('GET', 'https://api.linkedin.com/v2/me', ['Authorization: Bearer ' . \urlencode($accessToken)]), true);
}
return $this->user;

View file

@ -13,17 +13,17 @@ class Microsoft extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'offline_access',
'user.read'
];
@ -35,17 +35,17 @@ class Microsoft extends OAuth2
{
return 'microsoft';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://login.microsoftonline.com/'.$this->getTenantID().'/oauth2/v2.0/authorize?'.\http_build_query([
return 'https://login.microsoftonline.com/' . $this->getTenantID() . '/oauth2/v2.0/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'state'=> \json_encode($this->state),
'scope'=> \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
'scope' => \implode(' ', $this->getScopes()),
'response_type' => 'code',
'response_mode' => 'query'
]);
@ -58,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';
}
}

View file

@ -10,29 +10,29 @@ class Mock extends OAuth2
/**
* @var string
*/
protected $version = 'v1';
protected string $version = 'v1';
/**
* @var array
*/
protected $scopes = [
protected array $scopes = [
'email'
];
/**
* @var array
*/
protected $user = [];
protected array $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $tokens = [];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'mock';
}
@ -40,9 +40,9 @@ class Mock extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return 'http://localhost/'.$this->version.'/mock/tests/general/oauth2?'. \http_build_query([
return 'http://localhost/' . $this->version . '/mock/tests/general/oauth2?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
@ -57,16 +57,16 @@ class Mock extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'GET',
'http://localhost/' . $this->version . '/mock/tests/general/oauth2/token?' .
\http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'client_secret' => $this->appSecret,
'code' => $code
])
\http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'client_secret' => $this->appSecret,
'code' => $code
])
), true);
}
@ -78,20 +78,20 @@ class Mock extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'GET',
'http://localhost/' . $this->version . '/mock/tests/general/oauth2/token?' .
\http_build_query([
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'refresh_token' => $refreshToken,
'grant_type' => 'refresh_token'
])
\http_build_query([
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'refresh_token' => $refreshToken,
'grant_type' => 'refresh_token'
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -103,15 +103,11 @@ class Mock extends OAuth2
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
@ -119,15 +115,23 @@ class Mock extends OAuth2
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return $user['email'] ?? '';
}
return '';
/**
* Check if the OAuth email is verified
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
return true;
}
/**
@ -135,15 +139,11 @@ class Mock extends OAuth2
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@ -151,10 +151,10 @@ class Mock extends OAuth2
*
* @return array
*/
protected function getUser(string $accessToken):array
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request('GET', 'http://localhost/'.$this->version.'/mock/tests/general/oauth2/user?token='.\urlencode($accessToken));
$user = $this->request('GET', 'http://localhost/' . $this->version . '/mock/tests/general/oauth2/user?token=' . \urlencode($accessToken));
$this->user = \json_decode($user, true);
}

View file

@ -9,32 +9,32 @@ class Notion extends OAuth2
/**
* @var string
*/
private $endpoint = 'https://api.notion.com/v1';
private string $endpoint = 'https://api.notion.com/v1';
/**
* @var string
*/
private $version = '2021-08-16';
private string $version = '2021-08-16';
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [];
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'notion';
}
@ -42,9 +42,9 @@ class Notion extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return $this->endpoint . '/oauth/authorize?'. \http_build_query([
return $this->endpoint . '/oauth/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'response_type' => 'code',
@ -60,7 +60,7 @@ class Notion extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = ['Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret)];
$this->tokens = \json_decode($this->request(
'POST',
@ -82,7 +82,7 @@ class Notion extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret)];
$this->tokens = \json_decode($this->request(
@ -95,7 +95,7 @@ class Notion extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -103,51 +103,55 @@ class Notion extends OAuth2
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$response = $this->getUser($accessToken);
if (isset($response['bot']['owner']['user']['id'])) {
return $response['bot']['owner']['user']['id'];
}
return '';
return $response['bot']['owner']['user']['id'] ?? '';
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$response = $this->getUser($accessToken);
if(isset($response['bot']['owner']['user']['person']['email'])){
return $response['bot']['owner']['user']['person']['email'];
}
return '';
return $response['bot']['owner']['user']['person']['email'] ?? '';
}
/**
* @param $accessToken
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Notion sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$response = $this->getUser($accessToken);
if (isset($response['bot']['owner']['user']['name'])) {
return $response['bot']['owner']['user']['name'];
}
return '';
return $response['bot']['owner']['user']['name'] ?? '';
}
/**
@ -155,11 +159,11 @@ class Notion extends OAuth2
*
* @return array
*/
protected function getUser(string $accessToken)
protected function getUser(string $accessToken): array
{
$headers = [
'Notion-Version: ' . $this->version,
'Authorization: Bearer '.\urlencode($accessToken)
'Authorization: Bearer ' . \urlencode($accessToken)
];
if (empty($this->user)) {

View file

@ -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) {

View file

@ -12,7 +12,7 @@ class Paypal extends OAuth2
/**
* @var array
*/
private $endpoint = [
private array $endpoint = [
'sandbox' => 'https://www.sandbox.paypal.com/',
'live' => 'https://www.paypal.com/',
];
@ -20,7 +20,7 @@ class Paypal extends OAuth2
/**
* @var array
*/
private $resourceEndpoint = [
private array $resourceEndpoint = [
'sandbox' => 'https://api.sandbox.paypal.com/v1/',
'live' => 'https://api.paypal.com/v1/',
];
@ -28,22 +28,22 @@ class Paypal extends OAuth2
/**
* @var string
*/
protected $environment = 'live';
protected string $environment = 'live';
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'openid',
'profile',
'email'
@ -62,7 +62,7 @@ class Paypal extends OAuth2
*/
public function getLoginURL(): string
{
$url = $this->endpoint[$this->environment] . 'connect/?'.
$url = $this->endpoint[$this->environment] . 'connect/?' .
\http_build_query([
'flowEntry' => 'static',
'response_type' => 'code',
@ -83,7 +83,7 @@ class Paypal extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
$this->resourceEndpoint[$this->environment] . 'oauth2/token',
@ -103,7 +103,7 @@ class Paypal extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
@ -115,7 +115,7 @@ class Paypal extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -131,11 +131,7 @@ class Paypal extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['payer_id'])) {
return $user['payer_id'];
}
return '';
return $user['payer_id'] ?? '';
}
/**
@ -148,12 +144,38 @@ class Paypal extends OAuth2
$user = $this->getUser($accessToken);
if (isset($user['emails'])) {
return $user['emails'][0]['value'];
$email = array_filter($user['emails'], function ($email) {
return $email['primary'] === true;
});
if (!empty($email)) {
return $email[0]['value'];
}
}
return '';
}
/**
* Check if the OAuth email is verified
*
* @link https://developer.paypal.com/docs/api/identity/v1/#userinfo_get
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['verified_account'] ?? false) {
return true;
}
return false;
}
/**
* @param string $accessToken
*
@ -163,11 +185,7 @@ class Paypal extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@ -179,7 +197,7 @@ class Paypal extends OAuth2
{
$header = [
'Content-Type: application/json',
'Authorization: Bearer '.\urlencode($accessToken),
'Authorization: Bearer ' . \urlencode($accessToken),
];
if (empty($this->user)) {
$user = $this->request(

View file

@ -6,7 +6,7 @@ use Appwrite\Auth\OAuth2\Paypal;
class PaypalSandbox extends Paypal
{
protected $environment = 'sandbox';
protected string $environment = 'sandbox';
/**
* @return string

View file

@ -14,17 +14,17 @@ class Salesforce extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
"openid"
];
@ -37,7 +37,7 @@ class Salesforce extends OAuth2
}
/**
* @param $state
* @param string $state
*
* @return array
*/
@ -52,13 +52,13 @@ class Salesforce extends OAuth2
*/
public function getLoginURL(): string
{
return 'https://login.salesforce.com/services/oauth2/authorize?'.\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'redirect_uri'=> $this->callback,
'scope'=> \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state)
]);
return 'https://login.salesforce.com/services/oauth2/authorize?' . \http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state)
]);
}
/**
@ -68,7 +68,7 @@ class Salesforce extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = [
'Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret),
'Content-Type: application/x-www-form-urlencoded',
@ -93,7 +93,7 @@ class Salesforce extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = [
'Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret),
@ -109,7 +109,7 @@ class Salesforce extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -125,11 +125,7 @@ class Salesforce extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['user_id'])) {
return $user['user_id'];
}
return '';
return $user['user_id'] ?? '';
}
/**
@ -141,11 +137,27 @@ class Salesforce extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://help.salesforce.com/s/articleView?id=sf.remoteaccess_using_userinfo_endpoint.htm&type=5
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['email_verified'] ?? false) {
return true;
}
return '';
return false;
}
/**
@ -157,11 +169,7 @@ class Salesforce extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@ -172,7 +180,7 @@ class Salesforce extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request('GET', 'https://login.salesforce.com/services/oauth2/userinfo?access_token='.\urlencode($accessToken));
$user = $this->request('GET', 'https://login.salesforce.com/services/oauth2/userinfo?access_token=' . \urlencode($accessToken));
$this->user = \json_decode($user, true);
}
return $this->user;

View file

@ -3,24 +3,23 @@
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
use Utopia\Exception;
class Slack extends OAuth2
{
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'identity.avatar',
'identity.basic',
'identity.email',
@ -30,7 +29,7 @@ class Slack extends OAuth2
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'slack';
}
@ -38,11 +37,11 @@ class Slack extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
// https://api.slack.com/docs/oauth#step_1_-_sending_users_to_authorize_and_or_install
return 'https://slack.com/oauth/authorize?'.\http_build_query([
'client_id'=> $this->appID,
return 'https://slack.com/oauth/authorize?' . \http_build_query([
'client_id' => $this->appID,
'scope' => \implode(' ', $this->getScopes()),
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state)
@ -56,7 +55,7 @@ class Slack extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
// https://api.slack.com/docs/oauth#step_3_-_exchanging_a_verification_code_for_an_access_token
$this->tokens = \json_decode($this->request(
'GET',
@ -77,7 +76,7 @@ class Slack extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'GET',
@ -89,7 +88,7 @@ class Slack extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -101,15 +100,11 @@ class Slack extends OAuth2
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['user']['id'])) {
return $user['user']['id'];
}
return '';
return $user['user']['id'] ?? '';
}
/**
@ -117,15 +112,29 @@ class Slack extends OAuth2
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['user']['email'])) {
return $user['user']['email'];
}
return $user['user']['email'] ?? '';
}
return '';
/**
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Slack sign up process
*
* @link https://slack.com/help/articles/207262907-Change-your-email-address
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
@ -133,29 +142,26 @@ class Slack extends OAuth2
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['user']['name'])) {
return $user['user']['name'];
}
return '';
return $user['user']['name'] ?? '';
}
/**
* @link https://api.slack.com/methods/users.identity
*
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken):array
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
// https://api.slack.com/methods/users.identity
$user = $this->request(
'GET',
'https://slack.com/api/users.identity?token='.\urlencode($accessToken)
'https://slack.com/api/users.identity?token=' . \urlencode($accessToken)
);
$this->user = \json_decode($user, true);

View file

@ -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);
}

View file

@ -10,38 +10,37 @@ class Stripe extends OAuth2
/**
* @var array
*/
protected $user = [];
protected array $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $tokens = [];
/**
* @var string
*/
protected $stripeAccountId = '';
protected string $stripeAccountId = '';
/**
* @var array
*/
protected $scopes = [
protected array $scopes = [
'read_write',
];
/**
* @return string
/**
* @var array
*/
protected $grantType = [
'authorize' => 'authorization_code',
'refresh' => 'refresh_token',
protected array $grantType = [
'authorize' => 'authorization_code',
'refresh' => 'refresh_token',
];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'stripe';
}
@ -49,9 +48,9 @@ class Stripe extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return 'https://connect.stripe.com/oauth/authorize?'. \http_build_query([
return 'https://connect.stripe.com/oauth/authorize?' . \http_build_query([
'response_type' => 'code', // The only option at the moment is "code."
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
@ -67,7 +66,7 @@ class Stripe extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
'https://connect.stripe.com/oauth/token',
@ -89,7 +88,7 @@ class Stripe extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
@ -101,7 +100,7 @@ class Stripe extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -110,51 +109,59 @@ class Stripe extends OAuth2
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken):string
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if(empty($user)) {
return '';
if (empty($user)) {
return '';
}
return $user['email'] ?? '';
}
/**
* @param $accessToken
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Stripe sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken):string
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
return $user['name'] ?? '';
}
/**
@ -166,15 +173,13 @@ class Stripe extends OAuth2
{
if (empty($this->user) && !empty($this->stripeAccountId)) {
$this->user = \json_decode(
$this->request(
'GET',
'https://api.stripe.com/v1/accounts/' . $this->stripeAccountId,
['Authorization: Bearer '.\urlencode($accessToken)]
),
true
$this->request(
'GET',
'https://api.stripe.com/v1/accounts/' . $this->stripeAccountId,
['Authorization: Bearer ' . \urlencode($accessToken)]
),
true
);
}
return $this->user;

View file

@ -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
*

View file

@ -6,7 +6,7 @@ use Appwrite\Auth\OAuth2\Tradeshift;
class TradeshiftBox extends Tradeshift
{
protected $environment = 'sandbox';
protected string $environment = 'sandbox';
/**
* @return string

View file

@ -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);

View file

@ -1,191 +0,0 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
use Utopia\Exception;
// Reference Material
// https://vk.com/dev/first_guide
// https://vk.com/dev/auth_sites
// https://vk.com/dev/api_requests
// https://plugins.miniorange.com/guide-to-configure-vkontakte-as-oauth-server
class Vk extends OAuth2
{
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
/**
* @var array
*/
protected $scopes = [
'openid',
'email'
];
/**
* @var string
*/
protected $version = '5.101';
/**
* @return string
*/
public function getName(): string
{
return 'vk';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://oauth.vk.com/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'response_type' => 'code',
'state' => \json_encode($this->state),
'v' => $this->version,
'scope' => \implode(' ', $this->getScopes())
]);
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded;charset=UTF-8'];
$this->tokens = \json_decode($this->request(
'POST',
'https://oauth.vk.com/access_token?',
$headers,
\http_build_query([
'code' => $code,
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'redirect_uri' => $this->callback
])
), true);
$this->user['email'] = $this->tokens['email'];
$this->user['user_id'] = $this->tokens['user_id'];
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
{
$headers = ['Content-Type: application/x-www-form-urlencoded;charset=UTF-8'];
$this->tokens = \json_decode($this->request(
'POST',
'https://oauth.vk.com/access_token?',
$headers,
\http_build_query([
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'grant_type' => 'refresh_token'
])
), true);
if(empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
$this->user['email'] = $this->tokens['email'];
$this->user['user_id'] = $this->tokens['user_id'];
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['user_id'])) {
return $user['user_id'];
}
return '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['name'])) {
return $user['name'];
}
return '';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user['name'])) {
$user = $this->request(
'GET',
'https://api.vk.com/method/users.get?'. \http_build_query([
'v' => $this->version,
'fields' => 'id,name,email,first_name,last_name',
'access_token' => $accessToken
])
);
$user = \json_decode($user, true);
$this->user['name'] = $user['response'][0]['first_name'] ." ".$user['response'][0]['last_name'];
}
return $this->user;
}
}

View file

@ -12,24 +12,24 @@ class WordPress extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'auth',
];
/**
* @return string
*/
public function getName():string
public function getName(): string
{
return 'wordpress';
}
@ -37,9 +37,9 @@ class WordPress extends OAuth2
/**
* @return string
*/
public function getLoginURL():string
public function getLoginURL(): string
{
return 'https://public-api.wordpress.com/oauth2/authorize?'. \http_build_query([
return 'https://public-api.wordpress.com/oauth2/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'response_type' => 'code',
@ -55,7 +55,7 @@ class WordPress extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
'https://public-api.wordpress.com/oauth2/token',
@ -78,7 +78,7 @@ class WordPress extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
@ -92,7 +92,7 @@ class WordPress extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -100,51 +100,63 @@ class WordPress extends OAuth2
}
/**
* @param $accessToken
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken):string
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
if (isset($user['ID'])) {
return $user['ID'];
return $user['ID'] ?? '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
if ($user['verified']) {
return $user['email'] ?? '';
}
return '';
}
/**
* @param $accessToken
*
* @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;

View file

@ -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);
}

View file

@ -12,17 +12,17 @@ class Yammer extends OAuth2
/**
* @var string
*/
private $endpoint = 'https://www.yammer.com/oauth2/';
private string $endpoint = 'https://www.yammer.com/oauth2/';
/**
* @var array
*/
protected $user = [];
protected array $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $tokens = [];
/**
* @return string
@ -37,13 +37,13 @@ class Yammer extends OAuth2
*/
public function getLoginURL(): string
{
return $this->endpoint . 'oauth2/authorize?'.
\http_build_query([
'client_id' => $this->appID,
'response_type' => 'code',
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state)
]);
return $this->endpoint . 'oauth2/authorize?' .
\http_build_query([
'client_id' => $this->appID,
'response_type' => 'code',
'redirect_uri' => $this->callback,
'state' => \json_encode($this->state)
]);
}
/**
@ -53,7 +53,7 @@ class Yammer extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
@ -76,7 +76,7 @@ class Yammer extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
@ -91,7 +91,7 @@ class Yammer extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -107,11 +107,7 @@ class Yammer extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
@ -123,11 +119,23 @@ class Yammer extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['email'])) {
return $user['email'];
}
return '';
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* If present, the email is verified. This was verfied through a manual Yammer sign up process
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
@ -139,11 +147,7 @@ class Yammer extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['full_name'])) {
return $user['full_name'];
}
return '';
return $user['full_name'] ?? '';
}
/**
@ -154,7 +158,7 @@ class Yammer extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$headers = ['Authorization: Bearer '. \urlencode($accessToken)];
$headers = ['Authorization: Bearer ' . \urlencode($accessToken)];
$user = $this->request('GET', 'https://www.yammer.com/api/v1/users/current.json', $headers);
$this->user = \json_decode($user, true);
}

View file

@ -14,17 +14,17 @@ class Yandex extends OAuth2
/**
* @var array
*/
protected $user = [];
/**
* @var array
*/
protected $tokens = [];
protected array $user = [];
/**
* @var array
*/
protected $scopes = [];
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [];
/**
* @return string
@ -35,7 +35,7 @@ class Yandex extends OAuth2
}
/**
* @param $state
* @param string $state
*
* @return array
*/
@ -50,12 +50,12 @@ class Yandex extends OAuth2
*/
public function getLoginURL(): string
{
return 'https://oauth.yandex.com/authorize?'.\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'scope'=> \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state)
]);
return 'https://oauth.yandex.com/authorize?' . \http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state)
]);
}
/**
@ -65,7 +65,7 @@ class Yandex extends OAuth2
*/
protected function getTokens(string $code): array
{
if(empty($this->tokens)) {
if (empty($this->tokens)) {
$headers = [
'Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret),
'Content-Type: application/x-www-form-urlencoded',
@ -89,7 +89,7 @@ class Yandex extends OAuth2
*
* @return array
*/
public function refreshTokens(string $refreshToken):array
public function refreshTokens(string $refreshToken): array
{
$headers = [
'Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret),
@ -105,7 +105,7 @@ class Yandex extends OAuth2
])
), true);
if(empty($this->tokens['refresh_token'])) {
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
@ -121,11 +121,7 @@ class Yandex extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['id'])) {
return $user['id'];
}
return '';
return $user['id'] ?? '';
}
/**
@ -137,11 +133,19 @@ class Yandex extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['default_email'])) {
return $user['default_email'];
}
return $user['default_email'] ?? '';
}
return '';
/**
* Check if the OAuth email is verified
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
return false;
}
/**
@ -153,11 +157,7 @@ class Yandex extends OAuth2
{
$user = $this->getUser($accessToken);
if (isset($user['display_name'])) {
return $user['display_name'];
}
return '';
return $user['display_name'] ?? '';
}
/**
@ -168,7 +168,7 @@ class Yandex extends OAuth2
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request('GET', 'https://login.yandex.ru/info?'.\http_build_query([
$user = $this->request('GET', 'https://login.yandex.ru/info?' . \http_build_query([
'format' => 'json',
'oauth_token' => $accessToken
]));

View file

@ -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)) {

View file

@ -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';

View file

@ -2,6 +2,7 @@
namespace Appwrite\Network\Validator;
use Utopia\Validator\Hostname;
use Utopia\Validator;
/**
@ -45,17 +46,16 @@ class Host extends Validator
*/
public function isValid($value): bool
{
// Check if value is valid URL
$urlValidator = new URL();
if (!$urlValidator->isValid($value)) {
return false;
}
if (\in_array(\parse_url($value, PHP_URL_HOST), $this->whitelist)) {
return true;
}
return false;
$hostname = \parse_url($value, PHP_URL_HOST);
$hostnameValidator = new Hostname($this->whitelist);
return $hostnameValidator->isValid($hostname);
}
/**

View file

@ -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);
}
/**

View file

@ -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']);
}
/**

View file

@ -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([