Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions lib/Exceptions/ConfigurationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

/**
* GlobalSiteSelector - Redirecting Nextcloud Users to their Instance.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <[email protected]>
* @copyright 2023
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\GlobalSiteSelector\Exceptions;

use Exception;

class ConfigurationException extends Exception {
}
154 changes: 139 additions & 15 deletions lib/Service/SlaveService.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,22 @@

use Exception;
use OCA\GlobalSiteSelector\AppInfo\Application;
use OCA\GlobalSiteSelector\Exceptions\ConfigurationException;
use OCA\GlobalSiteSelector\GlobalSiteSelector;
use OCA\GlobalSiteSelector\Lookup;
use OCP\Accounts\IAccountManager;
use OCP\Http\Client\IClientService;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IUser;
use OCP\IUserManager;
use Psr\Log\LoggerInterface;

class SlaveService {
private const CACHE_DISPLAY_NAME = 'gss/displayName';
private const CACHE_DISPLAY_NAME_TTL = 3600;

private LoggerInterface $logger;
private IClientService $clientService;
private IUserManager $userManager;
Expand All @@ -43,6 +49,8 @@ class SlaveService {
private string $lookupServer;
private string $operationMode;
private string $authKey;
private ICache $cacheDisplayName;
private int $cacheDisplayNameTtl;

public function __construct(
LoggerInterface $logger,
Expand All @@ -51,7 +59,8 @@ public function __construct(
IAccountManager $accountManager,
IConfig $config,
Lookup $lookup,
GlobalSiteSelector $gss
GlobalSiteSelector $gss,
ICacheFactory $cacheFactory
) {
$this->logger = $logger;
$this->clientService = $clientService;
Expand All @@ -63,6 +72,10 @@ public function __construct(
$this->lookupServer = rtrim($gss->getLookupServerUrl(), '/');
$this->operationMode = $gss->getMode();
$this->authKey = $gss->getJwtKey();

$this->cacheDisplayName = $cacheFactory->createDistributed(self::CACHE_DISPLAY_NAME);
$ttl = (int)$this->config->getAppValue('globalsiteselector', 'cache_displayname');
$this->cacheDisplayNameTtl = ($ttl === 0) ? self::CACHE_DISPLAY_NAME_TTL : $ttl;
}


Expand All @@ -79,7 +92,9 @@ public function updateUserById(string $userId): void {
* @param IUser $user
*/
public function updateUser(IUser $user): void {
if ($this->checkConfiguration() === false) {
try {
$this->checkConfiguration();
} catch (ConfigurationException $e) {
return;
}

Expand All @@ -89,12 +104,86 @@ public function updateUser(IUser $user): void {
}


protected function updateUsersOnLookup(array $users): void {
if (!$this->checkConfiguration()) {
return;
/**
* get single user's display name
*
* @param string $userId
* @param bool $cacheOnly - only get data from cache, do not request lus
*
* @return string
*/
public function getUserDisplayName(string $userId, bool $cacheOnly = false): string {
$userId = trim($userId, '/');
$details = $this->getUsersDisplayName([$userId], $cacheOnly);

return $details[$userId] ?? '';
}

/**
* get multiple users' display name
*
* @param array $userIds
* @param bool $cacheOnly - only get data from cache, do not request lus
*
* @return array
*/
public function getUsersDisplayName(array $userIds, bool $cacheOnly = false): array {
return $this->getDetails(
array_map(function (string $userId): string {
return trim($userId, '/');
}, $userIds), $cacheOnly
);
}

/**
* get details for a list of userIds from the LUS.
* Will first get data from cache, and will cache data returned by lus
*
* @param array $users
* @param bool $cacheOnly - only get data from cache, do not request lus
*
* @return array
*/
protected function getDetails(array $users, bool $cacheOnly = false): array {
$knownDetails = [];
foreach ($users as $userId) {
$knownName = $this->cacheDisplayName->get($userId);
if ($knownName !== null) {
$knownDetails[$userId] = $knownName;
}
}

$this->logger->debug('Batch updating users: {users}',
if ($cacheOnly) {
return $knownDetails;
}

$details = [];
$users = array_diff($users, array_keys($knownDetails));
if (!empty($users)) {
try {
$details = json_decode(
$this->getLookup('/gs/users', ['users' => $users]),
true,
512, JSON_THROW_ON_ERROR
);
} catch (Exception $e) {
// if configuration issue or request is not complete, we return known details.
return $knownDetails;
}
}

// cache displayName on returned result
foreach ($details as $userId => $displayName) {
$this->cacheDisplayName->set($userId, $displayName, $this->cacheDisplayNameTtl);
}

return array_merge($knownDetails, $details);
}


protected function updateUsersOnLookup(array $users): void {
$this->logger->debug(
'Batch updating users: {users}',
['users' => $users]
);

Expand All @@ -103,7 +192,9 @@ protected function updateUsersOnLookup(array $users): void {


protected function postLookup(string $path, array $data): void {
if (!$this->checkConfiguration()) {
try {
$this->checkConfiguration();
} catch (ConfigurationException $e) {
return;
}

Expand All @@ -116,28 +207,61 @@ protected function postLookup(string $path, array $data): void {
$this->lookup->configureClient(['body' => json_encode($dataBatch)])
);
} catch (Exception $e) {
$this->logger->warning('Could not send user to lookup server',
$this->logger->warning(
'Could not send user to lookup server',
['exception' => $e]
);
}
}


/**
* @param string $path
* @param array $data
*
* @return string
* @throws ConfigurationException
*/
protected function getLookup(string $path, array $data): string {
$this->checkConfiguration();

$dataBatch = array_merge(['authKey' => $this->authKey], $data);

$httpClient = $this->clientService->newClient();
try {
$response = $httpClient->get(
$this->lookupServer . $path,
$this->lookup->configureClient(['body' => json_encode($dataBatch)])
);
} catch (Exception $e) {
$this->logger->warning(
'Could not get data from lookup server',
['exception' => $e]
);

return '';
}

return $response->getBody();
}


protected function checkConfiguration(): bool {
/**
* @return void
* @throws ConfigurationException
*/
protected function checkConfiguration(): void {
if (empty($this->lookupServer)
|| empty($this->operationMode)
|| empty($this->authKey)
) {
$this->logger->error('global site selector app not configured correctly');

return false;
$this->logger->error('app not configured correctly');
throw new ConfigurationException('globalsiteselector app not configured correctly');
}

if ($this->operationMode !== 'slave') {
return false;
throw new ConfigurationException('not configured as slave');
}

return true;
}


Expand Down