From 7df045751c57e17325eae7f76912a02dd376516e Mon Sep 17 00:00:00 2001 From: Claudiu Cristea <clau.cristea@gmail.com> Date: Sun, 4 Dec 2022 12:20:48 +0100 Subject: [PATCH] ISAICP-7477: Temporary site registration disabling. --- config/sync/user.role.moderator.yml | 3 +- tests/features/authentication.feature | 5 +- tests/features/eulogin/eulogin.feature | 47 ++++++++++- .../dashboard/dashboard.permissions.yml | 2 + .../custom/dashboard/dashboard.routing.yml | 8 ++ .../src/Controller/DashboardController.php | 4 + .../src/Form/DisableRegistrationForm.php | 72 +++++++++++++++++ .../joinup_eulogin.services.yml | 2 +- .../JoinupEuLoginCasEventsSubscriber.php | 79 ++++++++++--------- 9 files changed, 179 insertions(+), 43 deletions(-) create mode 100644 web/modules/custom/dashboard/src/Form/DisableRegistrationForm.php diff --git a/config/sync/user.role.moderator.yml b/config/sync/user.role.moderator.yml index 9454584b9a..e4360ebe05 100644 --- a/config/sync/user.role.moderator.yml +++ b/config/sync/user.role.moderator.yml @@ -115,8 +115,8 @@ permissions: - 'create paragraph content image' - 'create paragraph content json_map' - 'create paragraph content row' - - 'create paragraph content testimony' - 'create paragraph content simple_paragraph' + - 'create paragraph content testimony' - 'create paragraph content video' - 'create rdf entity news' - 'create solution_banner media' @@ -147,6 +147,7 @@ permissions: - 'delete paragraph content testimony' - 'delete paragraph content video' - 'delete site alerts' + - 'disable site auto-registration' - 'download subscribers report' - 'edit any collection_banner media' - 'edit any collection_logo media' diff --git a/tests/features/authentication.feature b/tests/features/authentication.feature index b321823260..bfed0128a8 100644 --- a/tests/features/authentication.feature +++ b/tests/features/authentication.feature @@ -43,6 +43,7 @@ Feature: User authentication | admin/structure/media | | announcements | | dashboard | + | dashboard/disable-registration | | licence | | licence/add | | media/add | @@ -125,6 +126,7 @@ Feature: User authentication | admin/structure/media | | announcements | | dashboard | + | dashboard/disable-registration | | licence | | licence/add | | media/add | @@ -185,8 +187,9 @@ Feature: User authentication | admin/reporting/solutions-by-type | | admin/reporting/subscribers-report | | admin/structure/compatibility-document | - | announcements | + | announcements | | dashboard | + | dashboard/disable-registration | | licence | | licence/add | | media/add | diff --git a/tests/features/eulogin/eulogin.feature b/tests/features/eulogin/eulogin.feature index e7f30cc9a3..e1e35fb856 100644 --- a/tests/features/eulogin/eulogin.feature +++ b/tests/features/eulogin/eulogin.feature @@ -132,9 +132,9 @@ Feature: Log in through EU Login And I should see the following lines of text: | Account information | | Your name and E-mail are inherited from EU Login. To update this information, you can visit your EU Login account page. Synchronisation will take a few minutes and it will be visible the next time you login on Joinup. | - | Your e-mail address is not made public. We will only send you necessary system notifications and you can opt in later if you wish to receive additional notifications about content you are subscribed to. | - | Your first name is publicly visible. | - | Your last name is publicly visible. | + | Your e-mail address is not made public. We will only send you necessary system notifications and you can opt in later if you wish to receive additional notifications about content you are subscribed to. | + | Your first name is publicly visible. | + | Your last name is publicly visible. | When I press "Save" Then I should see the success message "The changes have been saved." @@ -295,7 +295,7 @@ Feature: Log in through EU Login Then I should see the success message "You have been logged in." # The email ends up getting the upstream email so that correct character casing is applied. And the user joe should have the following data in their user profile: - | E-mail | Joe_Case_Insensitive@example.com | + | E-mail | Joe_Case_Insensitive@example.com | Scenario: Anonymous user is asked to log in when accessing a protected page Given users: @@ -328,3 +328,42 @@ Feature: Log in through EU Login Given I am logged in as an "authenticated user" When I go to "/user/password" Then the response status code should be 404 + + Scenario: As a moderator I can temporary disable the site registration. + Given CAS users: + | Username | E-mail | Password | + | joe_doe | joe@example.com | 123 | + And users: + | Username | Roles | + | mod | moderator | + + Given I am logged in as mod + And I visit "Dashboard" + When I click "Disable registration" + And I check "Disable new accounts registration" + And I press "Submit" + Then I should see the warning message "Registering new users to Joinup is disabled" + + Given I am an anonymous user + And I am on the homepage + When I click "Sign in" + When I fill in "E-mail address" with "joe@example.com" + When I fill in "Password" with "123" + And I press "Log in" + Then I should see the error message "Registering new accounts to Joinup is temporary disabled. Please come back later." + # Check that the user is not logged in. + And I should see the link "Sign in" + + Given I am logged in as mod + And I visit "/dashboard/disable-registration" + And I uncheck "Disable new accounts registration" + And I press "Submit" + Then I should see the success message "Registering new users to Joinup is enabled" + + Given I am an anonymous user + And I am on the homepage + When I click "Sign in" + When I fill in "E-mail address" with "joe@example.com" + When I fill in "Password" with "123" + And I press "Log in" + Then I should see the success message "Fill in the fields below to let the Joinup community learn more about you!" diff --git a/web/modules/custom/dashboard/dashboard.permissions.yml b/web/modules/custom/dashboard/dashboard.permissions.yml index 42e88eb3d6..0415dc08d6 100644 --- a/web/modules/custom/dashboard/dashboard.permissions.yml +++ b/web/modules/custom/dashboard/dashboard.permissions.yml @@ -1,2 +1,4 @@ access dashboard: title: 'Access the dashboard' +disable site auto-registration: + title: 'Disable site auto-registration' diff --git a/web/modules/custom/dashboard/dashboard.routing.yml b/web/modules/custom/dashboard/dashboard.routing.yml index 00f2bf7b19..e0e4ac283d 100644 --- a/web/modules/custom/dashboard/dashboard.routing.yml +++ b/web/modules/custom/dashboard/dashboard.routing.yml @@ -5,3 +5,11 @@ dashboard.page: _title: 'Dashboard' requirements: _permission: 'access dashboard' + +dashboard.disable_registration: + path: '/dashboard/disable-registration' + defaults: + _form: Drupal\dashboard\Form\DisableRegistrationForm + _title: Disable site registration + requirements: + _permission: 'disable site auto-registration' diff --git a/web/modules/custom/dashboard/src/Controller/DashboardController.php b/web/modules/custom/dashboard/src/Controller/DashboardController.php index 14f78cd9c5..1e787b0230 100644 --- a/web/modules/custom/dashboard/src/Controller/DashboardController.php +++ b/web/modules/custom/dashboard/src/Controller/DashboardController.php @@ -56,6 +56,10 @@ public function page() { 'title' => $this->t('Curated content listings'), 'url' => Url::fromRoute('view.curated_content_listings.page'), ], + 'disable_registration' => [ + 'title' => $this->t('Disable registration'), + 'url' => Url::fromRoute('dashboard.disable_registration'), + ], ]; $links = array_map(function (array $link): array { diff --git a/web/modules/custom/dashboard/src/Form/DisableRegistrationForm.php b/web/modules/custom/dashboard/src/Form/DisableRegistrationForm.php new file mode 100644 index 0000000000..e47b1b9027 --- /dev/null +++ b/web/modules/custom/dashboard/src/Form/DisableRegistrationForm.php @@ -0,0 +1,72 @@ +<?php + +declare(strict_types = 1); + +namespace Drupal\dashboard\Form; + +use Drupal\Core\Form\FormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\State\StateInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Allows disabling the site CAS registration. + */ +class DisableRegistrationForm extends FormBase { + + /** + * Constructs a new form instance. + * + * @param \Drupal\Core\State\StateInterface $state + * The state service. + */ + public function __construct(protected StateInterface $state) { + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container): self { + return new static($container->get('state')); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state): array { + $form['disabled'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Disable new accounts registration'), + '#default_value' => $this->state->get('joinup_eulogin.registration_disabled', FALSE), + ]; + $form['actions'] = [ + '#type' => 'actions', + 'submit' => [ + '#type' => 'submit', + '#value' => $this->t('Submit'), + ], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state): void { + if ($form_state->getValue('disabled')) { + $this->state->set('joinup_eulogin.registration_disabled', TRUE); + $this->messenger()->addWarning($this->t('Registering new users to Joinup is disabled')); + return; + } + $this->state->delete('joinup_eulogin.registration_disabled'); + $this->messenger()->addStatus($this->t('Registering new users to Joinup is enabled')); + } + + /** + * {@inheritdoc} + */ + public function getFormId(): string { + return 'dashboard_disable_registration'; + } + +} diff --git a/web/modules/custom/joinup_eulogin/joinup_eulogin.services.yml b/web/modules/custom/joinup_eulogin/joinup_eulogin.services.yml index c875cf04cf..a8bd6aa2c5 100644 --- a/web/modules/custom/joinup_eulogin/joinup_eulogin.services.yml +++ b/web/modules/custom/joinup_eulogin/joinup_eulogin.services.yml @@ -1,7 +1,7 @@ services: joinup_eulogin.cas.subscriber: class: Drupal\joinup_eulogin\Event\Subscriber\JoinupEuLoginCasEventsSubscriber - arguments: ['@config.factory', '@user.data', '@entity_type.manager', '@request_stack', '@messenger'] + arguments: ['@config.factory', '@user.data', '@entity_type.manager', '@request_stack', '@messenger', '@state'] tags: - { name: 'event_subscriber' } joinup_eulogin.route.subscriber: diff --git a/web/modules/custom/joinup_eulogin/src/Event/Subscriber/JoinupEuLoginCasEventsSubscriber.php b/web/modules/custom/joinup_eulogin/src/Event/Subscriber/JoinupEuLoginCasEventsSubscriber.php index 49e594867c..165dac01c2 100644 --- a/web/modules/custom/joinup_eulogin/src/Event/Subscriber/JoinupEuLoginCasEventsSubscriber.php +++ b/web/modules/custom/joinup_eulogin/src/Event/Subscriber/JoinupEuLoginCasEventsSubscriber.php @@ -9,6 +9,7 @@ use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Messenger\MessengerInterface; +use Drupal\Core\State\StateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Url; use Drupal\cas\Event\CasPostLoginEvent; @@ -40,47 +41,38 @@ class JoinupEuLoginCasEventsSubscriber implements EventSubscriberInterface { */ protected ImmutableConfig $casSettings; - /** - * The user data service. - */ - protected UserDataInterface $userData; - - /** - * The entity type manager service. - */ - protected EntityTypeManagerInterface $entityTypeManager; - /** * The current request. */ protected Request $currentRequest; - /** - * The messenger service. - */ - protected MessengerInterface $messenger; - /** * Constructs a new event subscriber instance. * - * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory * The config factory service. - * @param \Drupal\user\UserDataInterface $user_data + * @param \Drupal\user\UserDataInterface $userData * The user data service. - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager * The entity type manager service. - * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack + * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack * The request stack. * @param \Drupal\Core\Messenger\MessengerInterface $messenger * The messenger service. + * @param \Drupal\Core\State\StateInterface $state + * The state service. */ - public function __construct(ConfigFactoryInterface $config_factory, UserDataInterface $user_data, EntityTypeManagerInterface $entity_type_manager, RequestStack $request_stack, MessengerInterface $messenger) { - $this->settings = $config_factory->get('joinup_eulogin.settings'); - $this->casSettings = $config_factory->get('cas.settings'); - $this->userData = $user_data; - $this->entityTypeManager = $entity_type_manager; - $this->currentRequest = $request_stack->getCurrentRequest(); - $this->messenger = $messenger; + public function __construct( + ConfigFactoryInterface $configFactory, + protected UserDataInterface $userData, + protected EntityTypeManagerInterface $entityTypeManager, + RequestStack $requestStack, + protected MessengerInterface $messenger, + protected StateInterface $state, + ) { + $this->settings = $configFactory->get('joinup_eulogin.settings'); + $this->casSettings = $configFactory->get('cas.settings'); + $this->currentRequest = $requestStack->getCurrentRequest(); } /** @@ -91,6 +83,7 @@ public static function getSubscribedEvents(): array { CasHelper::EVENT_PRE_VALIDATE => 'alterValidationUrl', CasHelper::EVENT_POST_VALIDATE => 'prepareAttributes', CasHelper::EVENT_PRE_REGISTER => [ + ['disableRegistration', 1000], ['markAsNewAccountRegistration'], ['handleDrupalUsernameCollision'], ['preventRegisterEmailCollision'], @@ -141,6 +134,20 @@ public function prepareAttributes(CasPostValidateEvent $event): void { }; } + /** + * Disables the CAS automatic registration based on a kill-switch. + * + * @param \Drupal\cas\Event\CasPreRegisterEvent $event + * The CAS pre-register event. + */ + public function disableRegistration(CasPreRegisterEvent $event): void { + if ($this->state->get('joinup_eulogin.registration_disabled', FALSE)) { + $event + ->cancelAutomaticRegistration($this->t('Registering new accounts to Joinup is temporary disabled. Please come back later.')) + ->stopPropagation(); + } + } + /** * Marks this login as being preceded by account registration. * @@ -166,12 +173,12 @@ public function handleDrupalUsernameCollision(CasPreRegisterEvent $event): void return; } - $new_name = $name = $event->getDrupalUsername(); - while (user_load_by_name($new_name)) { - $new_name = $this->buildUserName($name); + $newName = $name = $event->getDrupalUsername(); + while (user_load_by_name($newName)) { + $newName = $this->buildUserName($name); } - $event->setDrupalUsername($new_name); + $event->setDrupalUsername($newName); } /** @@ -191,15 +198,15 @@ public function preventRegisterEmailCollision(CasPreRegisterEvent $event): void throw new \InvalidArgumentException("Unexpected empty 'mail' property value."); } - $same_mail_accounts = $this->entityTypeManager->getStorage('user')->loadByProperties([ + $sameMailAccounts = $this->entityTypeManager->getStorage('user')->loadByProperties([ 'mail' => $mail, ]); - if ($same_mail_accounts) { + if ($sameMailAccounts) { $event->cancelAutomaticRegistration($this->t('While trying to register your account with Joinup, we found that your EU Login email address %mail is already in use on our site. Either change your EU Login email address or contact the <a href=":contact">Joinup support</a> if you feel that something is wrong.', [ '%mail' => $mail, ':contact' => Url::fromRoute('contact_form.contact_page')->toString(), - ])); + ]))->stopPropagation(); } } @@ -223,15 +230,15 @@ public function preventRegisterEmailCollision(CasPreRegisterEvent $event): void */ public function handlePotentialMailCollision(CasPreLoginEvent $event): void { $account = $event->getAccount(); - $eulogin_email = $event->getCasPropertyBag()->getAttribute('email'); + $euloginEmail = $event->getCasPropertyBag()->getAttribute('email'); // A new email has been configured upstream, on the EU Login account. If the // account was linked manually, there is a chance that the email stored // locally does not share the same case sensitivity as the upstream account. // In these cases, allow to proceed so that the local account can retrieve // the correct case sensitivity email. - if (strtolower($account->getEmail()) !== strtolower($eulogin_email)) { - if (user_load_by_mail($eulogin_email)) { + if (strtolower($account->getEmail()) !== strtolower($euloginEmail)) { + if (user_load_by_mail($euloginEmail)) { $event->cancelLogin($this->t("You've recently changed your EU Login account email but that email is already used in Joinup by another user. You cannot login until, either you change your EU Login email or you <a href=':url'>contact support</a> to fix the issue.", [ ':url' => Url::fromRoute('contact_form.contact_page')->setAbsolute()->toString(), ])); -- GitLab