12
12
namespace Symfony \Component \Security \Http \EventListener ;
13
13
14
14
use Symfony \Component \EventDispatcher \EventSubscriberInterface ;
15
+ use Symfony \Component \HttpFoundation \RateLimiter \PeekableRequestRateLimiterInterface ;
15
16
use Symfony \Component \HttpFoundation \RateLimiter \RequestRateLimiterInterface ;
16
17
use Symfony \Component \HttpFoundation \RequestStack ;
17
18
use Symfony \Component \Security \Core \Exception \TooManyLoginAttemptsAuthenticationException ;
18
19
use Symfony \Component \Security \Core \Security ;
19
20
use Symfony \Component \Security \Http \Authenticator \Passport \Badge \UserBadge ;
20
21
use Symfony \Component \Security \Http \Event \CheckPassportEvent ;
22
+ use Symfony \Component \Security \Http \Event \LoginFailureEvent ;
21
23
use Symfony \Component \Security \Http \Event \LoginSuccessEvent ;
22
24
23
25
/**
@@ -44,22 +46,40 @@ public function checkPassport(CheckPassportEvent $event): void
44
46
$ request = $ this ->requestStack ->getMainRequest ();
45
47
$ request ->attributes ->set (Security::LAST_USERNAME , $ passport ->getBadge (UserBadge::class)->getUserIdentifier ());
46
48
47
- $ limit = $ this ->limiter ->consume ($ request );
48
- if (!$ limit ->isAccepted ()) {
49
+ if ($ this ->limiter instanceof PeekableRequestRateLimiterInterface) {
50
+ $ limit = $ this ->limiter ->peek ($ request );
51
+ } else {
52
+ $ limit = $ this ->limiter ->consume ($ request );
53
+ }
54
+
55
+ // Checking isAccepted here is not enough as peek consumes 0 token, it will
56
+ // be accepted even if there are 0 tokens remaining to be consumed. We check both
57
+ // anyway for safety in case third party implementations behave unexpectedly.
58
+ if (!$ limit ->isAccepted () || 0 === $ limit ->getRemainingTokens ()) {
49
59
throw new TooManyLoginAttemptsAuthenticationException (ceil (($ limit ->getRetryAfter ()->getTimestamp () - time ()) / 60 ));
50
60
}
51
61
}
52
62
53
- public function onSuccessfulLogin ( LoginSuccessEvent $ event ): void
63
+ public function onLoginFailure ( LoginFailureEvent $ event ): void
54
64
{
55
- $ this ->limiter ->reset ($ event ->getRequest ());
65
+ if ($ this ->limiter instanceof PeekableRequestRateLimiterInterface) {
66
+ $ this ->limiter ->consume ($ event ->getRequest ());
67
+ }
68
+ }
69
+
70
+ public function onLoginSuccess (LoginSuccessEvent $ event ): void
71
+ {
72
+ if (!$ this ->limiter instanceof PeekableRequestRateLimiterInterface) {
73
+ $ this ->limiter ->reset ($ event ->getRequest ());
74
+ }
56
75
}
57
76
58
77
public static function getSubscribedEvents (): array
59
78
{
60
79
return [
61
80
CheckPassportEvent::class => ['checkPassport ' , 2080 ],
62
- LoginSuccessEvent::class => 'onSuccessfulLogin ' ,
81
+ LoginFailureEvent::class => 'onLoginFailure ' ,
82
+ LoginSuccessEvent::class => 'onLoginSuccess ' ,
63
83
];
64
84
}
65
85
}
0 commit comments