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.

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

StatusFileSize
new1.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
StatusFileSize
new1.96 KB