Loading README.md +9 −1 Original line number Diff line number Diff line Loading @@ -61,6 +61,14 @@ To delete a config value: occ config:system:delete recovery_warning_configs <key> ``` ## 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. Loading appinfo/info.xml +1 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ <command>OCA\EmailRecovery\Command\CreatePopularDomain</command> <command>OCA\EmailRecovery\Command\SpamAccountDetection</command> <command>OCA\EmailRecovery\Command\ResetDisposableDomainsList</command> <command>OCA\EmailRecovery\Command\AdminBlacklistedDomains</command> <command>OCA\EmailRecovery\Command\RecoveryWarningNotificationCommand</command> </commands> </info> lib/Command/AdminBlacklistedDomains.php 0 → 100644 +101 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); 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; class AdminBlacklistedDomains extends Command { public function __construct(private DomainService $domainService) { parent::__construct(); } protected function configure() { $this ->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 { try { if ($input->getOption('list')) { $this->listDomains($output); return 0; } } catch (\Throwable $e) { $output->writeln('Error listing admin blacklisted domains: ' . $e->getMessage()); return 1; } 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; } try { if ($input->getOption('delete')) { $this->deleteDomain($input, $output); return 0; } } catch (\Throwable $e) { $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 { 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 { if (!$input->getArgument('domain')) { throw new \Exception('Domain argument is required!'); } $domain = $input->getArgument('domain'); $this->domainService->deleteAdminBlacklistedDomain($domain); } } lib/Service/DomainService.php +49 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); $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); $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); } /** * Update blacklisted domains by fetching from the external source. */ Loading Loading @@ -125,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); Loading @@ -142,8 +186,11 @@ class DomainService { } catch (\Throwable $e) { // Other errors indicate a serious issue (e.g., unreadable file) $this->logger->error("Error reading $filename: " . $e->getMessage()); 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()); } } /** * Save domains to a file. Loading lib/Service/RecoveryEmailService.php +1 −1 Original line number Diff line number Diff line Loading @@ -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.')); } Loading Loading
README.md +9 −1 Original line number Diff line number Diff line Loading @@ -61,6 +61,14 @@ To delete a config value: occ config:system:delete recovery_warning_configs <key> ``` ## 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. Loading
appinfo/info.xml +1 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ <command>OCA\EmailRecovery\Command\CreatePopularDomain</command> <command>OCA\EmailRecovery\Command\SpamAccountDetection</command> <command>OCA\EmailRecovery\Command\ResetDisposableDomainsList</command> <command>OCA\EmailRecovery\Command\AdminBlacklistedDomains</command> <command>OCA\EmailRecovery\Command\RecoveryWarningNotificationCommand</command> </commands> </info>
lib/Command/AdminBlacklistedDomains.php 0 → 100644 +101 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); 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; class AdminBlacklistedDomains extends Command { public function __construct(private DomainService $domainService) { parent::__construct(); } protected function configure() { $this ->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 { try { if ($input->getOption('list')) { $this->listDomains($output); return 0; } } catch (\Throwable $e) { $output->writeln('Error listing admin blacklisted domains: ' . $e->getMessage()); return 1; } 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; } try { if ($input->getOption('delete')) { $this->deleteDomain($input, $output); return 0; } } catch (\Throwable $e) { $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 { 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 { if (!$input->getArgument('domain')) { throw new \Exception('Domain argument is required!'); } $domain = $input->getArgument('domain'); $this->domainService->deleteAdminBlacklistedDomain($domain); } }
lib/Service/DomainService.php +49 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); $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); $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); } /** * Update blacklisted domains by fetching from the external source. */ Loading Loading @@ -125,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); Loading @@ -142,8 +186,11 @@ class DomainService { } catch (\Throwable $e) { // Other errors indicate a serious issue (e.g., unreadable file) $this->logger->error("Error reading $filename: " . $e->getMessage()); 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()); } } /** * Save domains to a file. Loading
lib/Service/RecoveryEmailService.php +1 −1 Original line number Diff line number Diff line Loading @@ -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.')); } Loading