use
Acrux::RefUtil
qw/isnt_void is_integer is_array_ref is_hash_ref is_true_flag/
;
MAX_DISMISS
=> 5,
AUTH_HOLD_TIME
=> 60*5,
};
sub
authn {
my
$self
=
shift
;
my
$args
=
@_
?
@_
> 1 ? {
@_
} : {%{
$_
[0]}} : {};
my
$username
=
$args
->{username} //
$args
->{u} //
''
;
my
$password
=
$args
->{password} //
$args
->{p} //
''
;
my
$address
=
$args
->{address} //
$args
->{a} //
''
;
my
$cachekey
=
$args
->{cachekey} //
$args
->{k} //
''
;
$self
->clean;
my
$model
=
$self
->model;
return
$self
->raise(
400
=>
"E1320: No username specified"
)
unless
length
(
$username
);
return
$self
->raise(
413
=>
"E1321: The username is too long (1-256 chars required)"
)
unless
length
(
$username
) <= 256;
return
$self
->raise(
400
=>
"E1322: No password specified"
)
unless
length
(
$password
);
return
$self
->raise(
413
=>
"E1323: The password is too long (1-256 chars required)"
)
unless
length
(
$password
) <= 256;
my
$user
=
$self
->cached_user(
$username
,
$cachekey
);
return
if
$self
->error;
return
$self
->raise(
401
=>
$user
->error)
unless
$user
->is_valid;
my
%st
=
$model
->stat_get(
$address
,
$username
);
return
$self
->raise(
500
=>
"E1384: %s"
,
$model
->error)
if
$model
->error;
my
$dismiss
=
$st
{dismiss} || 0;
my
$updated
=
$st
{updated} || 0;
if
((
$dismiss
>= MAX_DISMISS) && ((
$updated
+ AUTH_HOLD_TIME) >=
time
)) {
return
$self
->raise(
403
=>
"E1324: Account frozen for 5 min"
);
}
my
$digest
=
$self
->checksum(
$password
,
$user
->algorithm);
return
$self
->raise(
501
=>
"E1325: Incorrect digest algorithm"
)
unless
$digest
;
if
(secure_compare(
$user
->password,
$digest
)) {
unless
(
$model
->stat_set(
address
=>
$address
,
username
=>
$username
)) {
return
$self
->raise(
500
=>
"E1385: %s"
,
$model
->error ||
'Database request error (stat_set)'
);
}
return
$user
;
}
unless
(
$model
->stat_set(
address
=>
$address
,
username
=>
$username
,
dismiss
=> (
$dismiss
+ 1))) {
return
$self
->raise(
500
=>
"E1385: %s"
,
$model
->error ||
'Database request error (stat_set)'
);
}
return
$self
->raise(
401
=>
"E1326: Incorrect username or password"
);
}
sub
authz {
my
$self
=
shift
;
my
$args
=
@_
?
@_
> 1 ? {
@_
} : {%{
$_
[0]}} : {};
my
$username
=
$args
->{username} //
$args
->{u} //
''
;
my
$cachekey
=
$args
->{cachekey} //
$args
->{k} //
''
;
my
$scope
=
$args
->{scope} ||
$args
->{s} || 0;
$self
->clean;
return
$self
->raise(
400
=>
"E1320: No username specified"
)
unless
length
(
$username
);
return
$self
->raise(
413
=>
"E1321: The username is too long (1-256 chars required)"
)
unless
length
(
$username
) <= 256;
my
$user
=
$self
->cached_user(
$username
,
$cachekey
);
return
if
$self
->error;
return
$self
->raise(
401
=>
$user
->error)
unless
$user
->is_valid;
return
$self
->raise(
403
=>
"E1327: User is disabled"
)
unless
$user
->is_enabled;
if
(
$scope
) {
return
$self
->raise(
403
=>
"E1317: External requests is blocked"
)
unless
$user
->allow_ext;
}
else
{
return
$self
->raise(
403
=>
"E1318: Internal requests is blocked"
)
unless
$user
->allow_int;
}
$user
->is_authorized(1);
return
$user
;
}
sub
access {
my
$self
=
shift
;
my
$args
=
@_
?
@_
> 1 ? {
@_
} : {%{
$_
[0]}} : {};
$self
->clean;
my
$cachekey
=
$args
->{cachekey} //
$args
->{k} //
''
;
my
$controller
=
$args
->{controller} //
$args
->{c};
croak
"No controller specified"
unless
ref
(
$controller
);
my
$url
=
$args
->{url} ? Mojo::URL->new(
$args
->{url}) :
$controller
->req->url;
my
$username
=
$args
->{username} //
$args
->{u} //
$url
->to_abs->username //
''
;
my
$routename
=
$args
->{routename} //
$args
->{r} //
$controller
->current_route //
''
;
my
$method
=
$args
->{method} //
$args
->{m} //
$controller
->req->method //
''
;
my
$url_path
=
$args
->{path} //
$args
->{p} //
$url
->path->to_string;
my
$url_base
=
$args
->{base} //
$args
->{b} //
$url
->base->path_query(
'/'
)->to_string //
''
;
$url_base
=~ s/\/+$//;
my
$remote_ip
=
$args
->{remote_ip} //
$args
->{client_ip} //
$args
->{i} //
$controller
->remote_ip;
my
$headers
=
$args
->{headers} //
$args
->{h};
my
$routes
=
$self
->cached_routes(
$url_base
,
$cachekey
);
return
if
$self
->error;
my
%route
= ();
if
(
exists
(
$routes
->{
$routename
})) {
my
$r
=
$routes
->{
$routename
};
%route
= (
%$r
,
rule
=>
"by routename directly"
);
}
else
{
foreach
my
$r
(
values
%$routes
) {
my
$m
=
$r
->{method};
next
unless
$m
&& ((
$m
eq
$method
) || (
$m
eq
'ANY'
) || (
$m
eq
'*'
));
my
$p
=
$r
->{path};
next
unless
$p
;
if
(
$p
eq
$url_path
) {
%route
= (
%$r
,
rule
=>
"by method and path ($m $p)"
);
last
;
}
if
(
$p
=~ s/\*+$//) {
if
(
index
(
$url_path
,
$p
) >= 0) {
%route
= (
%$r
,
rule
=>
"by method and part of path ($m $p)"
);
last
;
}
else
{
next
;
}
}
for
(
qw/foo bar baz quz quux corge grault garply waldo fred plugh xyzzy thud/
) {
$p
=~ s/[~]+/(
":$_"
)/e or
last
}
if
(
defined
(Mojolicious::Routes::Pattern->new(
$p
)->match(
$url_path
))) {
%route
= (
%$r
,
rule
=>
"by method and pattern of path ($m $p)"
);
last
;
}
}
}
return
1
unless
$route
{realmname};
$controller
->
log
->debug(
sprintf
(
"[access] The route \"%s\" was detected %s"
,
$route
{routename} //
''
,
$route
{rule}));
my
$realm
=
$self
->cached_realm(
$route
{realmname},
$cachekey
);
return
if
$self
->error;
return
1
unless
$realm
->id;
$controller
->
log
->debug(
sprintf
(
"[access] Use realm \"%s\""
,
$route
{realmname}));
my
$user
=
$self
->cached_user(
$username
,
$cachekey
);
return
if
$self
->error;
my
@checks
= ();
my
@grants
= ();
if
(
my
$s
=
$realm
->_check_by_usergroup(
$username
,
$user
->groups)) {
if
(
$s
== 1) {
push
@checks
, 1;
push
@grants
,
sprintf
(
"User/Group (username=%s)"
,
$username
);
}
}
else
{
push
@checks
, 0;
}
if
(
my
$s
=
$realm
->_check_by_host(
$remote_ip
)) {
if
(
$s
== 1) {
push
@checks
, 1;
push
@grants
,
sprintf
(
"Host (ip=%s)"
,
$remote_ip
);
}
}
else
{
push
@checks
, 0;
}
if
(
my
$s
=
$realm
->_check_by_env()) {
if
(
$s
== 1) {
push
@checks
, 1;
push
@grants
,
"Env"
;
}
}
else
{
push
@checks
, 0;
}
if
(
my
$s
=
$realm
->_check_by_header(
sub
{
my
$_k
=
$_
[0];
return
$headers
->{
$_k
}
if
defined
(
$headers
) && is_hash_ref(
$headers
);
return
$controller
->req->headers->header(
$_k
)
})) {
if
(
$s
== 1) {
push
@checks
, 1;
push
@grants
,
"Header"
;
}
}
else
{
push
@checks
, 0;
}
my
$default
=
$realm
->_check_by_default();
my
$status
= 0;
my
$sum
= 0;
$sum
+=
$_
for
@checks
;
my
$satisfy_all
=
lc
(
$realm
->satisfy ||
"any"
) eq
'all'
? 1 : 0;
if
(
$satisfy_all
) {
$status
= 1
if
(
$sum
> 0) &&
scalar
(
@checks
) ==
$sum
;
}
else
{
$status
= 1
if
$sum
> 0;
}
if
(
$status
) {
$controller
->
log
->debug(
sprintf
(
'[access] Access allowed by %s rule(s). Satisfy=%s'
,
join
(
", "
,
@grants
),
$satisfy_all
?
'All'
:
'Any'
));
}
else
{
$controller
->
log
->debug(
sprintf
(
'[access] Access %s by default. Satisfy=%s'
,
$default
?
'allowed'
:
'denied'
,
$satisfy_all
?
'All'
:
'Any'
));
}
my
$summary
=
$status
? 1 :
$default
;
return
$self
->raise(
403
=>
"E1319: Access denied"
)
unless
$summary
;
return
1;
}
sub
authen {
deprecated
'The "WWW::Suffit::AuthDB::authen" is deprecated in favor of "authn"'
;
goto
&authn
;
}
1;