get instances
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
This commit is contained in:
Родитель
26045cab82
Коммит
f9913a04b5
|
@ -41,6 +41,15 @@ CREATE TABLE IF NOT EXISTS `users` (
|
|||
KEY `federationId` (`federationId`(191))
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
DROP TABLE IF EXISTS `instances`;
|
||||
CREATE TABLE IF NOT EXISTS `instances` (
|
||||
`id` int(6) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`instance` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `instance` (`instance`(191))
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
DROP TABLE IF EXISTS `toVerify`;
|
||||
CREATE TABLE IF NOT EXISTS `toVerify` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
|
|
|
@ -65,6 +65,12 @@ $CONFIG = [
|
|||
'CONSUMER_SECRET' => '',
|
||||
'ACCESS_TOKEN' => '',
|
||||
'ACCESS_TOKEN_SECRET' => '',
|
||||
],
|
||||
|
||||
// enforce listing of instance instead of auto-generating it based on users' account
|
||||
'INSTANCES' => [
|
||||
// 'i001.example.net',
|
||||
// 'i002.example.net',
|
||||
]
|
||||
];
|
||||
|
||||
|
|
|
@ -101,6 +101,18 @@ $app->delete('/gs/users', $r_batchDelete);
|
|||
$app->delete('/index.php/gs/users', $r_batchDelete);
|
||||
|
||||
|
||||
|
||||
$r_instances = function (ServerRequestInterface $request, ResponseInterface $response, array $args) {
|
||||
/** @var InstanceManager $instanceManager */
|
||||
$instanceManager = $this->get('InstanceManager');
|
||||
|
||||
return $instanceManager->getInstances($request, $response);
|
||||
};
|
||||
$app->get('/gs/instances', $r_instances);
|
||||
$app->get('/index.php/gs/instances', $r_instances);
|
||||
$app->post('/instances', $r_instances); // retro compatibility until nc26
|
||||
$app->post('/index.php/instances', $r_instances); // retro compatibility until nc26
|
||||
|
||||
$r_validateEmail = function (ServerRequestInterface $request, ResponseInterface $response, array $args) {
|
||||
/** @var Email $emailValidator */
|
||||
$emailValidator = $this->get('EmailValidator');
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace LookupServer\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class SignedRequestException
|
||||
*
|
||||
* @package LookupServer\Exceptions
|
||||
*/
|
||||
class SignedRequestException extends Exception {
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace LookupServer;
|
||||
|
||||
use PDO;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
|
||||
class InstanceManager {
|
||||
private PDO $db;
|
||||
private SignatureHandler $signatureHandler;
|
||||
private bool $globalScaleMode = false;
|
||||
private string $authKey = '';
|
||||
private array $instances = [];
|
||||
|
||||
public function __construct(
|
||||
PDO $db,
|
||||
SignatureHandler $signatureHandler,
|
||||
bool $globalScaleMode,
|
||||
string $authKey,
|
||||
?array $instances
|
||||
) {
|
||||
$this->db = $db;
|
||||
$this->signatureHandler = $signatureHandler;
|
||||
$this->globalScaleMode = $globalScaleMode;
|
||||
$this->authKey = $authKey;
|
||||
if (is_array($instances)) {
|
||||
$this->instances = $instances;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function insert(string $instance) {
|
||||
$stmt = $this->db->prepare('SELECT id, instance, timestamp FROM instances WHERE instance=:instance');
|
||||
$stmt->bindParam(':instance', $instance, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
|
||||
$data = $stmt->fetch();
|
||||
if ($data === false) {
|
||||
$time = time();
|
||||
$insert = $this->db->prepare(
|
||||
'INSERT INTO instances (instance, timestamp) VALUES (:instance, FROM_UNIXTIME(:timestamp))'
|
||||
);
|
||||
$insert->bindParam(':instance', $instance, PDO::PARAM_STR);
|
||||
$insert->bindParam(':timestamp', $time, PDO::PARAM_INT);
|
||||
|
||||
$insert->execute();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* let Nextcloud servers obtains the full list of registered instances in the global scale scenario
|
||||
* If result is empty, sync from the users list
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getInstances(Request $request, Response $response): Response {
|
||||
if ($this->globalScaleMode !== true) {
|
||||
$response->withStatus(404);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
$body = json_decode($request->getBody(), true);
|
||||
if ($body === null || !isset($body['authKey'])) {
|
||||
$response->withStatus(400);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ($body['authKey'] !== $this->authKey) {
|
||||
$response->withStatus(403);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
$instances = $this->getAll();
|
||||
if (empty($instances)) {
|
||||
$this->syncInstances();
|
||||
$instances = $this->getAll();
|
||||
}
|
||||
|
||||
$response->getBody()
|
||||
->write(json_encode($instances));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAll(): array {
|
||||
if (is_array($this->instances) && !empty($this->instances)) {
|
||||
return $this->instances;
|
||||
}
|
||||
|
||||
$stmt = $this->db->prepare('SELECT instance FROM instances');
|
||||
$stmt->execute();
|
||||
|
||||
$instances = [];
|
||||
while ($data = $stmt->fetch()) {
|
||||
$instances[] = $data['instance'];
|
||||
}
|
||||
$stmt->closeCursor();
|
||||
|
||||
return $instances;
|
||||
}
|
||||
|
||||
|
||||
public function getAllFromConfig(): array {
|
||||
return $this->instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* sync the instances from the users table
|
||||
*/
|
||||
public function syncInstances(): void {
|
||||
$stmt = $this->db->prepare('SELECT federationId FROM users');
|
||||
$stmt->execute();
|
||||
$instances = [];
|
||||
while ($data = $stmt->fetch()) {
|
||||
$pos = strrpos($data['federationId'], '@');
|
||||
$instance = substr($data['federationId'], $pos + 1);
|
||||
if (substr($instance, 0, 7) === 'http://') {
|
||||
$instance = substr($instance, 7);
|
||||
}
|
||||
if (!in_array($instance, $instances)) {
|
||||
$instances[] = $instance;
|
||||
}
|
||||
}
|
||||
$stmt->closeCursor();
|
||||
|
||||
foreach ($instances as $instance) {
|
||||
$this->insert($instance);
|
||||
}
|
||||
|
||||
$this->removeDeprecatedInstances($instances);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|null $instance
|
||||
* @param bool $removeUsers
|
||||
*/
|
||||
public function remove(string $instance, bool $removeUsers = false): void {
|
||||
$stmt = $this->db->prepare('DELETE FROM instances WHERE instance = :instance');
|
||||
$stmt->bindParam(':instance', $instance);
|
||||
$stmt->execute();
|
||||
$stmt->closeCursor();
|
||||
|
||||
if ($removeUsers) {
|
||||
$this->removeUsers($instance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $instance
|
||||
*/
|
||||
private function removeUsers(string $instance) {
|
||||
$search = '%@' . $this->escapeWildcard($instance);
|
||||
$stmt = $this->db->prepare('SELECT id FROM users WHERE federationId LIKE :search');
|
||||
$stmt->bindParam(':search', $search);
|
||||
$stmt->execute();
|
||||
|
||||
while ($data = $stmt->fetch()) {
|
||||
$this->removeUser($data['id']);
|
||||
}
|
||||
|
||||
$stmt->closeCursor();
|
||||
$this->removingEmptyInstance($instance);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int $userId
|
||||
*/
|
||||
private function removeUser(int $userId) {
|
||||
$stmt = $this->db->prepare('DELETE FROM users WHERE id = :id');
|
||||
$stmt->bindParam(':id', $userId);
|
||||
$stmt->execute();
|
||||
|
||||
$stmt = $this->db->prepare('DELETE FROM store WHERE userId = :id');
|
||||
$stmt->bindParam(':id', $userId);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $input
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function escapeWildcard(string $input): string {
|
||||
$output = str_replace('%', '\%', $input);
|
||||
$output = str_replace('_', '\_', $output);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $cloudId
|
||||
*/
|
||||
public function newUser(string $cloudId): void {
|
||||
$pos = strrpos($cloudId, '@');
|
||||
$instance = substr($cloudId, $pos + 1);
|
||||
|
||||
$this->insert($instance);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $cloudId
|
||||
*/
|
||||
public function removingUser(string $cloudId): void {
|
||||
$pos = strrpos($cloudId, '@');
|
||||
$instance = substr($cloudId, $pos + 1);
|
||||
|
||||
$this->removingEmptyInstance($instance);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $instance
|
||||
*/
|
||||
private function removingEmptyInstance(string $instance) {
|
||||
$search = '%@' . $this->escapeWildcard($instance);
|
||||
|
||||
$stmt = $this->db->prepare('SELECT federationId FROM users WHERE federationId LIKE :search');
|
||||
$stmt->bindParam(':search', $search);
|
||||
$stmt->execute();
|
||||
if ($stmt->fetch() === false) {
|
||||
$this->remove($instance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $instances
|
||||
*/
|
||||
private function removeDeprecatedInstances(array $instances): void {
|
||||
$current = $this->getAll();
|
||||
|
||||
foreach ($current as $item) {
|
||||
if (!in_array($item, $instances)) {
|
||||
$this->remove($item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@ namespace LookupServer\Service;
|
|||
use Abraham\TwitterOAuth\TwitterOAuth;
|
||||
use DI\Container;
|
||||
use Exception;
|
||||
use LookupServer\InstanceManager;
|
||||
use LookupServer\Replication;
|
||||
use LookupServer\SignatureHandler;
|
||||
use LookupServer\Tools\Traits\TArrayTools;
|
||||
|
@ -77,12 +78,23 @@ class DependenciesService {
|
|||
});
|
||||
|
||||
|
||||
$container->set('InstanceManager', function (Container $c) {
|
||||
return new InstanceManager(
|
||||
$c->get('db'),
|
||||
$c->get('SignatureHandler'),
|
||||
$this->getBool('settings.global_scale', $c->get('Settings')),
|
||||
$this->get('settings.auth_key', $c->get('Settings')),
|
||||
$this->getArray('settings.instances', $c->get('Settings'))
|
||||
);
|
||||
});
|
||||
|
||||
$container->set('UserManager', function (Container $c) {
|
||||
return new UserManager(
|
||||
$c->get('db'),
|
||||
$c->get('EmailValidator'),
|
||||
$c->get('WebsiteValidator'),
|
||||
$c->get('TwitterValidator'),
|
||||
$c->get('InstanceManager'),
|
||||
$c->get('SignatureHandler'),
|
||||
$this->getBool('settings.global_scale', $c->get('Settings')),
|
||||
$this->get('settings.auth_key', $c->get('Settings'))
|
||||
|
|
|
@ -29,9 +29,10 @@ declare(strict_types=1);
|
|||
namespace LookupServer;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use LookupServer\Exceptions\SignedRequestException;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class SignatureHandler {
|
||||
|
||||
|
@ -97,4 +98,33 @@ class SignatureHandler {
|
|||
return [$user, $host];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @throws SignedRequestException
|
||||
*/
|
||||
public function verifyRequest(Request $request): string {
|
||||
$body = json_decode($request->getBody(), true);
|
||||
if ($body === null || !isset($body['message']) || !isset($body['message']['data'])
|
||||
|| !isset($body['message']['data']['federationId'])
|
||||
|| !isset($body['signature'])
|
||||
|| !isset($body['message']['timestamp'])) {
|
||||
throw new SignedRequestException();
|
||||
}
|
||||
|
||||
$cloudId = $body['message']['data']['federationId'];
|
||||
|
||||
try {
|
||||
$verified = $this->verify($cloudId, $body['message'], $body['signature']);
|
||||
if ($verified) {
|
||||
list(, $host) = $this->splitCloudId($body['message']['data']['federationId']);
|
||||
|
||||
return $host;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
throw new SignedRequestException();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ class UserManager {
|
|||
private Email $emailValidator;
|
||||
private Website $websiteValidator;
|
||||
private Twitter $twitterValidator;
|
||||
private InstanceManager $instanceManager;
|
||||
private SignatureHandler $signatureHandler;
|
||||
private int $maxVerifyTries = 10;
|
||||
private bool $globalScaleMode;
|
||||
|
@ -33,6 +34,7 @@ class UserManager {
|
|||
* @param Email $emailValidator
|
||||
* @param Website $websiteValidator
|
||||
* @param Twitter $twitterValidator
|
||||
* @param InstanceManager $instanceManager
|
||||
* @param SignatureHandler $signatureHandler
|
||||
* @param bool $globalScaleMode
|
||||
* @param string $authKey
|
||||
|
@ -42,6 +44,7 @@ class UserManager {
|
|||
Email $emailValidator,
|
||||
Website $websiteValidator,
|
||||
Twitter $twitterValidator,
|
||||
InstanceManager $instanceManager,
|
||||
SignatureHandler $signatureHandler,
|
||||
bool $globalScaleMode,
|
||||
string $authKey
|
||||
|
@ -50,6 +53,7 @@ class UserManager {
|
|||
$this->emailValidator = $emailValidator;
|
||||
$this->websiteValidator = $websiteValidator;
|
||||
$this->twitterValidator = $twitterValidator;
|
||||
$this->instanceManager = $instanceManager;
|
||||
$this->signatureHandler = $signatureHandler;
|
||||
$this->globalScaleMode = $globalScaleMode;
|
||||
$this->authKey = $authKey;
|
||||
|
@ -211,7 +215,10 @@ LIMIT :limit'
|
|||
|
||||
$users = [];
|
||||
while ($data = $stmt->fetch()) {
|
||||
$users[] = $this->getForUserId((int)$data['userId']);
|
||||
$entry = $this->getForUserId((int)$data['userId']);
|
||||
if (!empty($entry)) {
|
||||
$users[] = $entry;
|
||||
}
|
||||
}
|
||||
$stmt->closeCursor();
|
||||
|
||||
|
@ -306,6 +313,8 @@ LIMIT :limit'
|
|||
$this->emailValidator->emailUpdated($data[$field], $storeId);
|
||||
}
|
||||
}
|
||||
|
||||
$this->instanceManager->newUser($cloudId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -767,6 +776,8 @@ WHERE
|
|||
$stmt->execute();
|
||||
$stmt->closeCursor();
|
||||
|
||||
$this->instanceManager->removingUser($cloudId);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,5 +24,6 @@ return [
|
|||
'access_token' => $CONFIG['TWITTER']['ACCESS_TOKEN'],
|
||||
'access_token_secret' => $CONFIG['TWITTER']['ACCESS_TOKEN_SECRET'],
|
||||
],
|
||||
'instances' => $CONFIG['INSTANCES'] ?? []
|
||||
]
|
||||
];
|
||||
|
|
Загрузка…
Ссылка в новой задаче