lemonldap-ng/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/Notifications.pm

344 lines
11 KiB
Perl
Raw Normal View History

2016-05-29 09:48:06 +02:00
# Notifications plugin.
#
2016-05-31 13:47:08 +02:00
# Two entry points for notifications:
2016-05-29 09:48:06 +02:00
# * a new route "/notifback" for checking accepted notifications
# (sub getNotifBack). It launch then autoRedirect() with "mustRedirect"
# set to 1 because underlying handler has not seen user as authenticated
# so datas are not set;
2016-05-31 13:47:08 +02:00
# * a callback inserted in process steps after authentication process,
# This callback launches checkForNotifications to get notification and
# cipher LemonLDAP::NG cookies.
2016-05-29 09:48:06 +02:00
2016-05-28 10:33:39 +02:00
package Lemonldap::NG::Portal::Plugins::Notifications;
use strict;
use Mouse;
2016-05-30 22:20:53 +02:00
use XML::LibXSLT;
2016-05-29 09:48:06 +02:00
use Lemonldap::NG::Portal::Main::Constants qw(PE_OK PE_NOTIFICATION);
2016-05-28 10:33:39 +02:00
our $VERSION = '2.0.0';
2016-05-29 09:48:06 +02:00
# Lemonldap::NG::Portal::Main::Plugin provides addAuthRoute() and
2016-06-02 23:20:36 +02:00
# addUnauthRoute() methods in addition of Lemonldap::NG::Common::Module.
2016-05-28 10:33:39 +02:00
extends 'Lemonldap::NG::Portal::Main::Plugin';
2016-06-09 20:40:20 +02:00
# PROPERTIES
2016-06-02 23:20:36 +02:00
sub parser {
return $_[0]->notifObject->parser;
}
2016-05-28 10:33:39 +02:00
2016-05-29 09:48:06 +02:00
# XSLT document
has stylesheet => (
is => 'rw',
lazy => 1,
builder => sub {
2016-05-30 07:15:57 +02:00
my $self = $_[0];
my $xslt = XML::LibXSLT->new();
my $styleFile = (
2016-05-30 22:20:53 +02:00
(
$self->conf->{notificationXSLTfile}
and -e $self->conf->{notificationXSLTfile}
)
2016-05-29 09:48:06 +02:00
? $self->conf->{notificationXSLTfile}
2016-05-30 22:20:53 +02:00
: $self->conf->{templatesDir} . '/common/notification.xsl'
2016-05-30 07:15:57 +02:00
);
2016-05-30 22:20:53 +02:00
die "$styleFile not found" unless ( -e $styleFile );
return $xslt->parse_stylesheet( $self->parser->parse_file($styleFile) );
2016-05-29 09:48:06 +02:00
}
);
# Underlying notifications storage object (File, DBI, LDAP,...)
has notifObject => ( is => 'rw' );
2016-06-09 20:40:20 +02:00
# INTERFACE
2016-05-29 09:48:06 +02:00
# Declare additional process steps
sub afterDatas { 'checkNotifDuringAuth' }
2016-05-28 10:33:39 +02:00
2016-06-09 20:40:20 +02:00
# For now, notifications are done only during authentication process
#sub forAuthUser { 'checkNotifForAuthUser' }
# INITIALIZATION
2016-05-28 10:33:39 +02:00
sub init {
my ($self) = @_;
2016-05-29 09:48:06 +02:00
# Declare new route
$self->addUnauthRoute( 'notifback' => 'getNotifBack', ['POST'] );
$self->addAuthRoute( 'notifback' => 'getNotifBack', ['POST'] );
# Search for configuration options
2016-05-28 10:33:39 +02:00
my $type = $self->conf->{notificationStorage};
unless ($type) {
$self->error('notificationStorage is not defined, aborting');
return 0;
}
2016-05-29 09:48:06 +02:00
# Initialize notifications storage object
2016-06-02 23:20:36 +02:00
$type = "Lemonldap::NG::Common::Notifications::$type";
2016-05-29 09:48:06 +02:00
eval "require $type";
2016-05-28 10:33:39 +02:00
if ($@) {
2016-06-02 23:20:36 +02:00
$self->error(
"Unable to load Lemonldap::NG::Common::Notifications::$type: $@");
2016-05-28 10:33:39 +02:00
return 0;
}
# TODO: use conf database?
2016-06-02 23:20:36 +02:00
my $prms = {
%{ $self->conf->{notificationStorageOptions} },
p => $self->p,
conf => $self->p->conf
};
unless ( eval { $self->notifObject( $type->new($prms) ); } ) {
2016-05-28 10:33:39 +02:00
$self->error($@);
return 0;
}
1;
}
2016-05-31 13:47:08 +02:00
#sub checkNotifForAuthUser {
# my ( $self, $req ) = @_;
# if ( my $notif = $self->checkForNotifications($req) ) {
#
# # Cipher cookies
# return PE_NOTIFICATION;
# }
# else {
# return PE_OK;
# }
#}
2016-05-29 09:48:06 +02:00
2016-06-09 20:40:20 +02:00
# RUNNING METHODS
2016-05-31 13:47:08 +02:00
sub checkNotifDuringAuth {
2016-05-29 09:48:06 +02:00
my ( $self, $req ) = @_;
2016-05-31 23:52:18 +02:00
if ( $req->{datas}->{notification} = $self->checkForNotifications($req) ) {
2016-05-31 13:47:08 +02:00
$self->rebuildCookies($req);
2016-05-29 09:48:06 +02:00
# Restore and cipher cookies
2016-05-30 22:20:53 +02:00
return PE_NOTIFICATION;
2016-05-29 09:48:06 +02:00
}
else {
return PE_OK;
}
}
# Search for notifications and if any, returns HTML fragment.
# TODO: replace this by JSON+Ajax
2016-05-28 10:33:39 +02:00
sub checkForNotifications {
my ( $self, $req ) = @_;
# Look for pending notifications in database
2016-06-02 23:20:36 +02:00
my $uid = $req->sessionInfo->{ $self->notifObject->notifField };
my ( $notifs, $forUser ) = $self->notifObject->getNotifications($uid);
2016-05-29 09:48:06 +02:00
my $form;
return 0 unless ($notifs);
# Transform notifications
2016-05-30 07:15:57 +02:00
my $i = 0; #Files count
foreach my $file ( values %$notifs ) {
2016-05-29 09:48:06 +02:00
eval {
my $xml = $self->parser->parse_string($file);
2016-05-30 07:15:57 +02:00
my $j = 0; #Notifications count
foreach my $notif (
$xml->documentElement->getElementsByTagName('notification') )
{
2016-05-29 09:48:06 +02:00
# Get the reference
my $reference = $notif->getAttribute('reference');
$self->lmLog( "Get reference $reference", 'debug' );
# Check it in session
2016-05-30 07:15:57 +02:00
if ( exists $req->{sessionInfo}->{"notification_$reference"} ) {
2016-05-29 09:48:06 +02:00
# The notification was already accepted
$self->lmLog(
"Notification $reference was already accepted",
'debug' );
# Remove it from XML
$notif->unbindNode();
next;
}
# Check condition if any
my $condition = $notif->getAttribute('condition');
if ($condition) {
$self->lmLog( "Get condition $condition", 'debug' );
unless ( $self->p->HANDLER->safe->reval($condition) ) {
$self->lmLog( "Notification condition not accepted",
'debug' );
# Remove it from XML
$notif->unbindNode();
next;
}
}
$j++;
}
# Go to next file if no notification found
next unless $j;
$i++;
# Transform XML into HTML
my $results = $self->stylesheet->transform( $xml, start => $i );
$form .= $self->stylesheet->output_string($results);
};
if ($@) {
$self->lmLog(
"Bad XML file: a notification for $uid was not done ($@)",
'warn' );
return 0;
}
}
# Stop here if nothing to display
return 0 unless $i;
2016-05-28 10:33:39 +02:00
2016-05-29 09:48:06 +02:00
# Returns HTML fragment
2016-05-31 13:47:08 +02:00
$req->id( $self->p->HANDLER->tsv->{cipher}->encrypt( $req->id ) );
2016-05-29 09:48:06 +02:00
return $form;
2016-05-28 10:33:39 +02:00
}
sub getNotifBack {
my ( $self, $req, $name ) = @_;
2016-05-29 09:48:06 +02:00
# Look if all notifications have been accepted. If not, redirects to
2016-05-28 10:33:39 +02:00
# portal
2016-05-30 07:15:57 +02:00
# Search for Lemonldap::NG cookie (ciphered)
my $id;
unless ( $id = $req->cookies->{ $self->{conf}->{cookieName} } ) {
2016-05-31 22:41:35 +02:00
return $self->p->sendError( $req, 'No cookie found', 401 );
2016-05-30 07:15:57 +02:00
}
2016-05-31 13:47:08 +02:00
$id = $self->p->HANDLER->tsv->{cipher}->decrypt($id)
2016-05-31 22:41:35 +02:00
or return $self->sendError( $req, 'Unable to decrypt', 500 );
2016-05-30 07:15:57 +02:00
# Verify that session exists
2016-08-02 15:52:29 +02:00
$req->userData( $self->p->HANDLER->retrieveSession($id) )
2016-05-31 22:41:35 +02:00
or return $self->sendError( $req, 'Unknown session', 401 );
2016-05-30 07:15:57 +02:00
# Restore datas
$self->p->importHandlerDatas($req);
2016-06-02 23:20:36 +02:00
my $uid = $req->sessionInfo->{ $self->notifObject->notifField };
2016-05-28 10:33:39 +02:00
2016-06-02 23:20:36 +02:00
my ( $notifs, $forUser ) = $self->notifObject->getNotifications($uid);
2016-06-01 07:20:55 +02:00
if ($notifs) {
2016-05-30 13:32:21 +02:00
# Get accepted notifications
2016-05-31 23:52:18 +02:00
my ( $refs, $checks ) = ( {}, {} );
my $prms = $req->parameters;
2016-05-30 13:32:21 +02:00
foreach ( keys %$prms ) {
2016-05-30 22:20:53 +02:00
my $v = $prms->{$_};
2016-05-30 13:32:21 +02:00
if (s/^reference//) {
2016-05-31 23:52:18 +02:00
$refs->{$v} = $_;
2016-05-30 13:32:21 +02:00
}
2016-05-31 23:52:18 +02:00
elsif ( s/^check// and /^(\d+x\d+)x(\d+)$/ and $v eq 'accepted' ) {
2016-05-30 13:32:21 +02:00
push @{ $checks->{$1} }, $2;
}
}
2016-05-31 23:52:18 +02:00
2016-06-01 07:20:55 +02:00
my $result = 1;
2016-06-01 21:19:53 +02:00
foreach my $fileName ( keys %$notifs ) {
my $file = $notifs->{$fileName};
2016-06-01 07:20:55 +02:00
my $fileResult = 1;
my $xml = $self->parser->parse_string($file);
2016-05-31 23:52:18 +02:00
# Get pending notifications and verify that they have been accepted
2016-05-30 22:20:53 +02:00
foreach my $notif (
$xml->documentElement->getElementsByTagName('notification') )
{
2016-05-30 13:32:21 +02:00
my $reference = $notif->getAttribute('reference');
2016-05-30 22:20:53 +02:00
2016-05-31 23:52:18 +02:00
# Check if this pending notification has been seen
if ( my $refId = $refs->{$reference} ) {
# Verity that checkboxes have been checked
my @toCheck = $notif->getElementsByTagName('check');
if ( my $toCheckCount = @toCheck ) {
unless ($checks->{$refId}
and $toCheckCount == @{ $checks->{$refId} } )
{
2016-06-01 07:20:55 +02:00
$self->p->userNotice(
"$uid has not accepted notification $reference"
);
$result = $fileResult = 0;
next;
2016-05-31 23:52:18 +02:00
}
}
2016-05-30 07:15:57 +02:00
}
2016-05-30 13:32:21 +02:00
else {
2016-06-01 07:20:55 +02:00
# Current pending notification has not been found in
# request
$result = $fileResult = 0;
2016-08-02 15:52:29 +02:00
$self->lmLog(
'Current pending notification has not been found',
'debug' );
2016-06-01 07:20:55 +02:00
next;
2016-05-30 07:15:57 +02:00
}
2016-06-01 07:20:55 +02:00
# Register acceptation
$self->p->userNotice(
"$uid has accepted notification $reference");
2016-06-01 19:36:51 +02:00
$self->p->updatePersistentSession( $req,
2016-06-01 07:20:55 +02:00
{ "notification_$reference" => time() } );
$self->lmLog(
"Notification $reference registered in persistent session",
'debug'
);
2016-05-30 07:15:57 +02:00
}
2016-06-01 07:20:55 +02:00
# Notifications accepted for this file, delete it unless it's a wildcard
2016-06-01 21:19:53 +02:00
if ( $fileResult and exists $forUser->{$fileName} ) {
2016-06-01 07:20:55 +02:00
$self->lmLog( "Notification file deleted", 'debug' );
2016-06-01 21:19:53 +02:00
$self->notifObject->delete($fileName);
2016-06-01 07:20:55 +02:00
}
}
unless ($result) {
# One pending notification has been found and not accepted,
# restart process to display pending notifications
# TODO: is it a good idea to launch all 'afterDatas' subs ?
2016-08-02 15:52:29 +02:00
$self->lmLog(
'Pending notification has been found and not accepted',
'debug' );
2016-06-01 07:20:55 +02:00
return $self->p->do( $req, $self->p->afterDatas );
2016-05-30 07:15:57 +02:00
}
2016-05-31 23:52:18 +02:00
2016-08-02 15:52:29 +02:00
# All pending notifications have been accepted, restore cookies and
2016-06-01 07:20:55 +02:00
# launch 'controlUrl' to restore "urldc" using do()
2016-08-02 15:52:29 +02:00
$self->lmLog( 'All pending notifications have been accepted', 'debug' );
2016-05-31 23:52:18 +02:00
$self->rebuildCookies($req);
$self->p->do( $req, ['controlUrl'] );
2016-05-30 13:32:21 +02:00
}
else {
2016-05-31 23:52:18 +02:00
# No notifications checked here, this entry point must not be called.
# Redirecting to portal
2016-08-02 15:52:29 +02:00
$self->lmLog( 'No notifications checked', 'debug' );
2016-05-31 23:52:18 +02:00
$req->mustRedirect(1);
$self->p->do( $req, [] );
2016-05-30 07:15:57 +02:00
}
2016-05-30 13:32:21 +02:00
}
2016-05-31 13:47:08 +02:00
sub rebuildCookies {
my ( $self, $req ) = @_;
my @tmp;
for ( my $i = 0 ; $i < @{ $req->{respHeaders} } ; $i += 2 ) {
push @tmp, $req->respHeaders->[0], $req->respHeaders->[1]
2016-05-31 22:41:35 +02:00
unless ( $req->respHeaders->[0] eq 'Set-Cookie' );
2016-05-31 13:47:08 +02:00
}
2016-05-31 22:41:35 +02:00
$req->{respHeaders} = \@tmp;
2016-05-31 13:47:08 +02:00
$self->p->buildCookie($req);
}
2016-05-30 13:32:21 +02:00
1;