Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 59e08d03 authored by Akhil's avatar Akhil 🙂
Browse files

Add admin blacklist separate from existing lists

parent 8bb8b6ad
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -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.
+1 −0
Original line number Diff line number Diff line
@@ -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>
+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);
	}
}
+49 −2
Original line number Diff line number Diff line
@@ -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);

		$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.
	 */
@@ -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);
@@ -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.
+1 −1
Original line number Diff line number Diff line
@@ -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.'));
		}