At the moment it is either difficult or impossible to modify some of the UI text that is highly visible to the end-user - the results of the AJAX constraints callback.

To edit this text you either need to mess with constraint plugins, or do a hack-job with t() against password_policy_ajax_check() strings.

If the results of the AJAX callback was a standard Drupal render array with a drupal_alter() call it would be much easier to tweak the UI text as appropriate.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

thedavidmeister created an issue. See original summary.

thedavidmeister’s picture

POC below:

/**
 * AJAX callback to check password against applicable policies.
 */
function password_policy_ajax_check() {
  // Decode password which javascript ran encodeURIComponent.
  // The password will not be displayed, so there is no need to filter it with
  // check_plain() or filter_xss() as suggested by Coder.
  // @ignore security_17
  if (isset($_POST['password'])) {
    $untrimmed_password = rawurldecode($_POST['password']);

    // Trim the password before checking against policies, since Drupal will
    // trim passwords before saving them.
    $password = trim($untrimmed_password);
    $is_trimmed = ($password !== $untrimmed_password);

    // Determine whether password is all spaces.  If it is empty string after
    // trimming, it was all spaces.
    $is_all_spaces = ($is_trimmed && $password === '');
    if ($is_all_spaces) {
      return drupal_json_output(array(
        'message' => t('Password is all spaces and will not be saved.'),
        'strength' => 0,
        'indicatorText' => '',
      ));
    }

    // Using this user is not always going to work.
    global $user;
    $account = $user;
    password_policy_user_load(array($account->uid => $account));
    $policies = PasswordPolicy::matchedPolicies($account);

    // Exit prematurely if no policies are usable.
    if (count($policies) == 0) {
      return;
    }

    $total = 0;
    $errors = array();
    $build = [
      'intro' => [
        '#markup' => t('Password does not meet the following requirements:'),
      ],
      'errors' => [
        '#theme' => 'item_list',
        '#items' => [],
      ],
    ];

    foreach ($policies as $policy) {
      $total += count($policy->messages());
      $build['errors']['#items'] = $policy->check($password, $account);
    }
    if ($is_trimmed) {
      $build['trimmed'] = t('Password has spaces at the beginning or end which are ignored.');
    }

    drupal_alter(__FUNCTION__, $build);

    $errors = $build['errors']['#items'];

    $sus_count = $total - count($errors);

    $score = ($sus_count / $total) * 100;

    $msg = !empty($errors) ? drupal_render($build) : '';

    $return = array(
      'message' => $msg,
      'strength' => $score,
      'indicatorText' => t('@sus_count of @total', array('@sus_count' => $sus_count, '@total' => $total)),
    );

    drupal_json_output($return);
  }
}

thedavidmeister’s picture

This allows a bit more control over the scores too, which allows you to totally change the text if you wanted.

/**
 * AJAX callback to check password against applicable policies.
 */
function password_policy_ajax_check() {
  // Decode password which javascript ran encodeURIComponent.
  // The password will not be displayed, so there is no need to filter it with
  // check_plain() or filter_xss() as suggested by Coder.
  // @ignore security_17
  if (isset($_POST['password'])) {
    $untrimmed_password = rawurldecode($_POST['password']);

    // Trim the password before checking against policies, since Drupal will
    // trim passwords before saving them.
    $password = trim($untrimmed_password);
    $is_trimmed = ($password !== $untrimmed_password);

    // Determine whether password is all spaces.  If it is empty string after
    // trimming, it was all spaces.
    $is_all_spaces = ($is_trimmed && $password === '');
    if ($is_all_spaces) {
      return drupal_json_output(array(
        'message' => t('Password is all spaces and will not be saved.'),
        'strength' => 0,
        'indicatorText' => '',
      ));
    }

    // Using this user is not always going to work.
    global $user;
    $account = $user;
    password_policy_user_load(array($account->uid => $account));
    $policies = PasswordPolicy::matchedPolicies($account);

    // Exit prematurely if no policies are usable.
    if (count($policies) == 0) {
      return;
    }

    $total = 0;
    $errors = array();
    $build = [
      'intro' => [
        '#markup' => t('Password does not meet the following requirements:'),
      ],
      'errors' => [
        '#theme' => 'item_list',
        '#items' => [],
      ],
    ];

    foreach ($policies as $policy) {
      $total += count($policy->messages());
      $build['errors']['#items'] = $policy->check($password, $account);
    }
    if ($is_trimmed) {
      $build['trimmed'] = t('Password has spaces at the beginning or end which are ignored.');
    }

    $score = [];
    $score['total'] = $total;
    $score['error_count'] = count($build['errors']['#items']);
    $score['sus_count'] = $total - $score['error_count'];
    $score['score'] = ($score['sus_count'] / $score['total']) * 100;

    drupal_alter(__FUNCTION__, $build, $score);

    $msg = !empty($score['error_count']) ? drupal_render($build) : '';

    $return = array(
      'message' => $msg,
      'strength' => $score['score'],
      'indicatorText' => t('@sus_count of @total', array('@sus_count' => $score['sus_count'], '@total' => $score['total'])),
    );

    drupal_json_output($return);
  }
}
thedavidmeister’s picture

You could do something like this in an alter with the last set of code:

function e_security_password_policy_ajax_check_alter(&$render, &$score) {
  $too_short = in_array('Password must have at least 8 character(s).', $render['errors']['#items']);
  $missing_numbers = in_array('Password must have at least 1 digit(s).', $render['errors']['#items']);
  $missing_letters = in_array('Password must have at least 1 letter(s).', $render['errors']['#items']);

  $message = $too_short ? t('Your password must be <strong>at least 8 characters long</strong>.') : t('Your password is still not strong enough.');

  if ($missing_letters || $missing_numbers) {
    if ($too_short) {
      $message = rtrim($message, '.') . ' and include ';
    }
    else {
      $message .= ' Please include ';
    }
  }

  if ($missing_letters && $missing_numbers) {
    $message .= t('<strong>at least one letter and one number.</strong>');
  }
  else {
    if ($missing_letters) {
      $message .= t('<strong>at least one letter.</strong>');
    }
    if ($missing_numbers) {
      $message .= t('<strong>at least one number.</strong>');
    }
  }

  $render = ['#markup' => $message];
}
thedavidmeister’s picture

FileSize
1.94 KB
thedavidmeister’s picture

Status: Active » Needs review

Status: Needs review » Needs work

The last submitted patch, 5: 2598714-5.patch, failed testing.

thedavidmeister’s picture

Status: Needs work » Needs review
FileSize
1.96 KB