Commit 966f272d authored by Henrique Mendes's avatar Henrique Mendes Committed by Paulo Henrique Cota Starling
Browse files

Issue #2678578 by hmendes, Sophie.SK, marcusvsouza: Upgrade password blacklist constraint

parent aa46a632
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
password_policy_blacklist
=========================

Password Policy Blacklist Drupal module

Implements a plugin for a Password Policy constraint, allowing administrators to
restrict users from having blacklisted words or phrases in their passwords.
+14 −0
Original line number Diff line number Diff line
# Schema for configuration files of the Password Policy Blacklist submodule.

password_policy.constraint.plugin.password_blacklist:
  type: password_policy.constraint.plugin
  mapping:
    blacklist:
      type: sequence
      label: 'Blacklist'
      sequence:
        type: string
        label: 'Password'
    match_substrings:
      type: boolean
      label: 'Match substrings'
+8 −0
Original line number Diff line number Diff line
name: Password Blacklist Policy
description: Create a blacklist of phrases that cannot be used in passwords.
package: Security
type: module
core: 8.x
core_version_requirement: ^8 || ^9
dependencies:
  - password_policy:password_policy
+107 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\password_policy_blacklist\Plugin\PasswordConstraint;

use Drupal\Core\Form\FormStateInterface;
use Drupal\password_policy\PasswordConstraintBase;
use Drupal\password_policy\PasswordPolicyValidation;

/**
 * Ensures the password doesn't contain any restricted words or phrases.
 *
 * @PasswordConstraint(
 *   id = "password_blacklist",
 *   title = @Translation("Password Blacklist"),
 *   description = @Translation("Password cannot match certain disallowed passwords"),
 *   error_message = @Translation("Your password contains restricted words or phrases.")
 * )
 */
class PasswordBlacklist extends PasswordConstraintBase {

  /**
   * {@inheritdoc}
   */
  public function validate($password, $user_context) {
    $config = $this->getConfiguration();
    $validation = new PasswordPolicyValidation();

    // Parse the blacklist values.
    $blacklisted_passwords = $config['blacklist'];
    $blacklisted_passwords = array_map('trim', $blacklisted_passwords);
    $blacklisted_passwords = array_filter($blacklisted_passwords, 'strlen');

    // Check password against blacklisted values.
    foreach ($blacklisted_passwords as $blacklisted_password) {
      if ($config['match_substrings'] && stripos($password, $blacklisted_password) !== FALSE) {
        $validation->setErrorMessage($this->t('There are restricted terms in your password. Please modify your password.'));
        break;
      }
      elseif (strcasecmp($password, $blacklisted_password) == 0) {
        $validation->setErrorMessage($this->t('Your password is on the blacklist. Please choose a different password.'));
        break;
      }
    }

    return $validation;
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'blacklist' => [''],
      'match_substrings' => FALSE,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    // Get configuration.
    $config = $this->getConfiguration();

    $form['match_substrings'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Also disallow passwords containing blacklisted passwords'),
      '#default_value' => $config['match_substrings'],
    ];

    $form['blacklist'] = [
      '#type' => 'textarea',
      '#title' => $this->t('Blacklisted passwords'),
      '#description' => $this->t('Password cannot be a member of this list, ignoring case. Enter one password per line.'),
      '#default_value' => implode("\r\n", $config['blacklist']),
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    $this->configuration['blacklist'] = explode("\r\n", $form_state->getValue('blacklist'));
    $this->configuration['match_substrings'] = $form_state->getValue('match_substrings');
  }

  /**
   * {@inheritdoc}
   */
  public function getSummary() {
    if ($this->configuration['match_substrings']) {
      return $this->t('Password must not contain any restricted words or phrases.');
    }
    else {
      return $this->t('Password must not be on the blacklist.');
    }
  }

}
+88 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\Tests\password_policy_blacklist\Unit;

use Drupal\Tests\UnitTestCase;

/**
 * Tests the password blacklist constraint.
 *
 * @group password_policy_blacklist
 */
class PasswordBlacklistTest extends UnitTestCase {

  /**
   * Tests the password blacklist.
   *
   * @dataProvider passwordBlacklistDataProvider
   */
  public function testPasswordBlacklist($blacklist, $match_substrings, $password, $result) {
    $blacklist_test = $this->getMockBuilder('Drupal\password_policy_blacklist\Plugin\PasswordConstraint\PasswordBlacklist')
      ->disableOriginalConstructor()
      ->onlyMethods(['getConfiguration', 't'])
      ->getMock();

    $blacklist_test
      ->method('getConfiguration')
      ->willReturn([
        'blacklist' => $blacklist,
        'match_substrings' => $match_substrings,
      ]);

    $this->assertEquals($blacklist_test->validate($password, NULL)->isValid(), $result);
  }

  /**
   * Provides data for the testPasswordBlacklist method.
   */
  public function passwordBlacklistDataProvider() {
    return [
      // Passing conditions.
      [
        ['password'],
        FALSE,
        'testpass',
        TRUE,
      ],
      [
        ['password'],
        TRUE,
        'testpass',
        TRUE,
      ],
      // Failing conditions.
      [
        ['password'],
        FALSE,
        'password',
        FALSE,
      ],
      [
        ['password'],
        TRUE,
        'testpassword',
        FALSE,
      ],
      // Unusual inputs.
      [
        [''],
        FALSE,
        'password',
        TRUE,
      ],
      [
        [''],
        TRUE,
        'password',
        TRUE,
      ],
      [
        [''],
        FALSE,
        '',
        TRUE,
      ],
    ];
  }

}