# 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.0.10';

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;
