From 8b5de616881ac05f2a038ce380d5e63179a14a14 Mon Sep 17 00:00:00 2001 From: Akhil Date: Fri, 17 Oct 2025 14:43:41 +0530 Subject: [PATCH 1/6] (feat) add methods to DomainService for admin blacklist --- lib/Service/DomainService.php | 44 +++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/lib/Service/DomainService.php b/lib/Service/DomainService.php index ad0d55c..e80a90d 100644 --- a/lib/Service/DomainService.php +++ b/lib/Service/DomainService.php @@ -24,6 +24,7 @@ class DomainService { private const BLACKLISTED_DOMAINS_URL = 'https://raw.githubusercontent.com/disposable/disposable-email-domains/master/domains.json'; private const BLACKLISTED_DOMAINS_FILE = 'blacklisted_domains.json'; private const DISPOSABLE_DOMAINS_FILE = 'disposable_domains.json'; + private const ADMIN_BLACKLISTED_DOMAINS_FILE = 'admin_blacklisted_domains.json'; public function __construct(string $appName, LoggerInterface $logger, IAppData $appData, Client $httpClient, IL10N $l) { $this->logger = $logger; @@ -61,6 +62,49 @@ class DomainService { return $this->isDomainInList($email, $domains); } + /** + * Check if an email domain is blacklisted by admin action + */ + public function isDomainInAdminBlacklist(string $email, IL10N $l): bool { + $domains = $this->getDomainsFromFile(self::ADMIN_BLACKLISTED_DOMAINS_FILE, $l); + return $this->isDomainInList($email, $domains); + } + + /** + * Add domain to admin blacklist + */ + public function addAdminBlacklistedDomain(string $domain): void { + $domains = $this->getDomainsFromFile(self::ADMIN_BLACKLISTED_DOMAINS_FILE, $l); + + $cleanDomain = preg_replace('/[\x00-\x1F\x7F]/', '', $domain); + $domains = array_unique(array_merge($domains, [$cleanDomain])); + + $this->saveDomainsToFile(self::ADMIN_BLACKLISTED_DOMAINS_FILE, $domains); + } + + /** + * Delete domain from admin blacklist + */ + public function deleteAdminBlacklistedDomain(string $domain): void { + $domains = $this->getDomainsFromFile(self::ADMIN_BLACKLISTED_DOMAINS_FILE, $l); + $cleanDomain = preg_replace('/[\x00-\x1F\x7F]/', '', $domain); + + $domains = array_unique($domains); + $key = array_search($domain, $domains); + + if ($key !== false) { + unset($domains[$key]); + } + + $this->saveDomainsToFile(self::ADMIN_BLACKLISTED_DOMAINS_FILE, $domains); + } + + /** + * Return all admin blacklist domains + */ + public function getAdminBlacklistedDomains(): array { + return $this->getDomainsFromFile(self::ADMIN_BLACKLISTED_DOMAINS_FILE, $l); + } /** * Update blacklisted domains by fetching from the external source. */ -- GitLab From f5b69bc182b604addb299f5bbdd8fdb446a33421 Mon Sep 17 00:00:00 2001 From: Akhil Date: Fri, 17 Oct 2025 14:43:51 +0530 Subject: [PATCH 2/6] (feat) add admin blacklist command --- lib/Command/AdminBlacklistedDomains.php | 95 +++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 lib/Command/AdminBlacklistedDomains.php diff --git a/lib/Command/AdminBlacklistedDomains.php b/lib/Command/AdminBlacklistedDomains.php new file mode 100644 index 0000000..e11b2b7 --- /dev/null +++ b/lib/Command/AdminBlacklistedDomains.php @@ -0,0 +1,95 @@ +setName(Application::APP_ID.':admin-blacklisted-domains') + ->setDescription('Manage admin blacklisted domains') + ->addArgument( + 'domain', + InputArgument::OPTIONAL, + 'Domain name to add or delete', + '' + ) + ->addOption( + 'add', + null, + InputOption::VALUE_NONE, + 'Specify this option to add the domain' + ) + ->addOption( + 'list', + null, + InputOption::VALUE_NONE, + 'Specify this option to list all blacklisted domain' + ) + ->addOption( + 'delete', + null, + InputOption::VALUE_NONE, + 'Specify this option to delete the domain' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + if ($input->hasParameter('--list')) { + $this->listDomains($input, $output); + return 0; + } + if ($input->hasParameter('--add')) { + $this->addDomain($input, $output); + return 0; + } + if ($input->hasParameter('--delete')) { + $this->deleteDomain(); + return 0; + } + } + + private function listDomains(InputInterface $input, OutputInterface $output): void { + try { + $domains = $this->domainService->getAdminBlacklistedDomains(); + $this->writeArrayInOutputFormat($input, $output, $domains); + } catch (\Throwable $e) { + $output->writeln('Error listing admin blacklisted domains: ' . $e->getMessage()); + } + } + + private function addDomain(InputInterface $input, OutputInterface $output): void { + try { + if (!$input->getArgument('domain')) { + throw new \Exception('Domain argument is required!'); + } + $domain = $input->getArgument('domain'); + $this->domainService->addAdminBlacklistedDomain($domain); + } catch (\Throwable $e) { + $output->writeln("Error adding admin blacklisted domain $domain :" . $e->getMessage()); + } + } + + private function deleteDomain(InputInterface $input, OutputInterface $output): void { + try { + if (!$input->getArgument('domain')) { + throw new \Exception('Domain argument is required!'); + } + $domain = $input->getArgument('domain'); + $this->domainService->deleteAdminBlacklistedDomain($domain); + } catch (\Throwable $e) { + $output->writeln("Error deleting admin blacklisted domain $domain :" . $e->getMessage()); + } + } +} -- GitLab From fd3fdf4c599b849aa6033b688bf2c2d1127bac76 Mon Sep 17 00:00:00 2001 From: Akhil Date: Fri, 17 Oct 2025 14:45:43 +0530 Subject: [PATCH 3/6] (feat) add admin blacklist check to recovery email validation --- lib/Service/RecoveryEmailService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index 9bbfcf1..ca8e619 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -211,7 +211,7 @@ class RecoveryEmailService { throw new MurenaDomainDisallowedException($l->t('You cannot set an email address with a Murena domain as recovery email address.')); } - if ($this->domainService->isBlacklistedDomain($recoveryEmail, $l)) { + if ($this->domainService->isBlacklistedDomain($recoveryEmail, $l) || $this->domainService->isDomainInAdminBlacklist($recoveryEmail, $l)) { $this->logger->info("User ID $username's requested recovery email address domain is blacklisted."); throw new BlacklistedEmailException($l->t('The domain of this email address is blacklisted. Please provide another recovery address.')); } -- GitLab From 3ef479b8dd3587d8a04195e81c89f24fb8e0ee83 Mon Sep 17 00:00:00 2001 From: Akhil Date: Fri, 17 Oct 2025 14:55:40 +0530 Subject: [PATCH 4/6] (fix) remove l10n requirement for getting domains --- lib/Service/DomainService.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/Service/DomainService.php b/lib/Service/DomainService.php index e80a90d..53ec8bb 100644 --- a/lib/Service/DomainService.php +++ b/lib/Service/DomainService.php @@ -74,7 +74,7 @@ class DomainService { * Add domain to admin blacklist */ public function addAdminBlacklistedDomain(string $domain): void { - $domains = $this->getDomainsFromFile(self::ADMIN_BLACKLISTED_DOMAINS_FILE, $l); + $domains = $this->getDomainsFromFile(self::ADMIN_BLACKLISTED_DOMAINS_FILE); $cleanDomain = preg_replace('/[\x00-\x1F\x7F]/', '', $domain); $domains = array_unique(array_merge($domains, [$cleanDomain])); @@ -86,7 +86,7 @@ class DomainService { * Delete domain from admin blacklist */ public function deleteAdminBlacklistedDomain(string $domain): void { - $domains = $this->getDomainsFromFile(self::ADMIN_BLACKLISTED_DOMAINS_FILE, $l); + $domains = $this->getDomainsFromFile(self::ADMIN_BLACKLISTED_DOMAINS_FILE); $cleanDomain = preg_replace('/[\x00-\x1F\x7F]/', '', $domain); $domains = array_unique($domains); @@ -103,7 +103,7 @@ class DomainService { * Return all admin blacklist domains */ public function getAdminBlacklistedDomains(): array { - return $this->getDomainsFromFile(self::ADMIN_BLACKLISTED_DOMAINS_FILE, $l); + return $this->getDomainsFromFile(self::ADMIN_BLACKLISTED_DOMAINS_FILE); } /** * Update blacklisted domains by fetching from the external source. @@ -169,7 +169,7 @@ class DomainService { /** * Get domains from a file. */ - private function getDomainsFromFile(string $filename, IL10N $l): array { + private function getDomainsFromFile(string $filename, ?IL10N $l = null): array { try { // Attempt to get and read the file $file = $this->getDomainsFile($filename); @@ -186,7 +186,10 @@ class DomainService { } catch (\Throwable $e) { // Other errors indicate a serious issue (e.g., unreadable file) $this->logger->error("Error reading $filename: " . $e->getMessage()); - throw new \RuntimeException($l->t('The email could not be verified. Please try again later.')); + if ($l) { + throw new \RuntimeException($l->t('The email could not be verified. Please try again later.')); + } + throw new \RuntimeException("Error fetching domains from file $filename: " . $e->getMessage()); } } /** -- GitLab From 0e1cadb541ef8c30d83eaa6f14a9a4b60cefbf94 Mon Sep 17 00:00:00 2001 From: Akhil Date: Fri, 17 Oct 2025 15:17:39 +0530 Subject: [PATCH 5/6] (fix) add command to info.xml --- appinfo/info.xml | 1 + lib/Command/AdminBlacklistedDomains.php | 68 ++++++++++++++----------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index f0d156f..cda7e90 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -22,6 +22,7 @@ OCA\EmailRecovery\Command\CreatePopularDomain OCA\EmailRecovery\Command\SpamAccountDetection OCA\EmailRecovery\Command\ResetDisposableDomainsList + OCA\EmailRecovery\Command\AdminBlacklistedDomains OCA\EmailRecovery\Command\RecoveryWarningNotificationCommand diff --git a/lib/Command/AdminBlacklistedDomains.php b/lib/Command/AdminBlacklistedDomains.php index e11b2b7..b17eeea 100644 --- a/lib/Command/AdminBlacklistedDomains.php +++ b/lib/Command/AdminBlacklistedDomains.php @@ -7,6 +7,8 @@ namespace OCA\EmailRecovery\Command; use OCA\EmailRecovery\AppInfo\Application; use OCA\EmailRecovery\Service\DomainService; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -46,50 +48,54 @@ class AdminBlacklistedDomains extends Command { } protected function execute(InputInterface $input, OutputInterface $output): int { - if ($input->hasParameter('--list')) { - $this->listDomains($input, $output); - return 0; - } - if ($input->hasParameter('--add')) { - $this->addDomain($input, $output); - return 0; + try { + if ($input->getOption('list')) { + $this->listDomains($output); + return 0; + } + } catch (\Throwable $e) { + $output->writeln('Error listing admin blacklisted domains: ' . $e->getMessage()); + return 1; } - if ($input->hasParameter('--delete')) { - $this->deleteDomain(); - return 0; + try { + if ($input->getOption('add')) { + $this->addDomain($input, $output); + return 0; + } + } catch (\Throwable $e) { + $output->writeln("Error adding admin blacklisted domain: " . $e->getMessage()); + return 1; } - } - private function listDomains(InputInterface $input, OutputInterface $output): void { try { - $domains = $this->domainService->getAdminBlacklistedDomains(); - $this->writeArrayInOutputFormat($input, $output, $domains); + if ($input->getOption('delete')) { + $this->deleteDomain($input, $output); + return 0; + } } catch (\Throwable $e) { - $output->writeln('Error listing admin blacklisted domains: ' . $e->getMessage()); + $output->writeln("Error deleting admin blacklisted domain:" . $e->getMessage()); + return 1; } } + private function listDomains(OutputInterface $output): void { + $domains = $this->domainService->getAdminBlacklistedDomains(); + $output->write($domains, true); + } + private function addDomain(InputInterface $input, OutputInterface $output): void { - try { - if (!$input->getArgument('domain')) { - throw new \Exception('Domain argument is required!'); - } - $domain = $input->getArgument('domain'); - $this->domainService->addAdminBlacklistedDomain($domain); - } catch (\Throwable $e) { - $output->writeln("Error adding admin blacklisted domain $domain :" . $e->getMessage()); + if (!$input->getArgument('domain')) { + throw new \Exception('Domain argument is required!'); } + $domain = $input->getArgument('domain'); + $this->domainService->addAdminBlacklistedDomain($domain); } private function deleteDomain(InputInterface $input, OutputInterface $output): void { - try { - if (!$input->getArgument('domain')) { - throw new \Exception('Domain argument is required!'); - } - $domain = $input->getArgument('domain'); - $this->domainService->deleteAdminBlacklistedDomain($domain); - } catch (\Throwable $e) { - $output->writeln("Error deleting admin blacklisted domain $domain :" . $e->getMessage()); + if (!$input->getArgument('domain')) { + throw new \Exception('Domain argument is required!'); } + $domain = $input->getArgument('domain'); + $this->domainService->deleteAdminBlacklistedDomain($domain); } } -- GitLab From aace368c27ac070fb419a57765243267e2475341 Mon Sep 17 00:00:00 2001 From: Akhil Date: Fri, 17 Oct 2025 15:27:55 +0530 Subject: [PATCH 6/6] (docs) document the admin blacklist command --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cb8ba04..da31fe2 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,14 @@ To delete a config value: occ config:system:delete recovery_warning_configs ``` +## Blacklisting domains for recovery email + +- There is inbuilt automated blacklisting of domains in this apps based on multiple sources +- The command `occ email-recovery:admin-blacklisted-domains` can be used by an admin to manage manual blacklisting of domains + - `occ email-recovery:admin-blacklisted-domains --list` lists all the domains blacklisted by admin + - `occ email-recovery:admin-blacklisted-domains --add domain.xyz` can be used to add the domain "domain.xyz" to the admin blacklist + - `occ email-recovery:admin-blacklisted-domains --delete domain.xyz` can be used to delete the domain "domain.xyz" from the admin blacklist + ## OCC Commands The recovery warning notification system has been implemented as an OCC command for better control and reliability. @@ -143,4 +151,4 @@ occ email-recovery:recovery-warning-notification 2>> /var/log/nextcloud/recovery # Log with timestamps occ email-recovery:recovery-warning-notification 2>&1 | while IFS= read -r line; do echo "$(date '+%Y-%m-%d %H:%M:%S') $line"; done >> /var/log/nextcloud/recovery-timestamped.log -``` \ No newline at end of file +``` -- GitLab