lemonldap-ng/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/StayConnected.pm
2021-04-02 16:09:50 +02:00

244 lines
7.6 KiB
Perl

# Plugin to enable "stay connected on this device" feature
package Lemonldap::NG::Portal::Plugins::StayConnected;
use strict;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw(
PE_OK
PE_SENDRESPONSE
);
our $VERSION = '2.1.0';
extends 'Lemonldap::NG::Portal::Main::Plugin';
# INTERFACE
use constant endAuth => 'newDevice';
use constant beforeAuth => 'check';
use constant beforeLogout => 'logout';
# INITIALIZATION
has ott => (
is => 'rw',
lazy => 1,
default => sub {
my $ott =
$_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
$ott->timeout( $_[0]->conf->{formTimeout} );
return $ott;
}
);
has cookieName => (
is => 'rw',
lazy => 1,
default => sub {
$_[0]->conf->{stayConnectedCookieName} || 'llngconnection';
}
);
# Default timeout: 1 month
has timeout => (
is => 'rw',
lazy => 1,
default => sub {
$_[0]->conf->{stayConnectedTimeout} || 2592000;
}
);
sub init {
my ($self) = @_;
$self->addAuthRoute( registerbrowser => 'storeBrowser', ['POST'] );
return 1;
}
# RUNNING METHODS
# Registration: detect if user wants to stay connected.
# Then ask for browser fingerprint
sub newDevice {
my ( $self, $req ) = @_;
my $checkLogins = $req->param('checkLogins');
$self->logger->debug("StayConnected: checkLogins set") if $checkLogins;
if ( $req->param('stayconnected') ) {
my $token = $self->ott->createToken( {
name => $req->sessionInfo->{ $self->conf->{whatToTrace} },
(
$checkLogins
? ( history => $req->sessionInfo->{_loginHistory} )
: ()
)
}
);
$req->response(
$self->p->sendHtml(
$req,
'../common/registerBrowser',
params => {
URL => $req->urldc,
TOKEN => $token,
ACTION => '/registerbrowser',
CHECKLOGINS => $checkLogins
}
)
);
return PE_SENDRESPONSE;
}
return PE_OK;
}
# Store data in a long-time session
sub storeBrowser {
my ( $self, $req ) = @_;
$req->urldc( $req->param('url') );
$req->mustRedirect(1);
if ( my $token = $req->param('token') ) {
if ( my $tmp = $self->ott->getToken($token) ) {
my $uid = $req->userData->{ $self->conf->{whatToTrace} };
if ( $tmp->{name} eq $uid ) {
if ( my $fg = $req->param('fg') ) {
my $ps = Lemonldap::NG::Common::Session->new(
storageModule => $self->conf->{globalStorage},
storageModuleOptions =>
$self->conf->{globalStorageOptions},
kind => "SSO",
info => {
_utime => time + $self->timeout(),
_session_uid => $uid,
_connectedSince => time,
dataKeep => $req->data->{dataToKeep},
fingerprint => $fg,
},
);
# Cookie available 30 days
$req->addCookie(
$self->p->cookie(
name => $self->cookieName(),
value => $ps->id,
max_age => $self->timeout(),
secure => $self->conf->{securedCookie},
)
);
$req->sessionInfo->{_loginHistory} = $tmp->{history}
if exists $tmp->{history};
}
else {
$self->logger->warn("Browser hasn't return fingerprint");
}
}
else {
$self->userLogger->error(
"StayConnected: mismatch UID: $tmp->{name} / $uid");
}
}
else {
$self->userLogger->error(
"StayConnected called with an expired token");
}
}
else {
$self->userLogger->error('StayConnected called without token');
}
# Return persistent connection cookie
return $self->p->do( $req, [ @{ $self->p->endAuth }, sub { PE_OK } ] );
}
# Check for:
# - persistent connection cookie
# - valid session
# - uniq id is kept
# Then delete authentication methods from "steps" array.
sub check {
my ( $self, $req ) = @_;
if ( my $cid = $req->cookies->{ $self->cookieName() } ) {
my $ps = Lemonldap::NG::Common::Session->new(
storageModule => $self->conf->{globalStorage},
storageModuleOptions => $self->conf->{globalStorageOptions},
kind => "SSO",
id => $cid,
);
if ( $ps
and my $uid = $ps->data->{_session_uid}
and time() < $ps->data->{_utime} )
{
$self->logger->debug('Persistent connection found');
if ( my $fg = $req->param('fg')
and my $token = $req->param('token') )
{
if ( my $prm = $self->ott->getToken($token) ) {
$req->data->{dataKeep} = $ps->data->{dataKeep};
$self->logger->debug('Persistent connection found');
if ( $fg eq $ps->data->{fingerprint} ) {
$req->user($uid);
my @steps =
grep {
!ref $_
and $_ !~ /^(?:extractFormInfo|authenticate)$/
} @{ $req->steps };
$req->steps( \@steps );
$self->userLogger->notice(
"$uid connected by StayConnected cookie");
return PE_OK;
}
else {
$self->userLogger->warn("Fingerprint changed for $uid");
$ps->remove;
$self->logout($req);
}
}
else {
$self->userLogger->notice(
"StayConnected: expired token for $uid");
}
}
else {
my $token = $self->ott->createToken( $req->parameters );
$req->response(
$self->p->sendHtml(
$req,
'../common/registerBrowser',
params => {
TOKEN => $token,
ACTION => '#',
}
)
);
return PE_SENDRESPONSE;
}
}
else {
$self->userLogger->notice('Persistent connection expired');
unless ( $ps->{error} ) {
$self->logger->debug(
'Persistent connection session id = ' . $ps->{id} );
$self->logger->debug( 'Persistent connection session _utime = '
. $ps->data->{_utime} );
$ps->remove;
}
}
}
return PE_OK;
}
sub logout {
my ( $self, $req ) = @_;
$req->addCookie(
$self->p->cookie(
name => $self->cookieName(),
value => 0,
expires => 'Wed, 21 Oct 2015 00:00:00 GMT',
secure => $self->conf->{securedCookie},
)
);
return PE_OK;
}
1;