summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorJoas Schilling <coding@schilljs.com>2021-10-20 10:29:45 +0200
committerJoas Schilling <coding@schilljs.com>2021-10-23 00:54:50 +0200
commitb578a1e8b56f6b3ecf7dee837af6bd8265f9c0b0 (patch)
treee483d50ca038882c3521782aff6574eb0bda0f31 /lib
parent1895a6dc573cdb46cfa1987e25d45781623fdb7d (diff)
downloadnextcloud-server-b578a1e8b56f6b3ecf7dee837af6bd8265f9c0b0.tar.gz
nextcloud-server-b578a1e8b56f6b3ecf7dee837af6bd8265f9c0b0.zip
Fair use of push notifications
We want to keep offering our push notification service for free, but large users overload our infrastructure. For this reason we have to rate-limit the use of push notifications. If you need this feature, consider setting up your own push server or using Nextcloud Enterprise. Signed-off-by: Joas Schilling <coding@schilljs.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/private/Notification/Manager.php80
-rw-r--r--lib/private/Support/Subscription/Registry.php23
-rw-r--r--lib/private/User/Manager.php7
-rw-r--r--lib/public/Notification/IManager.php12
-rw-r--r--lib/public/Support/Subscription/IRegistry.php4
5 files changed, 93 insertions, 33 deletions
diff --git a/lib/private/Notification/Manager.php b/lib/private/Notification/Manager.php
index fb3a46d5f5d..4e0992053f2 100644
--- a/lib/private/Notification/Manager.php
+++ b/lib/private/Notification/Manager.php
@@ -27,8 +27,10 @@ declare(strict_types=1);
namespace OC\Notification;
use OC\AppFramework\Bootstrap\Coordinator;
-use OCP\AppFramework\QueryException;
-use OCP\ILogger;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\ICache;
+use OCP\ICacheFactory;
+use OCP\IUserManager;
use OCP\Notification\AlreadyProcessedException;
use OCP\Notification\IApp;
use OCP\Notification\IDeferrableApp;
@@ -37,11 +39,22 @@ use OCP\Notification\IManager;
use OCP\Notification\INotification;
use OCP\Notification\INotifier;
use OCP\RichObjectStrings\IValidator;
+use OCP\Support\Subscription\IRegistry;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Log\LoggerInterface;
class Manager implements IManager {
/** @var IValidator */
protected $validator;
- /** @var ILogger */
+ /** @var IUserManager */
+ private $userManager;
+ /** @var ICache */
+ protected $cache;
+ /** @var ITimeFactory */
+ protected $timeFactory;
+ /** @var IRegistry */
+ protected $subscription;
+ /** @var LoggerInterface */
protected $logger;
/** @var Coordinator */
private $coordinator;
@@ -64,9 +77,17 @@ class Manager implements IManager {
private $parsedRegistrationContext;
public function __construct(IValidator $validator,
- ILogger $logger,
+ IUserManager $userManager,
+ ICacheFactory $cacheFactory,
+ ITimeFactory $timeFactory,
+ IRegistry $subscription,
+ LoggerInterface $logger,
Coordinator $coordinator) {
$this->validator = $validator;
+ $this->userManager = $userManager;
+ $this->cache = $cacheFactory->createDistributed('notifications');
+ $this->timeFactory = $timeFactory;
+ $this->subscription = $subscription;
$this->logger = $logger;
$this->coordinator = $coordinator;
@@ -97,9 +118,10 @@ class Manager implements IManager {
*/
public function registerNotifier(\Closure $service, \Closure $info) {
$infoData = $info();
- $this->logger->logException(new \InvalidArgumentException(
+ $exception = new \InvalidArgumentException(
'Notifier ' . $infoData['name'] . ' (id: ' . $infoData['id'] . ') is not considered because it is using the old way to register.'
- ));
+ );
+ $this->logger->error($exception->getMessage(), ['exception' => $exception]);
}
/**
@@ -121,10 +143,10 @@ class Manager implements IManager {
foreach ($this->appClasses as $appClass) {
try {
- $app = \OC::$server->query($appClass);
- } catch (QueryException $e) {
- $this->logger->logException($e, [
- 'message' => 'Failed to load notification app class: ' . $appClass,
+ $app = \OC::$server->get($appClass);
+ } catch (ContainerExceptionInterface $e) {
+ $this->logger->error('Failed to load notification app class: ' . $appClass, [
+ 'exception' => $e,
'app' => 'notifications',
]);
continue;
@@ -153,10 +175,10 @@ class Manager implements IManager {
$notifierServices = $this->coordinator->getRegistrationContext()->getNotifierServices();
foreach ($notifierServices as $notifierService) {
try {
- $notifier = \OC::$server->query($notifierService->getService());
- } catch (QueryException $e) {
- $this->logger->logException($e, [
- 'message' => 'Failed to load notification notifier class: ' . $notifierService->getService(),
+ $notifier = \OC::$server->get($notifierService->getService());
+ } catch (ContainerExceptionInterface $e) {
+ $this->logger->error('Failed to load notification notifier class: ' . $notifierService->getService(), [
+ 'exception' => $e,
'app' => 'notifications',
]);
continue;
@@ -181,10 +203,10 @@ class Manager implements IManager {
foreach ($this->notifierClasses as $notifierClass) {
try {
- $notifier = \OC::$server->query($notifierClass);
- } catch (QueryException $e) {
- $this->logger->logException($e, [
- 'message' => 'Failed to load notification notifier class: ' . $notifierClass,
+ $notifier = \OC::$server->get($notifierClass);
+ } catch (ContainerExceptionInterface $e) {
+ $this->logger->error('Failed to load notification notifier class: ' . $notifierClass, [
+ 'exception' => $e,
'app' => 'notifications',
]);
continue;
@@ -278,6 +300,28 @@ class Manager implements IManager {
}
/**
+ * {@inheritDoc}
+ */
+ public function isFairUseOfFreePushService(): bool {
+ $pushAllowed = $this->cache->get('push_fair_use');
+ if ($pushAllowed === null) {
+ /**
+ * We want to keep offering our push notification service for free, but large
+ * users overload our infrastructure. For this reason we have to rate-limit the
+ * use of push notifications. If you need this feature, consider setting up your
+ * own push server or using Nextcloud Enterprise.
+ */
+ // TODO Remove time check after 1st March 2022
+ $isFairUse = $this->timeFactory->getTime() < 1646089200
+ || $this->subscription->delegateHasValidSubscription()
+ || $this->userManager->countSeenUsers() < 5000;
+ $pushAllowed = $isFairUse ? 'yes' : 'no';
+ $this->cache->set('push_fair_use', $pushAllowed, 3600);
+ }
+ return $pushAllowed === 'yes';
+ }
+
+ /**
* @param INotification $notification
* @throws \InvalidArgumentException When the notification is not valid
* @since 8.2.0
diff --git a/lib/private/Support/Subscription/Registry.php b/lib/private/Support/Subscription/Registry.php
index e64eaac1fa2..1298337acb2 100644
--- a/lib/private/Support/Subscription/Registry.php
+++ b/lib/private/Support/Subscription/Registry.php
@@ -59,21 +59,17 @@ class Registry implements IRegistry {
private $groupManager;
/** @var LoggerInterface */
private $logger;
- /** @var IManager */
- private $notificationManager;
public function __construct(IConfig $config,
IServerContainer $container,
IUserManager $userManager,
IGroupManager $groupManager,
- LoggerInterface $logger,
- IManager $notificationManager) {
+ LoggerInterface $logger) {
$this->config = $config;
$this->container = $container;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->logger = $logger;
- $this->notificationManager = $notificationManager;
}
private function getSubscription(): ?ISubscription {
@@ -158,15 +154,16 @@ class Registry implements IRegistry {
/**
* Indicates if a hard user limit is reached and no new users should be created
*
+ * @param IManager|null $notificationManager
* @since 21.0.0
*/
- public function delegateIsHardUserLimitReached(): bool {
+ public function delegateIsHardUserLimitReached(?IManager $notificationManager = null): bool {
$subscription = $this->getSubscription();
if ($subscription instanceof ISubscription &&
$subscription->hasValidSubscription()) {
$userLimitReached = $subscription->isHardUserLimitReached();
- if ($userLimitReached) {
- $this->notifyAboutReachedUserLimit();
+ if ($userLimitReached && $notificationManager instanceof IManager) {
+ $this->notifyAboutReachedUserLimit($notificationManager);
}
return $userLimitReached;
}
@@ -181,8 +178,8 @@ class Registry implements IRegistry {
$hardUserLimit = $this->config->getSystemValue('one-click-instance.user-limit', 50);
$userLimitReached = $userCount >= $hardUserLimit;
- if ($userLimitReached) {
- $this->notifyAboutReachedUserLimit();
+ if ($userLimitReached && $notificationManager instanceof IManager) {
+ $this->notifyAboutReachedUserLimit($notificationManager);
}
return $userLimitReached;
}
@@ -216,17 +213,17 @@ class Registry implements IRegistry {
return $userCount;
}
- private function notifyAboutReachedUserLimit() {
+ private function notifyAboutReachedUserLimit(IManager $notificationManager) {
$admins = $this->groupManager->get('admin')->getUsers();
foreach ($admins as $admin) {
- $notification = $this->notificationManager->createNotification();
+ $notification = $notificationManager->createNotification();
$notification->setApp('core')
->setUser($admin->getUID())
->setDateTime(new \DateTime())
->setObject('user_limit_reached', '1')
->setSubject('user_limit_reached');
- $this->notificationManager->notify($notification);
+ $notificationManager->notify($notification);
}
$this->logger->warning('The user limit was reached and the new user was not created', ['app' => 'lib']);
diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php
index dbbfc2b53a2..7ed80bc5bc2 100644
--- a/lib/private/User/Manager.php
+++ b/lib/private/User/Manager.php
@@ -44,6 +44,7 @@ use OCP\IGroup;
use OCP\IUser;
use OCP\IUserBackend;
use OCP\IUserManager;
+use OCP\Notification\IManager;
use OCP\Support\Subscription\IRegistry;
use OCP\User\Backend\IGetRealUIDBackend;
use OCP\User\Backend\ISearchKnownUsersBackend;
@@ -379,7 +380,11 @@ class Manager extends PublicEmitter implements IUserManager {
*/
public function createUser($uid, $password) {
// DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
- if (\OC::$server->get(IRegistry::class)->delegateIsHardUserLimitReached()) {
+ /** @var IRegistry $registry */
+ $registry = \OC::$server->get(IRegistry::class);
+ /** @var IManager $notificationManager */
+ $notificationManager = \OC::$server->get(IManager::class);
+ if ($registry->delegateIsHardUserLimitReached($notificationManager)) {
$l = \OC::$server->getL10N('lib');
throw new HintException($l->t('The user limit has been reached and the user was not created.'));
}
diff --git a/lib/public/Notification/IManager.php b/lib/public/Notification/IManager.php
index 66fe78b723e..e2f37176850 100644
--- a/lib/public/Notification/IManager.php
+++ b/lib/public/Notification/IManager.php
@@ -107,4 +107,16 @@ interface IManager extends IApp, INotifier {
* @since 20.0.0
*/
public function flush(): void;
+
+ /**
+ * Whether the server can use the hosted push notification service
+ *
+ * We want to keep offering our push notification service for free, but large
+ * users overload our infrastructure. For this reason we have to rate-limit the
+ * use of push notifications. If you need this feature, consider setting up your
+ * own push server or using Nextcloud Enterprise.
+ *
+ * @since 23.0.0
+ */
+ public function isFairUseOfFreePushService(): bool;
}
diff --git a/lib/public/Support/Subscription/IRegistry.php b/lib/public/Support/Subscription/IRegistry.php
index 1082f12ab58..4a34cc91c5e 100644
--- a/lib/public/Support/Subscription/IRegistry.php
+++ b/lib/public/Support/Subscription/IRegistry.php
@@ -27,6 +27,7 @@ declare(strict_types=1);
*/
namespace OCP\Support\Subscription;
+use OCP\Notification\IManager;
use OCP\Support\Subscription\Exception\AlreadyRegisteredException;
/**
@@ -81,7 +82,8 @@ interface IRegistry {
/**
* Indicates if a hard user limit is reached and no new users should be created
*
+ * @param IManager|null $notificationManager
* @since 21.0.0
*/
- public function delegateIsHardUserLimitReached(): bool;
+ public function delegateIsHardUserLimitReached(?IManager $notificationManager = null): bool;
}