our
$VERSION
=
'2.0.9'
;
use
constant
afterSub
=> {
storeHistory
=>
'run'
};
has
lockTimes
=> (
is
=>
'rw'
,
isa
=>
'ArrayRef'
,
default
=>
sub
{ [] }
);
has
maxAge
=> (
is
=>
'rw'
,
isa
=>
'Int'
);
sub
init {
my
(
$self
) =
@_
;
if
(
$self
->conf->{disablePersistentStorage} ) {
$self
->logger->error(
'"BruteForceProtection" plugin enabled WITHOUT persistent session storage"'
);
return
0;
}
unless
(
$self
->conf->{loginHistoryEnabled} ) {
$self
->logger->error(
'"BruteForceProtection" plugin enabled WITHOUT "History" plugin'
);
return
0;
}
unless
(
$self
->conf->{failedLoginNumber} >
$self
->conf->{bruteForceProtectionMaxFailed} )
{
$self
->logger->error(
'Number of failed logins history ('
.
$self
->conf->{failedLoginNumber}
.
') must be higher than allowed failed logins attempt ('
.
$self
->conf->{bruteForceProtectionMaxFailed}
.
')'
);
return
0;
}
if
(
$self
->conf->{bruteForceProtectionIncrementalTempo} ) {
my
$lockTimes
= @{
$self
->lockTimes } =
sort
{
$a
<=>
$b
}
map
{
$_
=~ s/\D//;
$_
<
$self
->conf->{bruteForceProtectionMaxLockTime} ?
$_
: ()
}
grep
{ /\d+/ }
split
/\s*,\s*/,
$self
->conf->{bruteForceProtectionLockTimes};
unless
(
$lockTimes
) {
@{
$self
->lockTimes } = ( 5, 15, 60, 300, 600 );
$lockTimes
= 5;
}
for
(
my
$i
= 1 ;
$i
<=
$self
->conf->{bruteForceProtectionMaxFailed} ;
$i
++
)
{
unshift
@{
$self
->lockTimes }, 0;
$lockTimes
++;
}
unless
(
$lockTimes
<
$self
->conf->{failedLoginNumber} ) {
$self
->logger->
warn
(
'Number failed logins history ('
.
$self
->conf->{failedLoginNumber}
.
') must be higher than incremental lock time values plus allowed failed logins attempt ('
.
"$lockTimes)"
);
splice
@{
$self
->lockTimes },
$self
->conf->{failedLoginNumber};
$lockTimes
=
$self
->conf->{failedLoginNumber};
}
my
$sum
=
$self
->conf->{bruteForceProtectionMaxAge} *
( 1 +
$self
->conf->{failedLoginNumber} -
$lockTimes
);
$sum
+=
$_
foreach
@{
$self
->lockTimes };
$self
->maxAge(
$sum
);
}
else
{
$self
->maxAge(
$self
->conf->{bruteForceProtectionMaxAge} );
}
return
1;
}
sub
run {
my
(
$self
,
$req
) =
@_
;
my
$now
=
time
;
my
$countFailed
=
my
@failedLogins
=
map
{ (
$now
-
$_
->{_utime} ) <
$self
->maxAge ?
$_
: () }
@{
$req
->sessionInfo->{_loginHistory}->{failedLogin} };
$self
->logger->debug(
' Failed login maxAge = '
.
$self
->maxAge );
$self
->logger->debug(
" Number of failed login(s) to take into account = $countFailed"
);
if
(
$self
->conf->{bruteForceProtectionIncrementalTempo} ) {
my
$lastFailedLoginEpoch
=
$failedLogins
[0]->{_utime} ||
undef
;
return
PE_OK
unless
$lastFailedLoginEpoch
;
my
$delta
=
$now
-
$lastFailedLoginEpoch
;
$self
->logger->debug(
" -> Delta = $delta"
);
my
$waitingTime
=
$self
->lockTimes->[
$countFailed
- 1 ]
//
$self
->conf->{bruteForceProtectionMaxLockTime};
$self
->logger->debug(
" -> Waiting time = $waitingTime"
);
if
(
$waitingTime
&&
$delta
<=
$waitingTime
) {
$self
->logger->debug(
"BruteForceProtection enabled"
);
$req
->lockTime(
$waitingTime
);
return
PE_WAIT;
}
return
PE_OK;
}
return
PE_OK
if
(
$countFailed
<=
$self
->conf->{bruteForceProtectionMaxFailed} );
my
@lastFailedLoginEpoch
= ();
my
$MaxAge
=
$self
->maxAge + 1;
foreach
( 0 ..
$self
->conf->{bruteForceProtectionMaxFailed} - 1 ) {
push
@lastFailedLoginEpoch
,
$req
->sessionInfo->{_loginHistory}->{failedLogin}->[
$_
]->{_utime}
if
(
$req
->sessionInfo->{_loginHistory}->{failedLogin}->[
$_
] );
}
$MaxAge
=
$lastFailedLoginEpoch
[0] -
$lastFailedLoginEpoch
[
$self
->conf->{bruteForceProtectionMaxFailed} - 1 ]
if
$self
->conf->{bruteForceProtectionMaxFailed};
$self
->logger->debug(
" -> MaxAge = $MaxAge"
);
return
PE_OK
if
(
$MaxAge
>
$self
->maxAge );
my
$delta
=
defined
$lastFailedLoginEpoch
[1] ?
$now
-
$lastFailedLoginEpoch
[1] : 0;
$self
->logger->debug(
" -> Delta = $delta"
);
return
PE_OK
unless
(
$delta
<=
$self
->conf->{bruteForceProtectionTempo} );
$self
->logger->debug(
"BruteForceProtection enabled"
);
$req
->lockTime(
$self
->conf->{bruteForceProtectionTempo} );
return
PE_WAIT;
}
1;