diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 32b5c1779..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,102 +0,0 @@ -version: 2 -aliases: - - &ssh_key_fingerprint "36:03:e3:ca:b3:0b:82:18:e2:e9:ae:5d:81:17:86:b1" - # Fingerprint of the SSH deploy key of the project used to pull code. - # The value can be found in CircleCI UI -> SSH Permissions. - - - &step_configure_git - run: - name: Configure git - command: | - git config --global user.email "$DEPLOY_USER_EMAIL" && git config --global user.name "$DEPLOY_USER_NAME" - - # Re-usable job to run different types of builds. - - &job-build - working_directory: &working-directory /app - docker: - - image: &builder-image singledigital/bay-ci-builder:5.x - environment: - INSTALL_NEW_SITE: 1 - LAGOON_ENVIRONMENT_TYPE: ci - steps: - - attach_workspace: - at: /workspace - - checkout - # Init environment for development. - - run: if [ -f "./dev-tools.sh" ] && [ ! "$DEV_TOOLS" ]; then ./dev-tools.sh; fi - - setup_remote_docker: - docker_layer_caching: true - - run: .circleci/build.sh - - run: .circleci/test.sh - - run: - name: Copy artifacts - command: .circleci/test-artifacts.sh - when: always - - store_artifacts: - path: /tmp/artifacts - - # Job to perform merge to reference branch after a merge to develop. - - &merge-to-reference - working_directory: *working-directory - docker: - - image: *builder-image - auth: - username: $DOCKERHUB_USERNAME - password: $DOCKERHUB_PASSWORD - environment: - LAGOON_ENVIRONMENT_TYPE: ci - SSH_KEY_FINGERPRINT: *ssh_key_fingerprint - DEPLOY_USER_EMAIL: sdp.devs@dpc.vic.gov.au - DEPLOY_USER_NAME: sdpdeploy - steps: - - attach_workspace: - at: /workspace - - checkout - - *step_configure_git - - setup_remote_docker: - docker_layer_caching: true - - add_ssh_keys: - fingerprints: - - *ssh_key_fingerprint - - run: - name: Merge to reference branch - command: .circleci/merge-to-reference.sh - no_output_timeout: 30m - - -jobs: - build: - <<: *job-build - - build_suggest: - <<: *job-build - docker: - - image: *builder-image - environment: - INSTALL_NEW_SITE: 1 - LAGOON_ENVIRONMENT_TYPE: ci - INSTALL_SUGGEST: 1 - BEHAT_PROFILE: "--profile=suggest" - - merge_to_reference: - <<: *merge-to-reference - - -workflows: - version: 2 - main: - jobs: - - build - - build_suggest: - filters: - branches: - ignore: - - reference - - mergetoreference: - jobs: - - merge_to_reference: - filters: - branches: - only: - - develop diff --git a/.github/workflows/pull-request-reviewer-reminder.yml b/.github/workflows/pull-request-reviewer-reminder.yml new file mode 100644 index 000000000..47188d8a2 --- /dev/null +++ b/.github/workflows/pull-request-reviewer-reminder.yml @@ -0,0 +1,21 @@ +name: 'Close stale issues and PRs' +on: + workflow_dispatch: + schedule: + - cron: '30 1 * * *' + +permissions: + contents: write # only for delete-branch option + issues: write + pull-requests: write + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + exempt-pr-labels: "DO NOT MERGE" + stale-issue-message: 'This issue is stale because it has been open 28 days with no activity. Remove stale label or comment or this will be closed in 14 days.' + days-before-stale: 28 + days-before-close: 14 diff --git a/modules/tide_api/src/Plugin/jsonapi/FieldEnhancer/YamlEnhancer.php b/modules/tide_api/src/Plugin/jsonapi/FieldEnhancer/YamlEnhancer.php index 6f19e0c69..c663fd204 100644 --- a/modules/tide_api/src/Plugin/jsonapi/FieldEnhancer/YamlEnhancer.php +++ b/modules/tide_api/src/Plugin/jsonapi/FieldEnhancer/YamlEnhancer.php @@ -23,7 +23,7 @@ class YamlEnhancer extends ResourceFieldEnhancerBase { */ protected function doUndoTransform($data, Context $context) { $data = Yaml::decode($data); - + if (!empty($data['markup']['#markup'])) { $data['processed_text']['#text'] = $this->processText($data['markup']['#markup']); } diff --git a/modules/tide_tfa/src/Controller/TideTfaUserController.php b/modules/tide_tfa/src/Controller/TideTfaUserController.php new file mode 100644 index 000000000..7be18ee78 --- /dev/null +++ b/modules/tide_tfa/src/Controller/TideTfaUserController.php @@ -0,0 +1,143 @@ +requestStack = $container->get('request_stack'); + + return $instance; + } + + /** + * {@inheritdoc} + */ + public function doResetPassLogin($uid, $timestamp, $hash, $request = NULL) { + // Ensure a valid request object. + if (!$request) { + $request = $this->requestStack->getCurrentRequest(); + } + + // Check if the PRLP module is enabled. + if (!\Drupal::moduleHandler()->moduleExists('prlp')) { + // If PRLP is not enabled, call the parent method. + return parent::doResetPassLogin($uid, $timestamp, $hash, $request); + } + + // Create an instance of PrlpController. + $prlp_controller = new PrlpController( + \Drupal::service('date.formatter'), + \Drupal::entityTypeManager()->getStorage('user'), + \Drupal::service('user.data'), + \Drupal::service('logger.factory')->get('prlp'), + \Drupal::service('flood'), + \Drupal::service('event_dispatcher') + ); + + /** @var \Drupal\user\UserInterface $user */ + $user = $this->userStorage->load($uid); + $this->setUser($user); + + // Let Drupal core deal with the one-time login, + // if TFA is not enabled or + // current user can skip TFA while resetting password. + if ($this->isTfaDisabled() || $this->canSkipPassReset()) { + // Use PRLP's resetPassLogin instead of the core function. + return $prlp_controller->prlpResetPassLogin($request, $uid, $timestamp, $hash); + } + + // Whether the TFA Validation Plugin is set and ready for use. + $tfa_ready = $this->isReady(); + + // Check for authentication plugin. + if ($tfa_ready && $this->pluginAllowsLogin()) { + $this->messenger()->addStatus($this->t('You have logged in on a trusted browser.')); + return $prlp_controller->prlpResetPassLogin($request, $uid, $timestamp, $hash); + } + + // Borrow the following codes from the core function: + $current = \Drupal::time()->getRequestTime(); + + // Verify that the user exists and is active. + if ($user === NULL || !$user->isActive()) { + throw new AccessDeniedHttpException(); + } + + // Time out, in seconds, until login URL expires. + $timeout = $this->config('user.settings')->get('password_reset_timeout'); + if ($user->getLastLoginTime() && $current - $timestamp > $timeout) { + $this->messenger()->addError($this->t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.')); + return $this->redirect('user.pass'); + } + elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && hash_equals($hash, user_pass_rehash($user, $timestamp))) { + if ($tfa_ready) { + $this->session->migrate(); + $token = Crypt::randomBytesBase64(55); + $request->getSession()->set('pass_reset_' . $uid, $token); + + $this->logger->notice('User %name used one-time login link at time %timestamp.', [ + '%name' => $user->getDisplayName(), + '%timestamp' => $timestamp, + ]); + + $this->tempStoreUid($user->id()); + + return $this->redirect('tfa.entry', [ + 'uid' => $uid, + 'hash' => $this->getLoginHash($user), + ], [ + 'query' => ['pass-reset-token' => $token], + 'absolute' => TRUE, + ]); + } + else { + if ($this->canLoginWithoutTfa($this->getLogger('tfa'))) { + return $this->redirectToUserForm($user, $request, $timestamp); + } + else { + return $this->redirect(''); + } + } + } + + // Use PRLP's resetPassLogin instead of the core function. + return $prlp_controller->prlpResetPassLogin($request, $uid, $timestamp, $hash); + } + + /** + * Determines if the user can skip tfa on password reset. + * + * This function checks the TFA settings to see if the option to skip TFA + * during password reset is enabled. If enabled, users will not be required + * to complete two-factor authentication when resetting their password. + * + * @return bool + * TRUE if the user can skip TFA on password reset, FALSE otherwise. + */ + public function canSkipPassReset() { + return $this->tfaSettings->get('reset_pass_skip_enabled'); + } + +} diff --git a/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php b/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php new file mode 100644 index 000000000..fbf1d9760 --- /dev/null +++ b/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php @@ -0,0 +1,30 @@ +get('user.reset.login')) { + $route->setDefault('_controller', '\Drupal\tide_tfa\Controller\TideTfaUserController::doResetPassLogin'); + } + } + +} diff --git a/modules/tide_tfa/src/TideTfaOperation.php b/modules/tide_tfa/src/TideTfaOperation.php index 922721979..281c7f2c8 100644 --- a/modules/tide_tfa/src/TideTfaOperation.php +++ b/modules/tide_tfa/src/TideTfaOperation.php @@ -56,11 +56,17 @@ public static function setupTfaSettings() { $roles = Role::loadMultiple(); // Initialize the $tfa_required_roles array. $tfa_required_roles = []; - // Iterate through the roles and map the role IDs. - foreach ($roles as $role) { - if ($role->id() !== 'authenticated') { - // Map the role ID to itself. - $tfa_required_roles[$role->id()] = $role->id(); + // Define the roles to exclude in a variable. + $excluded_roles = ['authenticated', 'previewer', 'secure_file_user']; + + if (!empty($roles)) { + // Iterate through the roles and map the role IDs. + foreach ($roles as $role) { + // Check if the current role is not in the excluded roles. + if (!in_array($role->id(), $excluded_roles)) { + // Map the role ID to itself. + $tfa_required_roles[$role->id()] = $role->id(); + } } } diff --git a/modules/tide_tfa/tide_tfa.module b/modules/tide_tfa/tide_tfa.module index e894ea473..f9771b950 100644 --- a/modules/tide_tfa/tide_tfa.module +++ b/modules/tide_tfa/tide_tfa.module @@ -11,6 +11,12 @@ use Drupal\Core\Form\FormStateInterface; * Implements hook_form_alter(). */ function tide_tfa_form_alter(&$form, FormStateInterface $form_state, $form_id) { + // [SD-375] Bypass tfa during reset pass for all users. + if ($form_id == 'tfa_settings_form') { + if (isset($form['reset_pass_skip_enabled'])) { + $form['reset_pass_skip_enabled']['#description'] = t('Allow TFA to be bypassed during password reset by the authenticated user.'); + } + } if ($form_id == 'tfa_entry_form') { // [SD-294] Change the label of the 'Send' button. if (isset($form['actions']['send'])) { diff --git a/modules/tide_tfa/tide_tfa.services.yml b/modules/tide_tfa/tide_tfa.services.yml new file mode 100644 index 000000000..fa90d9713 --- /dev/null +++ b/modules/tide_tfa/tide_tfa.services.yml @@ -0,0 +1,5 @@ +services: + tide_tfa.route_subscriber: + class: Drupal\tide_tfa\Routing\TideTfaRouteSubscriber + tags: + - { name: event_subscriber }