diff --git a/composer.json b/composer.json index e3ec881..3aaf71d 100644 --- a/composer.json +++ b/composer.json @@ -37,6 +37,7 @@ "lobsterr/newsroom_connector": "dev-master", "lobsterr/oe_newsroom_connector": "dev-master", "openeuropa/composer-artifacts": "~0.1", + "openeuropa/oe_authentication": "^1.3", "openeuropa/oe_content": "^1.11", "openeuropa/oe_corporate_blocks": "~3.0.0-beta3", "openeuropa/oe_corporate_countries": "^1.0@beta", diff --git a/config/sync/cas.settings.yml b/config/sync/cas.settings.yml new file mode 100644 index 0000000..67bf46b --- /dev/null +++ b/config/sync/cas.settings.yml @@ -0,0 +1,56 @@ +server: + version: '3.0' + protocol: https + hostname: ecas.ec.europa.eu + port: 443 + path: /cas + verify: 0 + cert: '' +gateway: + check_frequency: -2 + paths: + id: request_path + pages: '' + negate: false +forced_login: + enabled: true + paths: + id: request_path + pages: /user/login + negate: false +logout: + logout_destination: '' + enable_single_logout: false + cas_logout: true + single_logout_session_lifetime: 25 +proxy: + initialize: false + can_be_proxied: false + proxy_chains: '' +user_accounts: + prevent_normal_login: true + auto_register: true + email_assignment_strategy: 0 + email_hostname: localhost + email_attribute: '' + auto_assigned_roles: { } + restrict_password_management: true + restrict_email_management: true +error_handling: + login_failure_page: / + message_validation_failure: 'There was a problem validating your login, please contact a site administrator.' + message_no_local_account: 'You do not have an account on this website. Please contact a site administrator.' + message_subscriber_denied_reg: 'You do not have access to log in to this website. Please contact a site administrator if you believe you should have access.' + message_account_blocked: 'Your account is blocked or has not been activated. Please contact a site administrator.' + message_subscriber_denied_login: 'You do not have access to log in to this website. Please contact a site administrator if you believe you should have access.' + message_username_already_exists: 'An account on this website with your username already exists. Please contact a site administrator.' + message_prevent_normal_login: 'This account must log in using EU Login.' + message_restrict_password_management: 'The requested account is associated with EU Login and its password cannot be managed from this website.' +advanced: + debug_log: false + connection_timeout: 10 +login_link_enabled: true +login_link_label: 'EU Login' +login_success_message: 'You have been logged in.' +_core: + default_config_hash: W42TeamUFaYIcLMbvbx1hngcurDgrV8-Rh_iNsgeFJc diff --git a/config/sync/core.entity_view_display.node.cnt_description.teaser.yml b/config/sync/core.entity_view_display.node.cnt_description.teaser.yml index 442f058..d09547e 100644 --- a/config/sync/core.entity_view_display.node.cnt_description.teaser.yml +++ b/config/sync/core.entity_view_display.node.cnt_description.teaser.yml @@ -52,6 +52,7 @@ hidden: cnt_txt_sv_02: true cnt_txt_sv_03: true cnt_txt_sv_04: true + extra_field_in_page_navigation: true oe_content_content_owner: true oe_content_legacy_link: true oe_content_navigation_title: true diff --git a/config/sync/core.extension.yml b/config/sync/core.extension.yml index 6c1f3dc..606f05d 100644 --- a/config/sync/core.extension.yml +++ b/config/sync/core.extension.yml @@ -5,9 +5,11 @@ module: block: 0 block_content: 0 breakpoint: 0 + cas: 0 ckeditor: 0 cnt_description: 0 cnt_landing: 0 + cnt_user: 0 composite_reference: 0 conditional_fields: 0 config: 0 @@ -33,9 +35,9 @@ module: geofield: 0 image: 0 inline_entity_form: 0 + json_field: 0 layout_builder: 0 layout_discovery: 0 - json_field: 0 link: 0 linkit: 0 maxlength: 0 @@ -51,6 +53,7 @@ module: newsroom_connector_topic: 0 newsroom_connector_type: 0 node: 0 + oe_authentication: 0 oe_content: 0 oe_content_documents_field: 0 oe_content_entity: 0 @@ -103,6 +106,7 @@ module: menu_link_content: 1 oe_media: 1 pathauto: 1 + externalauth: 10 views: 10 paragraphs: 11 oe_profile: 1000 diff --git a/config/sync/oe_authentication.settings.yml b/config/sync/oe_authentication.settings.yml new file mode 100644 index 0000000..059dc1e --- /dev/null +++ b/config/sync/oe_authentication.settings.yml @@ -0,0 +1,7 @@ +protocol: eulogin +register_path: eim/external/register.cgi +validation_path: TicketValidationService +assurance_level: TOP +ticket_types: 'SERVICE,PROXY' +_core: + default_config_hash: QqXrdnYD_oVmsY_aKMaDLRwxQfZ8eveMW5HTuTrXY7o diff --git a/lib/modules/cnt_user/cnt_user.info.yml b/lib/modules/cnt_user/cnt_user.info.yml new file mode 100644 index 0000000..1ee8757 --- /dev/null +++ b/lib/modules/cnt_user/cnt_user.info.yml @@ -0,0 +1,9 @@ +name: 'Cnect User' +type: module +description: 'Cnect user module contains changes and configurations relative to users.' +core: 8.x +core_version_requirement: ^8 || ^9 +package: 'Cnect' +php: 7.1 +dependencies: + - oe_authentication:oe_authentication diff --git a/lib/modules/cnt_user/cnt_user.links.task.yml b/lib/modules/cnt_user/cnt_user.links.task.yml new file mode 100644 index 0000000..0560fdc --- /dev/null +++ b/lib/modules/cnt_user/cnt_user.links.task.yml @@ -0,0 +1,4 @@ +cnt_user.task: + route_name: cnt_user.canonical + base_route: entity.user.canonical + title: View diff --git a/lib/modules/cnt_user/cnt_user.module b/lib/modules/cnt_user/cnt_user.module new file mode 100644 index 0000000..9bdc229 --- /dev/null +++ b/lib/modules/cnt_user/cnt_user.module @@ -0,0 +1,48 @@ +isExternal() || $url->isRouted() && !in_array($url->getRouteName(), $routes)) { + return; + } + + // Get current path. + $current_path = \Drupal::service('path.current')->getPath(); + + // Add destination parameter to bring user back to the current page. + $variables['options']['query']['destination'] = $current_path; +} + +/** + * Implements hook_local_tasks_alter(). + */ +function cnt_user_local_tasks_alter(&$local_tasks) { + /* + Unset "View" link on user page tabs to avoid duplicated items. + + In order to force redirection to user edit page under certain conditions, + for example: + "redirect permanently until the user doesn't approve privacy policy", + replace the the array key with 'cnt_user.canonical' and + change the logic into RedirectController::postLoginRedirect(). + */ + unset($local_tasks['entity.user.canonical']); +} diff --git a/lib/modules/cnt_user/cnt_user.routing.yml b/lib/modules/cnt_user/cnt_user.routing.yml new file mode 100644 index 0000000..333760a --- /dev/null +++ b/lib/modules/cnt_user/cnt_user.routing.yml @@ -0,0 +1,19 @@ +cnt_user.login: + path: '/superuser/login' + defaults: + _form: '\Drupal\user\Form\UserLoginForm' + _title: 'Log in' + requirements: + _user_is_logged_in: 'FALSE' + options: + _maintenance_access: TRUE + +cnt_user.canonical: + path: '/user/{user}/view' + defaults: + _entity_view: 'user.full' + _title_callback: 'Drupal\user\Controller\UserController::userTitle' + requirements: + _entity_access: 'user.view' + _user_is_logged_in: 'TRUE' + user: '\d+' diff --git a/lib/modules/cnt_user/cnt_user.services.yml b/lib/modules/cnt_user/cnt_user.services.yml new file mode 100644 index 0000000..f272f5d --- /dev/null +++ b/lib/modules/cnt_user/cnt_user.services.yml @@ -0,0 +1,5 @@ +services: + cnt_user.route_subscriber: + class: Drupal\cnt_user\Routing\UserRouteSubscriber + tags: + - { name: event_subscriber } diff --git a/lib/modules/cnt_user/src/Controller/RedirectController.php b/lib/modules/cnt_user/src/Controller/RedirectController.php new file mode 100644 index 0000000..ea7b7bb --- /dev/null +++ b/lib/modules/cnt_user/src/Controller/RedirectController.php @@ -0,0 +1,202 @@ +request = $request; + $this->casHelper = $cas_helper; + $this->configFactory = $configFactory; + $this->unroutedUrlAssembler = $url_assembler; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('request_stack')->getCurrentRequest(), + $container->get('cas.helper'), + $container->get('config.factory'), + $container->get('unrouted_url_assembler') + ); + } + + /** + * Privacy policy acceptance. + */ + public function postLoginRedirect(User $user) { + // Check if the user has just been created. + // If so, redirect to user edit page. + $user->load($this->currentUser()->id()); + if ($user->getCreatedTime() == $user->getLastLoginTime()) { + $response = $this->redirect('entity.user.edit_form', ['user' => $this->currentUser()->id()]); + } + else { + $response = $this->redirect('cnt_user.canonical', ['user' => $this->currentUser()->id()]); + } + return $response; + } + + /** + * Redirect to eulogin. + */ + public function login() { + $parameters = []; + if (!empty($this->request->query->get('destination'))) { + $parameters['returnto'] = $this->request->query->get('destination'); + } + $response = $this->redirect('cas.login', $parameters); + $response->send(); + return $response; + } + + /** + * Redirects a user to the CAS path for registering. + * + * @return \Drupal\Core\Routing\TrustedRedirectResponse + * The response object. + */ + public function register(): TrustedRedirectResponse { + // @todo remove the logic once + // https://github.com/openeuropa/oe_authentication/issues/117 fixed. + if ($this->currentUser()->isAuthenticated()) { + throw new AccessDeniedHttpException(); + } + + $url = $this->getRegisterUrl()->toString(); + + $response = new TrustedRedirectResponse($url); + + $cache = new CacheableMetadata(); + $cache->addCacheContexts(['user.roles:anonymous']); + $response->addCacheableDependency($cache); + $response->send(); + return $response; + } + + /** + * Converts the passed in destination into an absolute URL. + * + * @param string $destination + * The path for the destination. In case it starts with a slash it should + * have the base path included already. + * + * @return string + * The destination as absolute URL. + */ + protected function getDestinationAsAbsoluteUrl($destination) { + if (!UrlHelper::isExternal($destination)) { + // The destination query parameter can be a relative URL in the sense of + // not including the scheme and host, but its path is expected to be + // absolute (start with a '/'). For such a case, prepend the scheme and + // host, because the 'Location' header must be absolute. + if (strpos($destination, '/') === 0) { + $destination = $this->request->getSchemeAndHttpHost() . $destination; + } + else { + // Legacy destination query parameters can be internal paths that have + // not yet been converted to URLs. + $destination = UrlHelper::parse($destination); + $uri = 'base:' . $destination['path']; + $options = [ + 'query' => $destination['query'], + 'fragment' => $destination['fragment'], + 'absolute' => TRUE, + ]; + // Treat this as if it's user input of a path relative to the site's + // base URL. + $destination = $this->unroutedUrlAssembler->assemble($uri, $options); + } + } + return $destination; + } + + /** + * Get the register URL. + * + * @return \Drupal\Core\Url + * The register URL. + */ + public function getRegisterUrl(): Url { + $config = $this->configFactory->get('oe_authentication.settings'); + $base_url = $this->casHelper->getServerBaseUrl(); + $path = $config->get('register_path'); + + $destination = $this->request->query->get('destination'); + if (!empty($destination)) { + $destination_path = $this->getDestinationAsAbsoluteUrl($destination); + // Remove destination parameter to avoid redirect. + $this->request->query->remove('destination'); + } + else { + $service = Url::fromRoute(''); + $service->setAbsolute(); + // We need to ensure we are collecting the cache metadata so it doesn't + // bubble up to the render context or we get a Logic exception later + // when we return a Cacheable response. + $service = $service->toString(TRUE); + $destination_path = $service->getGeneratedUrl(); + } + + return Url::fromUri($base_url . $path, ['query' => ['service' => $destination_path]]); + } + +} diff --git a/lib/modules/cnt_user/src/Routing/UserRouteSubscriber.php b/lib/modules/cnt_user/src/Routing/UserRouteSubscriber.php new file mode 100644 index 0000000..7a825b5 --- /dev/null +++ b/lib/modules/cnt_user/src/Routing/UserRouteSubscriber.php @@ -0,0 +1,44 @@ +get('user.login')) { + $route->setDefault('_controller', '\Drupal\cnt_user\Controller\RedirectController::login'); + $route->setOption('no_cache', TRUE); + } + + if ($route = $collection->get('user.register')) { + $route->setDefault('_controller', '\Drupal\cnt_user\Controller\RedirectController::register'); + $route->setOption('no_cache', TRUE); + } + + if ($route = $collection->get('entity.user.canonical')) { + $route->setDefault('_controller', '\Drupal\cnt_user\Controller\RedirectController::postLoginRedirect'); + $route->setOption('no_cache', TRUE); + } + } + +}