Problem/Motivation

Comment in core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php says it all:

    // Flood protection: this is very similar to the user login form code.
    // @see \Drupal\user\Form\UserLoginForm::validateAuthentication()

This should be motivation enough to extract that security critical code to a place where it can be shared with the login form as well as with contrib authentication methods.

In core alone we already have 3 places that duplicate this security critical code:
core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php
core/modules/user/src/Controller/UserAuthenticationController.php
core/modules/user/src/Form/UserLoginForm.php

It will be useful here as well #2339399: User edit form does not use flood control and allow for password brute force attacks

Proposed resolution

Create a new User AuthFloodService that combines all necessary security checks for user authentication.
* User name + password verification
* Check if user account is blocked
* Check flood control to avoid brute force attacks on passwords

Remaining tasks

Review patch.

User interface changes

None.

API changes

Only API addition: new UserAuthFlood service.

CommentFileSizeAuthor
#75 user-flood-2431357-75.patch43.63 KBMunavijayalakshmi
#74 interdiff.txt600 bytesklausi
#74 user-flood-2431357-74.patch44.07 KBklausi
#71 interdiff.txt11.86 KBklausi
#71 user-flood-2431357-71.patch44.06 KBklausi
#64 interdiff.txt3.1 KBklausi
#64 user-flood-2431357-64.patch41 KBklausi
#62 interdiff.txt28.39 KBklausi
#62 user-flood-2431357-62.patch40.32 KBklausi
#61 interdiff.txt15.48 KBklausi
#61 user-flood-2431357-61.patch25.86 KBklausi
#59 user-flood-2431357-59.patch14.11 KBklausi
#51 extract_a_reusable-2431357-51.patch31.82 KBmr.baileys
#51 interdiff.txt1.94 KBmr.baileys
#47 interdiff.txt9.99 KBdawehner
#47 2431357-47.patch29.88 KBdawehner
#45 interdiff.txt1.12 KBbojanz
#44 2431357-44-credentials-flood-protection.patch23.07 KBbojanz
#40 interdiff.txt1.43 KBbojanz
#40 2431357-39-credentials-flood-protection.patch23.09 KBbojanz
#37 interdiff.txt9.77 KBbojanz
#37 2431357-37-credentials-flood-protection.patch23.25 KBbojanz
#32 2431357-29-credentials-flood-protection.patch23.63 KBmgifford
#29 2431357-29-credentials-flood-protection.patch23.63 KBmr.baileys
#29 interdiff.txt686 bytesmr.baileys
#27 2431357-27-credentials-flood-protection.patch23.63 KBmr.baileys
#27 interdiff.txt1.01 KBmr.baileys
#20 extract_a_reusable-2431357-19.patch23.48 KBznerol
#19 interdiff.txt16.19 KBznerol
#12 extract_a_reusable-2431357-12.patch20.63 KBznerol
#9 interdiff.txt10.06 KBznerol
#9 2431357-extract-login-flood-service-9.diff20.59 KBznerol
#7 2431357-extract-login-flood-service-7.diff19.66 KBznerol
#3 interdiff.txt1.03 KBznerol
#3 2431357-extract-login-flood-service-3.diff19.68 KBznerol
#1 2431357-extract-login-flood-service.diff19.11 KBznerol

Issue fork drupal-2431357

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

znerol’s picture

Status: Active » Needs review
StatusFileSize
new19.11 KB

This should be functionally equivalent with HEAD. This brings down BasicAuth::authenticate() to ~10 lines of simple code.

Status: Needs review » Needs work

The last submitted patch, 1: 2431357-extract-login-flood-service.diff, failed testing.

znerol’s picture

Status: Needs work » Needs review
StatusFileSize
new19.68 KB
new1.03 KB

Fix @count in Sorry, there have been more than @count failed login attempts for this account message.

vijaycs85’s picture

Thanks @znerol. Looks very good. Here are few comments.

  1. +++ b/core/modules/basic_auth/basic_auth.services.yml
    @@ -1,6 +1,9 @@
    +    class: Drupal\user\Flood\LoginFlood
    

    Nice! we don't need to duplicate basic_auth and contrib can extend LoginFlood, if overrides needed.

  2. +++ b/core/modules/user/src/Flood/LoginFlood.php
    @@ -0,0 +1,139 @@
    + * Contains \Drupal\user\Flood\UserLoginFlood
    ...
    +class LoginFlood implements LoginFloodInterface {
    

    Just has loginFlood, but I do prefer UserLoginFlood.

  3. +++ b/core/modules/user/src/Flood/LoginFlood.php
    @@ -0,0 +1,139 @@
    +  }
    

    ideally, @flood should be @flood.default_storage and alias of @flood.storage.db

berdir’s picture

3. I don't think that is needed or how we do it elsewhere? https://www.drupal.org/node/2306083

znerol’s picture

Looking at getAccountIdentifier, I wonder whether we could get rid of the entity manager dependency completely and instead just use $name instead of $account->id(). Is it really necessary/desirable that we only record flood-events for existing users?

znerol’s picture

StatusFileSize
new19.66 KB

Reroll.

dawehner’s picture

  1. +++ b/core/modules/basic_auth/basic_auth.services.yml
    @@ -1,7 +1,10 @@
    +  basic_auth.login_flood:
    +    class: Drupal\user\Flood\LoginFlood
    +    arguments: ['@flood', '@entity.manager', '@config.factory', 'basic_auth']
    ...
    +    arguments: ['@config.factory', '@user.auth', '@basic_auth.login_flood', '@entity.manager']
    
    +++ b/core/modules/user/src/Flood/LoginFlood.php
    @@ -0,0 +1,139 @@
    +  /**
    ...
    +    $this->config = $config_factory->get('user.flood');
    

    We have here potentially a little bit more conservative ... given that authentication providers are constructors quite early. Better do it lazy, to avoid all kind of potential problems.

  2. +++ b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php
    @@ -82,49 +82,20 @@ public function applies(Request $request) {
    +    $ip = $request->getClientIP();
    +
    +    if ($this->flood->isAllowedHost($ip) && $this->flood->isAllowedAccount($ip, $username)) {
    

    I curious whether the two methods could / should be moved into a common helper method. IsAllowedRequest($ip, $username) or something like that.

  3. +++ b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php
    @@ -82,49 +82,20 @@ public function applies(Request $request) {
    +        $this->flood->clearAccount($ip, $username);
    

    Should we name the service loginFlood?

  4. +++ b/core/modules/user/src/Flood/LoginFlood.php
    @@ -0,0 +1,139 @@
    +class LoginFlood implements LoginFloodInterface {
    

    I first thought it would be a good idea to move the namespace into the methods, but it turned out, just no, it would be horrible DX wise.

  5. +++ b/core/modules/user/src/Flood/LoginFloodInterface.php
    @@ -0,0 +1,73 @@
    +
    +/**
    + * Provides a flood service tailored to user login.
    + */
    +interface LoginFloodInterface {
    +
    

    If you compare FloodInterface with LoginFloodInterface it almost seems to be as if it could be named IpAwareFloodInterface ... at least parts of it.

znerol’s picture

StatusFileSize
new20.59 KB
new10.06 KB
  1. Fixed.
  2. We need separate functions because the login form desires to report the exact reason when login was blocked to too flood. Obviously we do not need that in BasicAuth. Still the condition is so simple that I'd prefer to keep it like this also because then we do not need to add yet another method to the interface.
  3. Fixed.
  1. I fear that a generalization would lead to code which is harder to use. We'd need to name the methods in a more generic name, also we'd want to keep the window and threshold parameters. The current implementation intentionally is specific to login. It even frees user code from dealing with the configuration. In addition the interface is a great place for code comments turned into documentation.
andypost’s picture

Status: Needs review » Needs work
Issue tags: +API addition
  1. +++ b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php
    @@ -82,49 +82,20 @@ public function applies(Request $request) {
    +    if ($this->loginFlood->isAllowedHost($ip) && $this->loginFlood->isAllowedAccount($ip, $username)) {
    

    isAllowedHost() looks more related to ban module, no idea now how to do that right

  2. +++ b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php
    @@ -82,49 +82,20 @@ public function applies(Request $request) {
    +      if ($uid && !user_is_blocked($username)) {
    +        $this->loginFlood->clearAccount($ip, $username);
    

    user_is_blocked looks the same as isAllowedAccount()

  3. +++ b/core/modules/user/src/Flood/LoginFlood.php
    @@ -0,0 +1,161 @@
    +    $config = $this->configFactory->get($this->configName);
    ...
    +    $config = $this->configFactory->get($this->configName);
    ...
    +    $config = $this->configFactory->get($this->configName);
    ...
    +    $config = $this->configFactory->get($this->configName);
    

    too much calls

  4. +++ b/core/modules/user/src/Flood/LoginFlood.php
    @@ -0,0 +1,161 @@
    +    $this->flood->register($this->floodPrefix . '.failed_login_ip', $config->get('ip_window'), $ip);
    ...
    +      $this->flood->register($this->floodPrefix . '.failed_login_user', $config->get('user_window'), $identifier);
    ...
    +    return $this->flood->isAllowed($this->floodPrefix . '.failed_login_ip', $config->get('ip_limit'), $config->get('ip_window'), $ip);
    ...
    +      $allowed = $this->flood->isAllowed($this->floodPrefix . '.failed_login_user', $config->get('user_limit'), $config->get('user_window'), $identifier);
    ...
    +      if ($config->get('uid_only')) {
    

    seems easy to pass a readonly config object to constructor

  5. +++ b/core/modules/user/src/Flood/LoginFlood.php
    @@ -0,0 +1,161 @@
    +  protected function getAccountIdentifier($ip, $name) {
    ...
    +        return $account->id();
    ...
    +        return $account->id() . '-' . $ip;
    
    +++ b/core/modules/user/src/Flood/LoginFloodInterface.php
    @@ -0,0 +1,73 @@
    +  public function register($ip, $name);
    ...
    +  public function clearAccount($ip, $name);
    ...
    +  public function isAllowedAccount($ip, $name);
    

    I'd swap order of arguments to be more like result

  6. +++ b/core/modules/user/src/Form/UserLoginForm.php
    @@ -166,40 +165,19 @@ public function validateName(array &$form, FormStateInterface $form_state) {
    -        $form_state->set('flood_control_user_identifier', $identifier);
    ...
    +        $form_state->set('flood_control_triggered', 'user');
    
    @@ -213,17 +191,13 @@ public function validateAuthentication(array &$form, FormStateInterface $form_st
    -      if ($flood_control_user_identifier = $form_state->get('flood_control_user_identifier')) {
    

    this naming change breaks BC for the login form, better to change back

cilefen’s picture

Issue summary: View changes
Issue tags: +Needs issue summary update

This would be useful for the user edit form as well. #2339399: User edit form does not use flood control and allow for password brute force attacks The issue summary should be updated accordingly.

znerol’s picture

StatusFileSize
new20.63 KB

Reroll.

znerol’s picture

Reroll.

znerol’s picture

Status: Needs work » Needs review

Review in #10 gives me a hard time.

  1. This is in accordance to the original flood interface. I'd prefer to keep the similarities.
  2. user_is_blocked() also works if the user entity is not loaded. Changing that to UserInterface::isActive() would mean loading the user entity before checking flood. We know from #2345611: Load user entity in Cookie AuthenticationProvider instead of using manual queries that this might have performance implications. Therefore I'd prefer to not change that in this patch but rather in the other one.
  3. See #8.1
  4. Ditto
  5. I do not understand that comment
  6. Please view the patch in full-context. There is no rename here. Rather the flood_control_user_identifier form state is removed completely. The flood_control_triggered form state is not touched.
znerol’s picture

Given that #2339399: User edit form does not use flood control and allow for password brute force attacks wants to reuse the service introduced here, it is probably necessary to find a better name for the new class (and maybe address #10.1). The OWASP Guide to Authentication uses the term Threshold Governor for the facility we are trying to implement here. I did not find any class/interface in Symfony which serves that purpose (there are some third party bundles like this).

greggles’s picture

Priority: Normal » Major

Since #2339399: User edit form does not use flood control and allow for password brute force attacks is major and is blocked on this, moving this to Major (right?).

znerol’s picture

I'm willing to continue the work here. Would be great to have feedback on the naming (see #15).

This basically protects against brute-forcing user credentials (not restricted to login). The current patch provides \Drupal\user\Flood\UserLoginFlood. An easy first step would be to rename it to: Drupal\user\Flood\CredentialsCheckFlood.php.

greggles’s picture

I think that CredentialCheckFlood is a good name for it since it is not only about Login.

znerol’s picture

StatusFileSize
new16.19 KB

Renamed the classes, services and changed the docs/comments.

znerol’s picture

StatusFileSize
new23.48 KB
pwolanin’s picture

We should also re-use this on the used edit form when the user enters their password - if they leave the account logged in, there is no brute-force protection

andypost’s picture

@pwolanin +100 but maybe we need separate issue for that (scope is not clear)

greggles’s picture

cilefen’s picture

@greggles Yes.

neclimdul’s picture

Status: Needs review » Needs work

This looks pretty good! Dug pretty deep through the changes and things almost identical just refactored which is great. Have a couple concerns though.

  1. +++ b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php
    @@ -82,49 +82,20 @@ public function applies(Request $request) {
    +    if ($this->credentialsCheckFlood->isAllowedHost($ip) && $this->credentialsCheckFlood->isAllowedAccount($ip, $username)) {
    ...
    +        $this->credentialsCheckFlood->clearAccount($ip, $username);
    ...
    +    $this->credentialsCheckFlood->register($ip, $username);
    

    isAllowedAccount() calls getAccountIdentifier() does an uncached property lookup and then both the clearAccount() and register() call it again and do another uncached lookup of the same account. That means on both the pass and fail path we do at least 2 lookups and then the cached entity retrieval just in the flood code. That doesn't seem ideal.

  2. +++ b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php
    @@ -82,49 +82,20 @@ public function applies(Request $request) {
    -    return [];
    

    is loosing this ok?

  3. +++ b/core/modules/user/src/FThis seems pretty different. I need to see if this is addressed in the comments.orm/UserLoginForm.php
    @@ -244,10 +218,8 @@ public function validateFinal(array &$form, FormStateInterface $form_state) {
    -    elseif ($flood_control_user_identifier = $form_state->get('flood_control_user_identifier')) {
    -      // Clear past failures for this user so as not to block a user who might
    -      // log in and out more than once in an hour.
    -      $this->flood->clear('user.failed_login_user', $flood_control_user_identifier);
    +    else {
    +      $this->credentialsCheckFlood->clearAccount($ip, $form_state->getValue('name'));
    

    This confused me at first too but for future reviewers it is because clearAccount has access to the id builder in the flood class so it doesn't have to cache the earlier resolution and use it. Previous comments about adding uncached lookups applies though.

mr.baileys’s picture

Status: Needs work » Needs review
StatusFileSize
new1.01 KB
new23.63 KB

1: Added drupal_static caching to \Drupal\user\Flood\CredentialsCheckFlood::getAccountIdentifier()
2: according to the \Drupal\Core\Authentication\AuthenticationProviderInterface, it should either return

  • AccountInterface - in case of a successful authentication, or
  • NULL - in case where authentication failed.

Since we have a failed authentication, not returning (NULL) seems better than returning an empty array.

Status: Needs review » Needs work

The last submitted patch, 27: 2431357-27-credentials-flood-protection.patch, failed testing.

mr.baileys’s picture

Status: Needs work » Needs review
StatusFileSize
new686 bytes
new23.63 KB

I feel stupid now.

The last submitted patch, 27: 2431357-27-credentials-flood-protection.patch, failed testing.

mgifford’s picture

The bots like this now. What manual testing is needed?

mgifford’s picture

re-uploading the patch for the bot.

Status: Needs review » Needs work

The last submitted patch, 32: 2431357-29-credentials-flood-protection.patch, failed testing.

catch’s picture

+++ b/core/modules/basic_auth/basic_auth.services.yml
@@ -1,7 +1,10 @@
+  basic_auth.credentials_check_flood:

Why can't basic_auth.authentication.basic_auth use the service provided by user module?

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

bojanz’s picture

Version: 8.1.x-dev » 8.2.x-dev

The service needs to inject EntityTypeManager instead of EntityManager.

The code has no reason to do $config = $this->configFactory->get($this->configName); in every method, that can be done in the constructor.

$accounts = &drupal_static(__FUNCTION__);

We should not be using drupal_static in a class. Just use a class attribute.

bojanz’s picture

Status: Needs work » Needs review
StatusFileSize
new23.25 KB
new9.77 KB

I'm interested in the answer to #34 too.

For now here's a reroll with fixes for #36.

dawehner’s picture

  1. +++ b/core/modules/user/src/Flood/CredentialsCheckFlood.php
    @@ -0,0 +1,158 @@
    +
    +/**
    + * @file
    + * Contains \Drupal\user\Flood\CredentialsCheckFlood.
    + */
    
    +++ b/core/modules/user/src/Flood/CredentialsCheckFloodInterface.php
    @@ -0,0 +1,71 @@
    +/**
    + * @file
    + * Contains \Drupal\user\Flood\CredentialsCheckFloodInterface.
    + */
    

    Unneeded now

  2. +++ b/core/modules/user/src/Flood/CredentialsCheckFlood.php
    @@ -0,0 +1,158 @@
    +class CredentialsCheckFlood implements CredentialsCheckFloodInterface {
    ...
    +  public function register($ip, $name) {
    

    All the methods deal with IPs so I'm wondering whether it should be part of the name of the interface

  3. +++ b/core/modules/user/src/Flood/CredentialsCheckFlood.php
    @@ -0,0 +1,158 @@
    +  protected function getAccountIdentifier($ip, $name) {
    +    if (!isset($this->accounts[$name])) {
    +      $storage = $this->entityTypeManager->getStorage('user');
    +      $account_by_name = $storage->loadByProperties(['name' => $name]);
    +      $this->accounts[$name] = reset($account_by_name);
    +    }
    +    /** @var \Drupal\Core\Session\AccountInterface $account **/
    +    $account = $this->accounts[$name];
    +    if ($account) {
    

    IMHO classes which provide internal state/storage should also have a reset method. On the other hand I seriously wonder whether this static cache is helpful. How likely will you call methods multiple times per request?

Status: Needs review » Needs work

The last submitted patch, 37: 2431357-37-credentials-flood-protection.patch, failed testing.

bojanz’s picture

Status: Needs work » Needs review
StatusFileSize
new23.09 KB
new1.43 KB

Removed the @file docblocks, fixed a stupid mistake that caused many of the test fails.

Status: Needs review » Needs work

The last submitted patch, 40: 2431357-39-credentials-flood-protection.patch, failed testing.

tim.plunkett’s picture

+++ b/core/modules/user/src/Flood/CredentialsCheckFlood.php
@@ -0,0 +1,153 @@
+  protected $config;
...
+    $this->configFactory = $config_factory->get($config_name);

Should be $this->config =

znerol’s picture

Regarding the caching of account objects, see #6. In my opinion we should not load the account at all but just treat any flood event equal (regardless of whether or not there is actually an account with that name). Since this would be a change in behavior (which is not desirable during a refactoring) and in order to facilitate reviews I left it there.

bojanz’s picture

Status: Needs work » Needs review
StatusFileSize
new23.07 KB

@tim.plunkett
D'oh. Rushing things is my middle name.

bojanz’s picture

StatusFileSize
new1.12 KB

Interdiff got eaten.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dawehner’s picture

Title: Extract a reusable LoginFlood class and reuse it in UserLoginForm and BasicAuth » Extract a reusable LoginFlood class and reuse it in UserLoginForm and BasicAuth and UserAuthenticationController
StatusFileSize
new29.88 KB
new9.99 KB

Rerolled it a bit and worked on using it for \Drupal\user\Controller\UserAuthenticationController as well

Status: Needs review » Needs work

The last submitted patch, 47: 2431357-47.patch, failed testing.

larowlan’s picture

Issue tags: +Needs reroll
manuel garcia’s picture

Issue tags: -Needs reroll

Patch #47 applies cleanly, removing the tag...

mr.baileys’s picture

Status: Needs work » Needs review
StatusFileSize
new1.94 KB
new31.82 KB

#47 changed the wording on the access-denied-by-flood-protection-messages, which is causing the 2 test failures. Updated the messages in the test to make the testbot happy.

larowlan’s picture

+++ b/core/modules/basic_auth/basic_auth.services.yml
@@ -1,7 +1,10 @@
+    arguments: ['@config.factory', '@user.auth', '@basic_auth.credentials_check_flood', '@entity.manager']

I think this is classified as a BC break.

i.e. we might need to retain the original signature and add the new service as a fifth optional argument

But I'd check with @alexpott or @catch to make sure that I'm right about this first, it might be an allowed BC break.

Looking good though!

tim.plunkett’s picture

The constructor for a service object (one in the container), plugin, or controller is considered internal unless otherwise marked, and may change in a minor release. These are objects where developers are not expected to call the constructor directly in the first place. Constructors for value objects where a developer will be calling the constructor directly are excluded from this statement.

- https://www.drupal.org/core/d8-bc-policy#constructors

berdir’s picture

That's correct but we've started using the pattern of keeping the argument optional with a fallback to the controller in a few places recently and I like that. We usually use that if it's a class that is quite likely extend somewhere, which is probably not the case here, but there's an argument to be made about this case too:

If you use basic authentication or have the user login block placed on your site then applying this patch gives you a fatal error on every page, you can't even log in. Accessing update.php as anonymous won't clear the cache, so you have to change settings.php to allow anonymous access or use rebuild.php or so.

We could have a single issue to remove all those fallbacks in 9.x and add the same @todo everywhere.

klausi’s picture

I think this patch does not got far enough. BasicAuth.php should be just a thin wrapper that extracts username and password from the request, then calls some user-authentication-user-is-blocked-plus-flood-control service. That puts the flood and authentication logic into one central place and can be used from multiple locations. Because we always need to check if the user is blocked + the password matches + flood control.

This is security critical code and everytime people have to write an authentication provider they need to get this right. They should not have to think about this.

andypost’s picture

user is blocked + the password matches + flood control

In case of external auth password could be skipped

klausi’s picture

In case of external auth systems you can swap out the user.auth service. I'm saying we should have a user.all_inclusive_with_flood_control_plus_blocked_check service which uses user.auth (wraps it). With a better name :) user.login? user.login_flood? user.auth_flood? user.auth_check_flood?

The service can return a result object with a bool success true/false and a fail message in case authentication failed.

klausi’s picture

Assigned: Unassigned » klausi

Let me try.

klausi’s picture

Assigned: klausi » Unassigned
StatusFileSize
new14.11 KB

need to run now, work in progress patch.

Status: Needs review » Needs work

The last submitted patch, 59: user-flood-2431357-59.patch, failed testing.

klausi’s picture

Status: Needs work » Needs review
StatusFileSize
new25.86 KB
new15.48 KB

Now demonstrating that we can share code between basic auth and user auth controller.

Still some rough edges in the patch:
* usage of t()
* no interface for the new service
* is it ok to keep the AuthResult class as data junk with public properties?
* very long authenticate() method in the new service, can we split it up in a meaningful way?

The next step will be to try to refactor UserLoginForm to use this new service. The biggest difference between HTTP API call messages and login form messages is that the latter can contain links (for example to reset your password). I'm not sure yet how we can deal with that difference.

klausi’s picture

StatusFileSize
new40.32 KB
new28.39 KB

Implemented UserLoginForm. I solved the problem with the messages by introducing constants as error codes for each error state. That way a consumer such as UserLoginForm can override the specific messages easily.

The code unification to one service also surfaced inconsistent behaviour for the HTTP API user login and the form login. I adapted the test case accordingly so that we have the same messages on forms as well as in API responses.

I was able remove large swaths of duplicated code, yay! Let's see where this blows up now.

TODO:
* Refactor authenticate() method because is has too many if/else levels
* Finalize the naming. How do you like the current user.auth_flood, UserAuthFlood and UserAuthFloodInterface names?

Status: Needs review » Needs work

The last submitted patch, 62: user-flood-2431357-62.patch, failed testing.

klausi’s picture

Status: Needs work » Needs review
StatusFileSize
new41 KB
new3.1 KB

Cool, that went better than I thought.

dawehner’s picture

  1. +++ b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php
    @@ -77,49 +52,13 @@ public function applies(Request $request) {
    -    return [];
    

    How do we deal with this empty case here, don't we break the API somehow here?

  2. +++ b/core/modules/user/src/Authentication/AuthResult.php
    @@ -0,0 +1,52 @@
    + * Represents the result after an authentication attempt.
    

    I really like the idea of a result object, that makes it clear to understand what is going on.

  3. +++ b/core/modules/user/src/Authentication/AuthResult.php
    @@ -0,0 +1,52 @@
    +class AuthResult {
    ...
    +  public $success = FALSE;
    

    I was wondering whether we should add public methods. I've seen some committers freaking out about objects having no public methods. Personally I agree with you, this object is not meant to be swapped out

  4. +++ b/core/modules/user/src/Authentication/UserAuthFlood.php
    @@ -0,0 +1,145 @@
    +          $auth_result->errorCode = AuthResult::ERROR_USER_BLOCKED;
    +          $auth_result->errorMessage = $this->t('The username @name has not been activated or is blocked.', array('@name' => $username));
    

    Is there a way we introduce custom static factories like: AuthResult::userBlocked($message) and AuthResult::success($user). We could then even have different objects one for success one for failure or so, basically similarily to the way how AccessResult works.

klausi’s picture

1. BasicAuth.php wrongly returned [], but AuthenticationProviderInterface says you should return NULL on authentication failure.

4. AccessResultInterface is quite a different beast because it offers functionality to combine multiple access results into one. It has quite a bit more of complexity than our authentication result here. I would avoid adding complexity if we don't have to - all we want to do here is provide a multidemensional result after the authentication. More like an array in the old days, but by using a class we can make the contents explicit and better document it. What would be the benefit of having 2 classes for success and failure? Same for having public methods, they would just be dumb getters for the properties.

I just feels wrong to write methods for on object that has zero behavior attached to it. It is just a data container. Anyone else think we should blow up the public methods on the AuthResult class?

klausi’s picture

Status: Needs review » Needs work

More feedback from Crell and bojanz says that we should not do public properties, so let's do a full blown class with getters and an AuthResultInterface in the next iteration of this patch.

berdir’s picture

Non-public properties doesn't mean we need an interface. I agree with them and think we should not have public properties but that doesn't mean we need an interface.

catch’s picture

Yeah I think single class + protected properties + methods is OK for this. The class with public properties is not much more defined than stdClass() so methods is good though, but no point in an interface unless we were going to add another class, which we've no intention of (and could add one then if we wanted).

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

klausi’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs issue summary update
StatusFileSize
new44.06 KB
new11.86 KB

now with public methods on AuthResult.

Also updated issue summary.

klausi’s picture

Title: Extract a reusable LoginFlood class and reuse it in UserLoginForm and BasicAuth and UserAuthenticationController » Extract a reusable UserAuthFlood service and reuse it in UserLoginForm and BasicAuth and UserAuthenticationController

better title.

Status: Needs review » Needs work

The last submitted patch, 71: user-flood-2431357-71.patch, failed testing.

klausi’s picture

Status: Needs work » Needs review
StatusFileSize
new44.07 KB
new600 bytes

Forgot to convert one line in BasicAuth, tests now passing locally.

Munavijayalakshmi’s picture

StatusFileSize
new43.63 KB

Rerolled the patch.

Status: Needs review » Needs work

The last submitted patch, 75: user-flood-2431357-75.patch, failed testing.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Darvanen made their first commit to this issue’s fork.

darvanen’s picture

I was beginning to re-roll this... but I see that there is now a UserFloodControl service that separates out the logic around allowing requests and the registration of flood events.

It does not centralise the error messaging the way the patches here do.

Is this issue defunct or does it still have some value?

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bradjones1’s picture

I see that there is now a UserFloodControl service that separates out the logic around allowing requests and the registration of flood events...Is this issue defunct or does it still have some value?

Yes, this issue is still active in so far as there's still repeated logic regarding user authentication... the user flood control service helps abstract out some of the inner workings/wrapping around the flood service, but it's not re-usable (e.g., by other authentication providers) for auth.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.