package Lemonldap::NG::Portal::Lib::Notifications::XML; use strict; use Mouse; use XML::LibXML; use XML::LibXSLT; our $VERSION = '2.0.0'; # Lemonldap::NG::Portal::Main::Plugin provides addAuthRoute() and # addUnauthRoute() methods in addition of Lemonldap::NG::Common::Module. extends 'Lemonldap::NG::Portal::Main::Plugin'; # PROPERTIES # XML parser has parser => ( is => 'rw', builder => sub { return XML::LibXML->new(); } ); # XSLT document has stylesheet => ( is => 'rw', lazy => 1, builder => sub { my $self = $_[0]; my $xslt = XML::LibXSLT->new(); my $styleFile = ( $self->conf->{notificationXSLTfile} and -e $self->conf->{notificationXSLTfile} ) ? $self->conf->{notificationXSLTfile} : $self->conf->{templatesDir} . '/common/notification.xsl'; unless ( -e $styleFile ) { $self->{logger}->error("$styleFile not found, aborting"); die "$styleFile not found"; } return $xslt->parse_stylesheet( $self->parser->parse_file($styleFile) ); } ); # Underlying notifications storage object (File, DBI, LDAP,...) has notifObject => ( is => 'rw' ); # INITIALIZATION sub init { 1; } # Search for notifications and if any, returns HTML fragment. sub checkForNotifications { my ( $self, $req ) = @_; # Look for pending notifications in database my $uid = $req->sessionInfo->{ $self->notifObject->notifField }; my ( $notifs, $forUser ) = $self->notifObject->getNotifications($uid); my $form; return 0 unless ($notifs); # Transform notifications my $i = 0; #Files count foreach my $file ( values %$notifs ) { my $xml = $self->parser->parse_string($file); my $j = 0; #Notifications count LOOP: foreach my $notif ( eval { $xml->documentElement->getElementsByTagName('notification') } ) { # Get the reference my $reference = $notif->getAttribute('reference'); $self->logger->debug("Get reference $reference"); # Check it in session if ( exists $req->{sessionInfo}->{"notification_$reference"} ) { # The notification was already accepted $self->logger->debug( "Notification $reference was already accepted"); # Remove it from XML $notif->unbindNode(); next LOOP; } # Check condition if any my $condition = $notif->getAttribute('condition'); if ($condition) { $self->logger->debug("Get condition $condition"); $condition = $self->p->HANDLER->substitute($condition); unless ( $condition = $self->p->HANDLER->buildSub($condition) ) { $self->logger->error( 'Notification condition error: ' . $self->p->HANDLER->tsv->{jail}->error ); $notif->unbindNode(); next LOOP; } unless ( $condition->( $req, $req->sessionInfo ) ) { $self->logger->debug( 'Notification condition not authorizated'); # Remove it from XML $notif->unbindNode(); next LOOP; } } $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->userLogger->warn( "Bad XML file: a notification for $uid was not done ($@)"); return 0; } # Stop here if nothing to display return 0 unless $i; # Returns HTML fragment $req->id( $self->p->HANDLER->tsv->{cipher}->encrypt( $req->id ) ); return $form; } sub getNotifBack { my ( $self, $req, $name ) = @_; # Look if all notifications have been accepted. If not, redirects to # portal # Search for Lemonldap::NG cookie (ciphered) my $id; unless ( $id = $req->cookies->{ $self->{conf}->{cookieName} } ) { return $self->p->sendError( $req, 'No cookie found', 401 ); } $id = $self->p->HANDLER->tsv->{cipher}->decrypt($id) or return $self->sendError( $req, 'Unable to decrypt', 500 ); # Verify that session exists $req->userData( $self->p->HANDLER->retrieveSession( $req, $id ) ) or return $self->sendError( $req, 'Unknown session', 401 ); # Restore datas $self->p->importHandlerDatas($req); my $uid = $req->sessionInfo->{ $self->notifObject->notifField }; my ( $notifs, $forUser ) = $self->notifObject->getNotifications($uid); if ($notifs) { # Get accepted notifications my ( $refs, $checks ) = ( {}, {} ); my $prms = $req->parameters; foreach ( keys %$prms ) { my $v = $prms->{$_}; if (s/^reference//) { $refs->{$v} = $_; } elsif ( s/^check// and /^(\d+x\d+)x(\d+)$/ and $v eq 'accepted' ) { push @{ $checks->{$1} }, $2; } } my $result = 1; foreach my $fileName ( keys %$notifs ) { my $file = $notifs->{$fileName}; my $fileResult = 1; my $xml = $self->parser->parse_string($file); # Get pending notifications and verify that they have been accepted foreach my $notif ( $xml->documentElement->getElementsByTagName('notification') ) { my $reference = $notif->getAttribute('reference'); # 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} } ) { $self->userLogger->notice( "$uid has not accepted notification $reference" ); $result = $fileResult = 0; next; } } } else { # Current pending notification has not been found in # request $result = $fileResult = 0; $self->logger->debulogger->debug( 'Current pending notification has not been found', 'debug' ); next; } # Register acceptation $self->userLogger->notice( "$uid has accepted notification $reference"); $self->p->updatePersistentSession( $req, { "notification_$reference" => time() } ); $self->logger->debug( "Notification $reference registered in persistent session" ); } # Notifications accepted for this file, delete it unless it's a wildcard if ( $fileResult and exists $forUser->{$fileName} ) { $self->logger->debug("Notification file deleted"); $self->notifObject->delete($fileName); } } 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 ? $self->logger->debug( 'Pending notification has been found and not accepted'); return $self->p->do( $req, $self->p->afterDatas ); } # All pending notifications have been accepted, restore cookies and # launch 'controlUrl' to restore "urldc" using do() $self->logger->debug('All pending notifications have been accepted'); $self->p->rebuildCookies($req); return $self->p->do( $req, ['controlUrl'] ); } else { # No notifications checked here, this entry point must not be called. # Redirecting to portal $self->logger->debug('No notifications checked'); $req->mustRedirect(1); return $self->p->do( $req, [] ); } } has imported => ( is => 'rw', default => 0 ); has server => ( is => 'rw' ); sub notificationServer { my ( $self, $req ) = @_; unless ( $self->imported ) { eval { require Lemonldap::NG::Common::PSGI::SOAPServer; require Lemonldap::NG::Common::PSGI::SOAPService; }; if ($@) { return $self->p->sendError( $req, $@, 500 ); } $self->server( Lemonldap::NG::Common::PSGI::SOAPServer->new ); $self->imported(1); } unless ( $req->env->{HTTP_SOAPACTION} ) { return $self->p->sendError( $req, 'SOAP requests only', 400 ); } return $self->server->dispatch_to( Lemonldap::NG::Common::PSGI::SOAPService->new( $self, $req, 'newNotification', ) )->handle($req); } sub newNotification { my ( $self, $req, $xml ) = @_; return $self->notifObject->newNotification($xml); } 1;