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

Commit 621fc1b3 authored by Fahim Salam Chowdhury's avatar Fahim Salam Chowdhury 👽
Browse files

Merge branch 'main' into dev/nc-28

parents a2b2476f f94abf4c
Loading
Loading
Loading
Loading
Loading
+13 −1
Original line number Diff line number Diff line
@@ -14,6 +14,18 @@

- This plugin creates an endpoint `/apps/ecloud-accounts/api/set_account_data` that is to be used to set user's email, quota,recovery email and create the user's folder if necessary

### Captcha Configuration for user account creation

- Simple image based captcha is the default for human verification
- To change the value, set `ecloud-accounts.captcha_provider` 
  - Allowed values are `image` (default) and `hcaptcha` (https://hcaptcha.com)

#### HCaptcha Configuration

- For hcaptcha provider to work, set the following values correctly:
  - `ecloud-accounts.hcaptcha_site_key`
  - `ecloud-accounts.hcaptcha_secret`

## Drop account

- The drop account functionality plugin works in conjunction with the drop_account plugin : https://apps.nextcloud.com/apps/drop_account
@@ -25,7 +37,7 @@

Please open issues here : https://gitlab.e.foundation/e/backlog/issues

## Dependancies
## Dependencies

This plugin works in cunjunction with the drop_account plugin : https://apps.nextcloud.com/apps/drop_account

+1 −1
Original line number Diff line number Diff line
@@ -10,7 +10,7 @@
    <description><![CDATA[in /e/OS cloud, nextcloud accounts are linked to mail accounts. This app ensures both are coordinated: it sets the e-mail address, quota and storage of the user upon creation.
    It also completes the account deletion by cleaning other parts of the /e/OS cloud setup to ensure no more data is retained when a user requests an account deletion.
    This app uses the UserDeletedEvent to invoke scripts in the docker-welcome container of /e/OS cloud setup]]></description>
    <version>7.0.0</version>
    <version>7.0.2</version>
    <licence>agpl</licence>
    <author mail="dev@murena.com" homepage="https://murena.com/">Murena SAS</author>
    <namespace>EcloudAccounts</namespace>
+1 −1
Original line number Diff line number Diff line
@@ -10,7 +10,7 @@ return ['routes' => [
	['name' => 'shop_account#check_shop_email_post_delete', 'url' => '/shop-accounts/check_shop_email_post_delete', 'verb' => 'GET'],
	[
		'name' => 'user#preflighted_cors', 'url' => '/api/{path}',
		'verb' => 'OPTIONS', 'requirements' => array('path' => '.+')
		'verb' => 'OPTIONS', 'requirements' => ['path' => '.+']
	],
	[
		'name' => 'beta_user#remove_user_in_group',
+3 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ use OCA\EcloudAccounts\Listeners\BeforeUserDeletedListener;
use OCA\EcloudAccounts\Listeners\PasswordUpdatedListener;
use OCA\EcloudAccounts\Listeners\TwoFactorStateChangedListener;
use OCA\EcloudAccounts\Listeners\UserChangedListener;
use OCA\EcloudAccounts\Middleware\AccountMiddleware;
use OCA\EcloudAccounts\Service\LDAPConnectionService;
use OCA\TwoFactorTOTP\Event\StateChanged;
use OCP\AppFramework\App;
@@ -56,6 +57,8 @@ class Application extends App implements IBootstrap {
		$context->registerEventListener(UserChangedEvent::class, UserChangedListener::class);
		$context->registerEventListener(StateChanged::class, TwoFactorStateChangedListener::class);
		$context->registerEventListener(PasswordUpdatedEvent::class, PasswordUpdatedListener::class);
	
		$context->registerMiddleware(AccountMiddleware::class);
	}

	public function boot(IBootContext $context): void {
+78 −15
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ use OCA\EcloudAccounts\Exception\AddUsernameToCommonStoreException;
use OCA\EcloudAccounts\Exception\LDAPUserCreationException;
use OCA\EcloudAccounts\Exception\RecoveryEmailValidationException;
use OCA\EcloudAccounts\Service\CaptchaService;
use OCA\EcloudAccounts\Service\HCaptchaService;
use OCA\EcloudAccounts\Service\NewsLetterService;
use OCA\EcloudAccounts\Service\UserService;
use OCP\AppFramework\Controller;
@@ -19,6 +20,7 @@ use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IRequest;
@@ -33,14 +35,21 @@ class AccountController extends Controller {
	private $userService;
	private $newsletterService;
	private $captchaService;
	private HCaptchaService $hCaptchaService;
	protected $l10nFactory;
	private $session;
	private $userSession;
	private $urlGenerator;
	/** @var IConfig */
	private IConfig $config;
	private IInitialState $initialState;
	private const SESSION_USERNAME_CHECK = 'username_check_passed';
	private const CAPTCHA_VERIFIED_CHECK = 'captcha_verified';
	private const ALLOWED_CAPTCHA_PROVIDERS = ['image', 'hcaptcha'];
	private const DEFAULT_CAPTCHA_PROVIDER = 'image';
	private const HCAPTCHA_PROVIDER = 'hcaptcha';
	private const HCAPTCHA_DOMAINS = ['https://hcaptcha.com', 'https://*.hcaptcha.com'];

	private ILogger $logger;
	public function __construct(
		$AppName,
@@ -48,18 +57,21 @@ class AccountController extends Controller {
		UserService $userService,
		NewsLetterService $newsletterService,
		CaptchaService $captchaService,
		HCaptchaService $hCaptchaService,
		IFactory $l10nFactory,
		IUserSession $userSession,
		IURLGenerator $urlGenerator,
		ISession $session,
		IConfig $config,
		ILogger $logger
		ILogger $logger,
		IInitialState $initialState
	) {
		parent::__construct($AppName, $request);
		$this->appName = $AppName;
		$this->userService = $userService;
		$this->newsletterService = $newsletterService;
		$this->captchaService = $captchaService;
		$this->hCaptchaService = $hCaptchaService;
		$this->l10nFactory = $l10nFactory;
		$this->session = $session;
		$this->userSession = $userSession;
@@ -67,6 +79,7 @@ class AccountController extends Controller {
		$this->urlGenerator = $urlGenerator;
		$this->logger = $logger;
		$this->request = $request;
		$this->initialState = $initialState;
	}

	/**
@@ -83,13 +96,31 @@ class AccountController extends Controller {
		}

		$_SERVER['HTTP_ACCEPT_LANGUAGE'] = $lang;
		$this->initialState->provideInitialState('lang', $lang);
		
		return new TemplateResponse(
		$response = new TemplateResponse(
			Application::APP_ID,
			'signup',
			['appName' => Application::APP_ID, 'lang' => $lang],
			TemplateResponse::RENDER_AS_GUEST
		);

		$captchaProvider = $this->getCaptchaProvider();
		$this->initialState->provideInitialState('captchaProvider', $captchaProvider);
		
		if ($captchaProvider === self::HCAPTCHA_PROVIDER) {
			$csp = $response->getContentSecurityPolicy();
			foreach (self::HCAPTCHA_DOMAINS as $domain) {
				$csp->addAllowedScriptDomain($domain);
				$csp->addAllowedFrameDomain($domain);
				$csp->addAllowedStyleDomain($domain);
				$csp->addAllowedConnectDomain($domain);
			}
			$response->setContentSecurityPolicy($csp);
			$hcaptchaSiteKey = $this->config->getSystemValue(Application::APP_ID . '.hcaptcha_site_key');
			$this->initialState->provideInitialState('hCaptchaSiteKey', $hcaptchaSiteKey);
		}
		return $response;
	}
	
	/**
@@ -143,7 +174,7 @@ class AccountController extends Controller {
			$mainDomain = $this->userService->getMainDomain();
			$userEmail = $username.'@'.$mainDomain;
			$this->userService->registerUser($displayname, $recoveryEmail, $username, $userEmail, $password, $language);
			sleep(2);
			sleep(5);

			$this->userService->setAccountDataLocally($username, $userEmail);
			$this->userService->createHMEAlias($username, $userEmail);
@@ -194,7 +225,7 @@ class AccountController extends Controller {
	 *
	 * @return string|null If validation fails, a string describing the error; otherwise, null.
	 */
	public function validateInput(string $inputName, string $value, int $maxLength = null) : ?string {
	private function validateInput(string $inputName, string $value, ?int $maxLength = null) : ?string {
		if ($value === '') {
			return "$inputName is required.";
		}
@@ -245,10 +276,15 @@ class AccountController extends Controller {
	 * @NoCSRFRequired
	 */
	public function captcha(): Http\DataDisplayResponse {
		$captchaValue = $this->captchaService->generateCaptcha();
		// Don't allow requests to image captcha if different provider is set
		if ($this->getCaptchaProvider() !== self::DEFAULT_CAPTCHA_PROVIDER) {
			$response = new DataResponse();
			$response->setStatus(400);
			return $response;
		}

		$captchaValue = $this->captchaService->generateCaptcha();
		$response = new Http\DataDisplayResponse($captchaValue, Http::STATUS_OK, ['Content-Type' => 'image/png']);

		return $response;
	}
	/**
@@ -258,24 +294,51 @@ class AccountController extends Controller {
	 * @PublicPage
	 * @NoCSRFRequired
	 *
	 * @param string $captchaInput The user-provided human verification input.
	 * @param string $token The user-provided human verification input.
	 * @param string $bypassToken Token to bypass captcha for automation testing
	 *
	 * @return \OCP\AppFramework\Http\DataResponse
	 */
	public function verifyCaptcha(string $captchaInput = '', string $bypassToken = '') : DataResponse {
	public function verifyCaptcha(string $userToken = '', string $bypassToken = '') : DataResponse {
		$response = new DataResponse();
		$captchaToken = $this->config->getSystemValue('bypass_captcha_token', '');
		// Initialize the default status to 400 (Bad Request)
		$response->setStatus(400);
		// Check if the input matches the bypass token or the stored captcha result
		$captchaResult = (string) $this->session->get(CaptchaService::CAPTCHA_RESULT_KEY, '');
		if ((!empty($captchaToken) && $bypassToken === $captchaToken) || (!empty($captchaResult) && $captchaInput === $captchaResult)) {

		// Check if the input matches the bypass token
		$bypassTokenInConfig = $this->config->getSystemValue('bypass_captcha_token', '');
		if ((!empty($bypassTokenInConfig) && $bypassToken === $bypassTokenInConfig)) {
			$this->session->set(self::CAPTCHA_VERIFIED_CHECK, true);
			$response->setStatus(200);
		}

		$response->setStatus(400);
		$captchaProvider = $this->getCaptchaProvider();

		// Check for default captcha provider
		if ($captchaProvider === self::DEFAULT_CAPTCHA_PROVIDER && $this->verifyImageCaptcha($userToken)) {
			$this->session->set(self::CAPTCHA_VERIFIED_CHECK, true);
			$this->session->remove(CaptchaService::CAPTCHA_RESULT_KEY);
			$response->setStatus(200);
		}

		// Check for hcaptcha provider
		if ($captchaProvider === self::HCAPTCHA_PROVIDER && $this->hCaptchaService->verify($userToken)) {
			$this->session->set(self::CAPTCHA_VERIFIED_CHECK, true);
			$response->setStatus(200);
		}
		return $response;
	}

	private function verifyImageCaptcha(string $captchaInput = '') : bool {
		$captchaResult = (string) $this->session->get(CaptchaService::CAPTCHA_RESULT_KEY, '');
		return (!empty($captchaResult) && $captchaInput === $captchaResult);
	}

	private function getCaptchaProvider() : string {
		$captchaProvider = $this->config->getSystemValue('ecloud-accounts.captcha_provider', self::DEFAULT_CAPTCHA_PROVIDER);

		if (!in_array($captchaProvider, self::ALLOWED_CAPTCHA_PROVIDERS)) {
			$captchaProvider = self::DEFAULT_CAPTCHA_PROVIDER;
		}
		return $captchaProvider;
	}

}
Loading