You are here

function login_security_validate in Login Security 8

Same name and namespace in other branches
  1. 5 login_security.module \login_security_validate()
  2. 6 login_security.module \login_security_validate()
  3. 7 login_security.module \login_security_validate()
  4. 2.x login_security.module \login_security_validate()

Implements hook_validate().

This functions does more than just validating, but it's main intention is to break the login form flow.

1 string reference to 'login_security_validate'
login_security_form_alter in ./login_security.module
Implements hook_form_alter().

File

./login_security.module, line 136
Login Security module hooks.

Code

function login_security_validate(array $form, FormStateInterface $form_state) {
  $conf = \Drupal::config('login_security.settings');

  // Sanitize user input.
  $name = $form_state
    ->getValue('name');

  // Null username should not be tracked.
  if (!strlen($name)) {
    return;
  }

  // Expire old tracked entries.
  _login_security_remove_events();

  // Populate variables to be used in any module message or login operation.
  $variables = _login_security_get_variables_by_name($name);

  // First, check if administrator should be notified of unexpected login
  // activity.
  // Only process if configured threshold > 1.
  // see: http://drupal.org/node/583092.
  if ($variables['@activity_threshold']) {
    $threshold = \Drupal::state()
      ->get('login_security.threshold_notified', FALSE);

    // Check if threshold has been reached.
    if ($variables['@tracking_current_count'] > $variables['@activity_threshold']) {

      // Check if admin has been already alerted.
      if (!$threshold) {

        // Mark alert status as notified and send the email.
        \Drupal::logger('login_security')
          ->warning('Ongoing attack detected: Suspicious activity detected in login form submissions. Too many invalid login attempts threshold reached: currently @tracking_current_count events are tracked, and threshold is configured for @activity_threshold attempts.', $variables);
        \Drupal::state()
          ->set('login_security.threshold_notified', TRUE);

        // Send notification only if required.
        $email_to = $conf
          ->get('login_activity_notification_emails');
        if ($email_to !== '') {
          $from = \Drupal::config('system.site')
            ->get('mail');
          $language = \Drupal::languageManager()
            ->getDefaultLanguage();
          \Drupal::service('plugin.manager.mail')
            ->mail('login_security', 'login_activity_notify', $email_to, $language, $variables, $from, TRUE);
        }
      }
    }
    elseif (\Drupal::state()
      ->get('login_security.threshold_notified', FALSE) && $variables['@tracking_current_count'] < $variables['@activity_threshold'] / 3) {

      // Reset alert if currently tracked events is < threshold / 3.
      \Drupal::logger('login_security')
        ->notice('Suspicious activity in login form submissions is no longer detected: currently @tracking_current_count events are being tracked, and threshold is configured for @activity_threshold maximum allowed attempts).', $variables);
      \Drupal::state()
        ->set('login_security.threshold_notified', TRUE);
    }
  }

  // Check for host login attempts: Hard.
  if ($variables['@hard_block_attempts'] >= 1) {
    if ($variables['@ip_current_count'] >= $variables['@hard_block_attempts']) {

      // Block the host ip_address().
      login_user_block_ip($variables, $form_state);
    }
  }

  // Check for user login attempts.
  if ($variables['@user_block_attempts'] >= 1) {
    if ($variables['@user_current_count'] >= $variables['@user_block_attempts']) {

      // Block the account $name.
      login_user_block_user_name($variables, $form_state);
    }
  }

  // At this point, they're either logged in or not by Drupal core's abuse of
  // the validation hook to login users completely.
  if ($form_state
    ->hasAnyErrors()) {
    $errors = $form_state
      ->getErrors();
    $password_message = preg_grep("/<a href=\"\\/user\\/password\\?name={$name}\">Have you forgotten your password\\?<\\/a>/", $errors);
    $block_message = preg_grep("/The username <em class=\"placeholder\">{$name}<\\/em> has not been activated or is blocked./", $errors);
    if (!count($password_message) || !count($block_message)) {
      if ($conf
        ->get('disable_core_login_error')) {

        // Resets the form error status so no form fields are highlighted in
        // red.
        $form_state
          ->setRebuild();
        $form_state
          ->clearErrors();

        // Removes "Unrecognized username or password. Have you
        // forgotten your password?" and "The username $name has not been
        // activated or is blocked.", and any other errors that might be
        // helpful to an attacker it should not reset the attempts message
        // because it is a warning, not an error.
        drupal_get_messages('error', TRUE);
      }

      // Should the user be advised about the remaining login attempts?
      $notice_user = $conf
        ->get('notice_attempts_available');
      if ($notice_user == TRUE && $variables['@user_block_attempts'] > 0 && $variables['@user_block_attempts'] >= $variables['@user_current_count']) {
        $message_raw = $conf
          ->get('notice_attempts_message');

        // Simple flag that can be changed using hook_alter (see below).
        $display_block_attempts = TRUE;

        // Allow other module to change the flag, or even the message displayed,
        // with a custom logic.
        \Drupal::moduleHandler()
          ->alter('login_security_display_block_attempts', $message_raw, $display_block_attempts, $variables['@user_current_count']);
        $message = [
          'message' => $message_raw,
          'variables' => $variables,
        ];

        // This loop is used instead of doing t() because t() can only
        // translate static strings, not variables.
        // Ignoring Coder because $variables is sanitized by
        // login_security_t().
        // See https://drupal.org/node/1743996#comment-6421246.
        // @ignore security_2
        $message = SafeMarkup::format($message['message'], $message['variables']);
        if ($display_block_attempts) {
          drupal_set_message($message, 'warning', TRUE);
        }
      }
    }
  }
}