File Coverage

blib/lib/Lemonldap/NG/Portal/Simple.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             ##@file
2             # Base package for Lemonldap::NG portal
3              
4             ##@class Lemonldap::NG::Portal::Simple
5             # Base class for Lemonldap::NG portal
6             package Lemonldap::NG::Portal::Simple;
7              
8 22     22   307204 use strict;
  22         41  
  22         866  
9 22     22   104 use warnings;
  22         33  
  22         784  
10              
11 22     22   188 use Exporter 'import';
  22         41  
  22         737  
12              
13 22     22   91 use warnings;
  22         29  
  22         668  
14 22     22   12788 use MIME::Base64;
  22         15933  
  22         1528  
15 22     22   5341 use Lemonldap::NG::Common::CGI;
  0            
  0            
16             use CGI::Cookie;
17             use POSIX qw(strftime);
18             use Lemonldap::NG::Portal::_i18n; #inherits
19             use Lemonldap::NG::Common::Captcha;
20             use Lemonldap::NG::Common::Session;
21             use Lemonldap::NG::Common::Apache::Session
22             ; #link protected session Apache::Session object
23             use Lemonldap::NG::Common::Safe; #link protected safe Safe object
24             use Lemonldap::NG::Common::Safelib;
25             use Digest::MD5;
26              
27             # Special comments for doxygen
28             #inherits Lemonldap::NG::Portal::_SOAP
29             #inherits Lemonldap::NG::Portal::AuthApache;
30             #inherits Lemonldap::NG::Portal::AuthAD;
31             #inherits Lemonldap::NG::Portal::AuthCAS;
32             #inherits Lemonldap::NG::Portal::AuthChoice;
33             #inherits Lemonldap::NG::Portal::AuthDBI;
34             #inherits Lemonldap::NG::Portal::AuthFacebook;
35             #inherits Lemonldap::NG::Portal::AuthGoogle;
36             #inherits Lemonldap::NG::Portal::AuthLDAP;
37             #inherits Lemonldap::NG::Portal::AuthMulti;
38             #inherits Lemonldap::NG::Portal::AuthNull;
39             #inherits Lemonldap::NG::Portal::AuthOpenID;
40             #inherits Lemonldap::NG::Portal::AuthProxy;
41             #inherits Lemonldap::NG::Portal::AuthRadius;
42             #inherits Lemonldap::NG::Portal::AuthRemote;
43             #inherits Lemonldap::NG::Portal::AuthSAML;
44             #inherits Lemonldap::NG::Portal::AuthSSL;
45             #inherits Lemonldap::NG::Portal::AuthTwitter;
46             #inherits Lemonldap::NG::Portal::Display;
47             #inherits Lemonldap::NG::Portal::IssuerDBCAS
48             #inherits Lemonldap::NG::Portal::IssuerDBNull
49             #inherits Lemonldap::NG::Portal::IssuerDBOpenID
50             #inherits Lemonldap::NG::Portal::IssuerDBSAML
51             #inherits Lemonldap::NG::Portal::Menu
52             #link Lemonldap::NG::Common::Notification protected notification
53             #inherits Lemonldap::NG::Portal::PasswordDBChoice;
54             #inherits Lemonldap::NG::Portal::PasswordDBDBI;
55             #inherits Lemonldap::NG::Portal::PasswordDBLDAP;
56             #inherits Lemonldap::NG::Portal::PasswordDBNull;
57             #inherits Lemonldap::NG::Portal::UserDBAD;
58             #inherits Lemonldap::NG::Portal::UserDBChoice;
59             #inherits Lemonldap::NG::Portal::UserDBDBI;
60             #inherits Lemonldap::NG::Portal::UserDBFacebook;
61             #inherits Lemonldap::NG::Portal::UserDBGoogle;
62             #inherits Lemonldap::NG::Portal::UserDBLDAP;
63             #inherits Lemonldap::NG::Portal::UserDBMulti;
64             #inherits Lemonldap::NG::Portal::UserDBNull;
65             #inherits Lemonldap::NG::Portal::UserDBOpenID;
66             #inherits Lemonldap::NG::Portal::UserDBProxy;
67             #inherits Lemonldap::NG::Portal::UserDBRemote;
68             #inherits Lemonldap::NG::Portal::UserDBSAML;
69             #inherits Lemonldap::NG::Portal::PasswordDBDBI
70             #inherits Lemonldap::NG::Portal::PasswordDBLDAP
71             #inherits Apache::Session
72             #link Lemonldap::NG::Common::Apache::Session::SOAP protected globalStorage
73              
74             our $VERSION = '1.4.2';
75              
76             use base qw(Lemonldap::NG::Common::CGI Exporter);
77             our @ISA;
78              
79             # Constants
80             use constant {
81              
82             # Portal errors
83             # Developers warning, do not use PE_INFO, it's reserved to autoRedirect.
84             # If you want to send an information, use $self->info('text').
85             PE_IMG_NOK => -5,
86             PE_IMG_OK => -4,
87             PE_INFO => -3,
88             PE_REDIRECT => -2,
89             PE_DONE => -1,
90             PE_OK => 0,
91             PE_SESSIONEXPIRED => 1,
92             PE_FORMEMPTY => 2,
93             PE_WRONGMANAGERACCOUNT => 3,
94             PE_USERNOTFOUND => 4,
95             PE_BADCREDENTIALS => 5,
96             PE_LDAPCONNECTFAILED => 6,
97             PE_LDAPERROR => 7,
98             PE_APACHESESSIONERROR => 8,
99             PE_FIRSTACCESS => 9,
100             PE_BADCERTIFICATE => 10,
101             PE_PP_ACCOUNT_LOCKED => 21,
102             PE_PP_PASSWORD_EXPIRED => 22,
103             PE_CERTIFICATEREQUIRED => 23,
104             PE_ERROR => 24,
105             PE_PP_CHANGE_AFTER_RESET => 25,
106             PE_PP_PASSWORD_MOD_NOT_ALLOWED => 26,
107             PE_PP_MUST_SUPPLY_OLD_PASSWORD => 27,
108             PE_PP_INSUFFICIENT_PASSWORD_QUALITY => 28,
109             PE_PP_PASSWORD_TOO_SHORT => 29,
110             PE_PP_PASSWORD_TOO_YOUNG => 30,
111             PE_PP_PASSWORD_IN_HISTORY => 31,
112             PE_PP_GRACE => 32,
113             PE_PP_EXP_WARNING => 33,
114             PE_PASSWORD_MISMATCH => 34,
115             PE_PASSWORD_OK => 35,
116             PE_NOTIFICATION => 36,
117             PE_BADURL => 37,
118             PE_NOSCHEME => 38,
119             PE_BADOLDPASSWORD => 39,
120             PE_MALFORMEDUSER => 40,
121             PE_SESSIONNOTGRANTED => 41,
122             PE_CONFIRM => 42,
123             PE_MAILFORMEMPTY => 43,
124             PE_BADMAILTOKEN => 44,
125             PE_MAILERROR => 45,
126             PE_MAILOK => 46,
127             PE_LOGOUT_OK => 47,
128             PE_SAML_ERROR => 48,
129             PE_SAML_LOAD_SERVICE_ERROR => 49,
130             PE_SAML_LOAD_IDP_ERROR => 50,
131             PE_SAML_SSO_ERROR => 51,
132             PE_SAML_UNKNOWN_ENTITY => 52,
133             PE_SAML_DESTINATION_ERROR => 53,
134             PE_SAML_CONDITIONS_ERROR => 54,
135             PE_SAML_IDPSSOINITIATED_NOTALLOWED => 55,
136             PE_SAML_SLO_ERROR => 56,
137             PE_SAML_SIGNATURE_ERROR => 57,
138             PE_SAML_ART_ERROR => 58,
139             PE_SAML_SESSION_ERROR => 59,
140             PE_SAML_LOAD_SP_ERROR => 60,
141             PE_SAML_ATTR_ERROR => 61,
142             PE_OPENID_EMPTY => 62,
143             PE_OPENID_BADID => 63,
144             PE_MISSINGREQATTR => 64,
145             PE_BADPARTNER => 65,
146             PE_MAILCONFIRMATION_ALREADY_SENT => 66,
147             PE_PASSWORDFORMEMPTY => 67,
148             PE_CAS_SERVICE_NOT_ALLOWED => 68,
149             PE_MAILFIRSTACCESS => 69,
150             PE_MAILNOTFOUND => 70,
151             PE_PASSWORDFIRSTACCESS => 71,
152             PE_MAILCONFIRMOK => 72,
153             PE_RADIUSCONNECTFAILED => 73,
154             PE_MUST_SUPPLY_OLD_PASSWORD => 74,
155             PE_FORBIDDENIP => 75,
156             PE_CAPTCHAERROR => 76,
157             PE_CAPTCHAEMPTY => 77,
158             PE_REGISTERFIRSTACCESS => 78,
159             PE_REGISTERFORMEMPTY => 79,
160             PE_REGISTERALREADYEXISTS => 80,
161              
162             # Portal messages
163             PM_USER => 0,
164             PM_DATE => 1,
165             PM_IP => 2,
166             PM_SESSIONS_DELETED => 3,
167             PM_OTHER_SESSIONS => 4,
168             PM_REMOVE_OTHER_SESSIONS => 5,
169             PM_PP_GRACE => 6,
170             PM_PP_EXP_WARNING => 7,
171             PM_SAML_IDPSELECT => 8,
172             PM_SAML_IDPCHOOSEN => 9,
173             PM_REMEMBERCHOICE => 10,
174             PM_SAML_SPLOGOUT => 11,
175             PM_REDIRECTION => 12,
176             PM_BACKTOSP => 13,
177             PM_BACKTOCASURL => 14,
178             PM_LOGOUT => 15,
179             PM_OPENID_EXCHANGE => 16,
180             PM_CDC_WRITER => 17,
181             PM_OPENID_RPNS => 18, # OpenID "requested parameter is not set"
182             PM_OPENID_PA => 19, # "OpenID policy available at"
183             PM_OPENID_AP => 20, # OpenID "Asked parameter"
184             PM_ERROR_MSG => 21,
185             PM_LAST_LOGINS => 22,
186             PM_LAST_FAILED_LOGINS => 23,
187             };
188              
189             # EXPORTER PARAMETERS
190             our @EXPORT = qw( PE_IMG_NOK PE_IMG_OK PE_INFO PE_REDIRECT PE_DONE PE_OK
191             PE_SESSIONEXPIRED PE_FORMEMPTY PE_WRONGMANAGERACCOUNT PE_USERNOTFOUND
192             PE_BADCREDENTIALS PE_LDAPCONNECTFAILED PE_LDAPERROR PE_APACHESESSIONERROR
193             PE_FIRSTACCESS PE_BADCERTIFICATE PE_PP_ACCOUNT_LOCKED PE_PP_PASSWORD_EXPIRED
194             PE_CERTIFICATEREQUIRED PE_ERROR PE_PP_CHANGE_AFTER_RESET
195             PE_PP_PASSWORD_MOD_NOT_ALLOWED PE_PP_MUST_SUPPLY_OLD_PASSWORD
196             PE_PP_INSUFFICIENT_PASSWORD_QUALITY PE_PP_PASSWORD_TOO_SHORT
197             PE_PP_PASSWORD_TOO_YOUNG PE_PP_PASSWORD_IN_HISTORY PE_PP_GRACE
198             PE_PP_EXP_WARNING PE_PASSWORD_MISMATCH PE_PASSWORD_OK PE_NOTIFICATION
199             PE_BADURL PE_NOSCHEME PE_BADOLDPASSWORD PE_MALFORMEDUSER PE_SESSIONNOTGRANTED
200             PE_CONFIRM PE_MAILFORMEMPTY PE_BADMAILTOKEN PE_MAILERROR PE_MAILOK
201             PE_LOGOUT_OK PE_SAML_ERROR PE_SAML_LOAD_SERVICE_ERROR PE_SAML_LOAD_IDP_ERROR
202             PE_SAML_SSO_ERROR PE_SAML_UNKNOWN_ENTITY PE_SAML_DESTINATION_ERROR
203             PE_SAML_CONDITIONS_ERROR PE_SAML_IDPSSOINITIATED_NOTALLOWED PE_SAML_SLO_ERROR
204             PE_SAML_SIGNATURE_ERROR PE_SAML_ART_ERROR PE_SAML_SESSION_ERROR
205             PE_SAML_LOAD_SP_ERROR PE_SAML_ATTR_ERROR PE_OPENID_EMPTY PE_OPENID_BADID
206             PE_MISSINGREQATTR PE_BADPARTNER PE_MAILCONFIRMATION_ALREADY_SENT
207             PE_PASSWORDFORMEMPTY PE_CAS_SERVICE_NOT_ALLOWED PE_MAILFIRSTACCESS
208             PE_MAILNOTFOUND PE_PASSWORDFIRSTACCESS PE_MAILCONFIRMOK
209             PE_MUST_SUPPLY_OLD_PASSWORD PE_FORBIDDENIP PE_CAPTCHAERROR PE_CAPTCHAEMPTY
210             PE_REGISTERFIRSTACCESS PE_REGISTERFORMEMPTY PE_REGISTERALREADYEXISTS
211             PM_USER PM_DATE PM_IP PM_SESSIONS_DELETED PM_OTHER_SESSIONS
212             PM_REMOVE_OTHER_SESSIONS PM_PP_GRACE PM_PP_EXP_WARNING
213             PM_SAML_IDPSELECT PM_SAML_IDPCHOOSEN PM_REMEMBERCHOICE PM_SAML_SPLOGOUT
214             PM_REDIRECTION PM_BACKTOSP PM_BACKTOCASURL PM_LOGOUT PM_OPENID_EXCHANGE
215             PM_CDC_WRITER PM_OPENID_RPNS PM_OPENID_PA PM_OPENID_AP PM_ERROR_MSG
216             PM_LAST_LOGINS PM_LAST_FAILED_LOGINS
217             );
218             our %EXPORT_TAGS = ( 'all' => [ @EXPORT, 'import' ], );
219              
220             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
221              
222             # Share secure jail between threads
223             our $safe;
224              
225             BEGIN {
226             eval {
227             require threads::shared;
228             threads::shared::share($safe);
229             };
230             }
231              
232             ##@cmethod Lemonldap::NG::Portal::Simple new(hashRef args)
233             # Class constructor.
234             #@param args hash reference
235             #@return Lemonldap::NG::Portal::Simple object
236             sub new {
237              
238             @ISA = qw(Lemonldap::NG::Common::CGI Exporter);
239             binmode( STDOUT, ":utf8" );
240             my $class = shift;
241             return $class if ( ref($class) );
242             my $self = $class->SUPER::new() or return undef;
243              
244             # Reinit _url
245             $self->{_url} = '';
246              
247             # Get global configuration
248             $self->getConf(@_)
249             or $self->abort( "Configuration error",
250             "Unable to get configuration: $Lemonldap::NG::Common::Conf::msg" );
251              
252             # Test mandatory elements
253              
254             # 1. Sessions backend
255             $self->abort( "Configuration error",
256             "You've to indicate a an Apache::Session storage module !" )
257             unless ( $self->{globalStorage} );
258              
259             # Use global storage for all backends by default
260              
261             # Persistent
262             $self->{persistentStorage} ||= $self->{globalStorage};
263             if ( !$self->{persistentStorageOptions}
264             or !%{ $self->{persistentStorageOptions} } )
265             {
266             $self->{persistentStorageOptions} = $self->{globalStorageOptions};
267             }
268              
269             # SAML
270             $self->{samlStorage} ||= $self->{globalStorage};
271             if ( !$self->{samlStorageOptions} or !%{ $self->{samlStorageOptions} } ) {
272             $self->{samlStorageOptions} = $self->{globalStorageOptions};
273             }
274              
275             # CAS
276             $self->{casStorage} ||= $self->{globalStorage};
277             if ( !$self->{casStorageOptions} or !%{ $self->{casStorageOptions} } ) {
278             $self->{casStorageOptions} = $self->{globalStorageOptions};
279             }
280              
281             # Captcha
282             $self->{captchaStorage} ||= $self->{globalStorage};
283             if ( !$self->{captchaStorageOptions}
284             or !%{ $self->{captchaStorageOptions} } )
285             {
286             $self->{captchaStorageOptions} = $self->{globalStorageOptions};
287             }
288              
289             # 2. Domain
290             $self->abort( "Configuration error",
291             "You've to indicate a domain for cookies" )
292             unless ( $self->{domain} );
293             $self->{domain} =~ s/^([^\.])/.$1/;
294              
295             # Load Display and Menu functions
296             $self->loadModule('Lemonldap::NG::Portal::Menu');
297             $self->loadModule('Lemonldap::NG::Portal::Display');
298              
299             # Rules to allow redirection
300             $self->{mustRedirect} =
301             defined $ENV{REQUEST_METHOD}
302             ? ( $ENV{REQUEST_METHOD} eq "POST" and not $self->param('newpassword') )
303             : $self->param('logout') ? 1
304             : 0;
305              
306             # Push authentication/userDB/passwordDB modules in @ISA
307             foreach my $type (qw(authentication userDB passwordDB registerDB)) {
308             my $module_name = 'Lemonldap::NG::Portal::';
309             my $db_type = $type;
310             my $db_name = $self->{$db_type};
311              
312             # Adapt module type to real module name
313             $db_type =~ s/authentication/Auth/;
314             $db_type =~ s/userDB/UserDB/;
315             $db_type =~ s/passwordDB/PasswordDB/;
316             $db_type =~ s/registerDB/RegisterDB/;
317              
318             # Full module name
319             $module_name .= $db_type . $db_name;
320              
321             # Remove white spaces
322             $module_name =~ s/\s.*$//;
323              
324             # Try to load module
325             $self->abort( "Configuration error", "Unable to load $module_name" )
326             unless $self->loadModule($module_name);
327              
328             # $self->{authentication} and $self->{userDB} can contains arguments
329             # (key1 = scalar_value; key2 = ...)
330             unless ( $db_name =~ /^Multi/ ) {
331             $db_name =~ s/^\w+\s*//;
332             my %h = split( /\s*[=;]\s*/, $db_name ) if ($db_name);
333             %$self = ( %h, %$self );
334             }
335             }
336              
337             # Check issuerDB path to load the correct issuerDB module
338             foreach my $issuerDBtype (qw(SAML OpenID CAS)) {
339             my $module_name = 'Lemonldap::NG::Portal::IssuerDB' . $issuerDBtype;
340              
341             $self->lmLog( "[IssuerDB activation] Try issuerDB module $issuerDBtype",
342             'debug' );
343              
344             # Check activation flag
345             my $activation =
346             $self->{ "issuerDB" . $issuerDBtype . "Activation" } ||= "0";
347              
348             unless ($activation) {
349             $self->lmLog(
350             "[IssuerDB activation] Activation flag set to off, trying next",
351             'debug'
352             );
353             next;
354             }
355              
356             # Check the path
357             my $path = $self->{ "issuerDB" . $issuerDBtype . "Path" };
358             if ( defined $path ) {
359             $self->lmLog( "[IssuerDB activation] Found path $path", 'debug' );
360              
361             # Get current path
362             my $url_path = $self->url( -absolute => 1 );
363             $url_path =~ s#^//#/#;
364             $self->lmLog(
365             "[IssuerDB activation] Path of current request is $url_path",
366             'debug' );
367              
368             # Match regular expression
369             if ( $url_path =~ m#$path# ) {
370             $self->abort( "Configuration error",
371             "Unable to load $module_name" )
372             unless $self->loadModule($module_name);
373              
374             # Remember loaded module
375             $self->{_activeIssuerDB} = $issuerDBtype;
376             $self->lmLog(
377             "[IssuerDB activation] IssuerDB module $issuerDBtype loaded",
378             'debug'
379             );
380             last;
381              
382             }
383             else {
384             $self->lmLog(
385             "[IssuerDB activation] Path do not match, trying next",
386             'debug' );
387             next;
388             }
389              
390             }
391             else {
392             $self->lmLog( "[IssuerDB activation] No path defined", 'debug' );
393             next;
394             }
395              
396             }
397              
398             # Load default issuerDB module if none was choosed
399             unless ( $self->{_activeIssuerDB} ) {
400              
401             # Manage old configuration format
402             my $db_type = $self->{'issuerDB'} || 'Null';
403              
404             my $module_name = 'Lemonldap::NG::Portal::IssuerDB' . $db_type;
405              
406             $self->abort( "Configuration error", "Unable to load $module_name" )
407             unless $self->loadModule($module_name);
408              
409             # Remember loaded module
410             $self->{_activeIssuerDB} = $db_type;
411             $self->lmLog( "[IssuerDB activation] IssuerDB module $db_type loaded",
412             'debug' );
413             }
414              
415             # Notifications
416             if ( $self->{notification} ) {
417             require Lemonldap::NG::Common::Notification;
418             my $tmp;
419              
420             # Use configuration options
421             if ( $self->{notificationStorage} ) {
422             $tmp->{type} = $self->{notificationStorage};
423             foreach ( keys %{ $self->{notificationStorageOptions} } ) {
424             $tmp->{$_} = $self->{notificationStorageOptions}->{$_};
425             }
426             }
427              
428             # Else use the configuration backend
429             else {
430             (%$tmp) = ( %{ $self->{lmConf} } );
431             $self->abort( "notificationStorage not defined",
432             "This parameter is required to use notification system" )
433             unless ( ref($tmp) );
434              
435             # Get the type
436             $tmp->{type} =~ s/.*:://;
437             $tmp->{type} =~ s/(CDBI|RDBI)/DBI/; # CDBI/RDBI are DBI
438              
439             # If type not File, DBI or LDAP, abort
440             $self->abort("Only File, DBI or LDAP supported for Notifications")
441             unless $tmp->{type} =~ /^(File|DBI|LDAP)$/;
442              
443             # Force table name
444             $tmp->{table} = 'notifications';
445             }
446              
447             $tmp->{p} = $self;
448             $self->{notifObject} = Lemonldap::NG::Common::Notification->new($tmp);
449             $self->abort($Lemonldap::NG::Common::Notification::msg)
450             unless ( $self->{notifObject} );
451             }
452              
453             # SOAP
454             if ( $self->{Soap} or $self->{soap} ) {
455             $self->loadModule('Lemonldap::NG::Portal::_SOAP');
456             if ( $self->{notification} and $ENV{PATH_INFO} ) {
457             $self->{CustomSOAPServices} ||= {};
458             $self->{CustomSOAPServices}->{'/notification'} = {
459             f => 'newNotification deleteNotification',
460             o => $self->{notifObject}
461             };
462             }
463             $self->startSoapServices();
464             }
465              
466             # Trusted domains
467             $self->{trustedDomains} ||= "";
468             $self->{trustedDomains} = "*"
469             if ( $self->{trustedDomains} =~ /(^|\s)\*(\s|$)/ );
470             if ( $self->{trustedDomains} and $self->{trustedDomains} ne "*" ) {
471             $self->{trustedDomains} =~ s#(^|\s+)\.#${1}[^/]+.#g;
472             $self->{trustedDomains} =
473             '(' . join( '|', split( /\s+/, $self->{trustedDomains} ) ) . ')';
474             $self->{trustedDomains} =~ s/\./\\./g;
475             }
476              
477             return $self;
478             }
479              
480             ##@method boolean loadModule(string module, boolean ignoreError)
481             # Load a module into portal namespace
482             # @param module module name
483             # @param ignoreError set to 1 if error should not appear in logs
484             # @return boolean
485             sub loadModule {
486             my ( $self, $module, $ignoreError ) = splice @_;
487              
488             return 1 unless $module;
489              
490             # Load module test
491             eval "require $module";
492             if ($@) {
493             $self->lmLog( "$module load error: $@", 'error' ) unless $ignoreError;
494             return 0;
495             }
496              
497             # Push module in @ISA
498             push @ISA, $module;
499              
500             $self->lmLog( "Module $module loaded", 'debug' );
501              
502             return 1;
503             }
504              
505             ##@method protected boolean getConf(hashRef args)
506             # Copy all parameters in caller object.
507             #@param args hash-ref
508             #@return True
509             sub getConf {
510             my ($self) = shift;
511             my %args;
512             if ( ref( $_[0] ) ) {
513             %args = %{ $_[0] };
514             }
515             else {
516             %args = @_;
517             }
518             %$self = ( %$self, %args );
519             1;
520             }
521              
522             ## @method protected void setHiddenFormValue(string fieldname, string value, string prefix, boolean base64)
523             # Add element into $self->{portalHiddenFormValues}, those values could be
524             # used to hide values into HTML form.
525             # @param fieldname The field name which will contain the correponding value
526             # @param value The associated value
527             # @param prefix Prefix of the field key
528             # @param base64 Encode value in base64
529             # @return nothing
530             sub setHiddenFormValue {
531             my ( $self, $key, $val, $prefix, $base64 ) = splice @_;
532              
533             # Default values
534             $prefix = "lmhidden_" unless defined $prefix;
535             $base64 = 1 unless defined $base64;
536              
537             # Store value
538             if ($val) {
539             $key = $prefix . $key;
540             $val = encode_base64($val) if $base64;
541             $self->{portalHiddenFormValues}->{$key} = $val;
542             }
543             }
544              
545             ## @method public void getHiddenFormValue(string fieldname, string prefix, boolean base64)
546             # Get value into $self->{portalHiddenFormValues}.
547             # @param fieldname The existing field name which contains a value
548             # @param prefix Prefix of the field key
549             # @param base64 Decode value from base64
550             # @return string The associated value
551             sub getHiddenFormValue {
552             my ( $self, $key, $prefix, $base64 ) = splice @_;
553              
554             # Default values
555             $prefix = "lmhidden_" unless defined $prefix;
556             $base64 = 1 unless defined $base64;
557              
558             $key = $prefix . $key;
559              
560             # Get value
561             if ( my $val = $self->param($key) ) {
562             $val = decode_base64($val) if $base64;
563             return $val;
564             }
565              
566             # No value found
567             return undef;
568             }
569              
570             ## @method protected void clearHiddenFormValue(arrayref keys)
571             # Clear values form stored hidden fields
572             # Delete all keys if no keys provided
573             # @param keys Array reference of keys
574             # @return nothing
575             sub clearHiddenFormValue {
576             my ( $self, $keys ) = splice @_;
577              
578             unless ( defined $keys ) {
579             delete $self->{portalHiddenFormValues};
580             }
581             else {
582             delete $self->{portalHiddenFormValues}->{$_} foreach (@$keys);
583             }
584              
585             return;
586             }
587              
588             ##@method public string buildHiddenForm()
589             # Return an HTML representation of hidden values.
590             # @return HTML code
591             sub buildHiddenForm {
592             my $self = shift;
593             my @keys = keys %{ $self->{portalHiddenFormValues} };
594             my $val = '';
595              
596             foreach (@keys) {
597              
598             # Check XSS attacks
599             next
600             if $self->checkXSSAttack( $_, $self->{portalHiddenFormValues}->{$_} );
601              
602             # Build hidden input HTML code
603             $val .= qq{<input type="hidden" name="$_" id="$_" value="}
604             . $self->{portalHiddenFormValues}->{$_} . '" />';
605             }
606              
607             return $val;
608             }
609              
610             ## @method void initCaptcha(void)
611             # init captcha module and generate captcha
612             # @return nothing
613             sub initCaptcha {
614             my $self = shift;
615              
616             # Create new captcha
617             my $captcha = Lemonldap::NG::Common::Captcha->new(
618             {
619             storageModule => $self->{captchaStorage},
620             storageModuleOptions => $self->{captchaStorageOptions},
621             size => $self->{captcha_size},
622             }
623             );
624              
625             $self->{captcha_secret} = $captcha->code;
626             $self->{captcha_code} = $captcha->md5;
627             $self->{captcha_img} = $self->{portal} . "?displayCaptcha=" . $captcha->md5;
628              
629             $self->lmLog( "Captcha code generated: " . $self->{captcha_code}, 'debug' );
630              
631             return;
632             }
633              
634             ## @method int checkCaptcha(code, ccode)
635             # Check captcha auth
636             # @param code that user enter in the form
637             # @param captcha code generated by Authen::Captcha
638             # @return a constant
639             sub checkCaptcha {
640             my ( $self, $code, $ccode ) = splice @_;
641              
642             # Get captcha object
643             my $captcha = Lemonldap::NG::Common::Captcha->new(
644             {
645             storageModule => $self->{captchaStorage},
646             storageModuleOptions => $self->{captchaStorageOptions},
647             md5 => $ccode,
648             size => $self->{captcha_size},
649             }
650             );
651              
652             # Remove captcha session (will not be used anymore)
653             if ( $captcha->removeSession ) {
654             $self->lmLog( "Code $code match captcha $ccode", 'debug' );
655             }
656             else {
657             $self->lmLog( "Unable to remove captcha session $ccode", 'warn' );
658             }
659              
660             # Check code
661             if ( $captcha && $captcha->code ) {
662              
663             if ( $code eq $captcha->code ) {
664             $self->lmLog( "Code $code match captcha $ccode", 'debug' );
665             return 1;
666             }
667             return -2;
668             }
669              
670             return 0;
671             }
672              
673             ## @method boolean isTrustedUrl(string url)
674             # Check if an URL's domain name is declared in LL::NG config or is declared as trusted domain
675             # @param url Parameter url
676             # @param value Parameter value
677             # @return 1 if url can be trusted, 0 else
678             sub isTrustedUrl {
679             my ( $self, $url ) = splice @_;
680             return
681             $url =~ m#^https?://$self->{reVHosts}(:\d+)?/#o
682             || $self->{trustedDomains} eq "*"
683             || $self->{trustedDomains}
684             && $url =~ m#^https?://$self->{trustedDomains}(:\d+)?/#o;
685             }
686              
687             ## @method boolean checkXSSAttack(string name, string value)
688             # Check value to detect XSS attack
689             # @param name Parameter name
690             # @param value Parameter value
691             # @return 1 if attack detected, 0 else
692             sub checkXSSAttack {
693             my ( $self, $name, $value ) = splice @_;
694              
695             # Empty values are not bad
696             return 0 unless $value;
697              
698             # Test value
699             if ( $value =~ m/(?:\0|<|'|"|`|\%(?:00|25|3C|22|27|2C))/ ) {
700             $self->lmLog( "XSS attack detected (param: $name | value: $value)",
701             "warn" );
702             return $self->{checkXSS};
703             }
704              
705             return 0;
706             }
707              
708             =begin WSDL
709              
710             _IN lang $string Language
711             _IN code $int Error code
712             _RETURN $string Error string
713              
714             =end WSDL
715              
716             =cut
717              
718             ##@method string msg(int code)
719             # calls Portal/_i18n.pm to display message in the client's language.
720             #@param $code message code
721             #@return message
722             sub msg {
723             my $self = shift;
724             my $code = shift;
725             return &Lemonldap::NG::Portal::_i18n::msg( $code, $self->{lang} );
726             }
727              
728             ##@method string error(int code)
729             # calls Portal/_i18n.pm to display error in the client's language.
730             #@param $code optional error code
731             #@return error message
732             sub error {
733             my $self = shift;
734             my $code = shift || $self->{error};
735             if ( my $lang = shift ) { # only for SOAP error requests
736             $self->{lang} = $self->extract_lang($lang);
737             }
738             my $msg;
739              
740             # Check for customized message
741             foreach ( @{ $self->{lang} } ) {
742             if ( $self->{ "error_" . $_ . "_" . $code } ) {
743             $msg = $self->{ "error_" . $_ . "_" . $code };
744             last;
745             }
746             }
747             $msg ||= $self->{ "error_" . $code };
748              
749             # Use customized message or built-in message
750             if ( defined $msg ) {
751              
752             # Manage UTF-8
753             utf8::decode($msg);
754              
755             $self->lmLog( "Use customized message $msg for error $code", 'debug' );
756             }
757             else {
758             $msg = &Lemonldap::NG::Portal::_i18n::error( $code, $self->{lang} );
759             }
760              
761             # Return message
762             # Manage SOAP
763             return $msg;
764             }
765              
766             ##@method string error_type(int code)
767             # error_type tells if error is positive, warning or negative
768             # @param $code Lemonldap::NG error code
769             # @return "positive", "warning" or "negative"
770             sub error_type {
771             my $self = shift;
772             my $code = shift || $self->{error};
773              
774             # Positive errors
775             return "positive"
776             if (
777             scalar(
778             grep { /^$code$/ } (
779             PE_REDIRECT, PE_DONE,
780             PE_OK, PE_PASSWORD_OK,
781             PE_MAILOK, PE_LOGOUT_OK,
782             PE_MAILFIRSTACCESS, PE_PASSWORDFIRSTACCESS,
783             PE_MAILCONFIRMOK, PE_REGISTERFIRSTACCESS,
784             )
785             )
786             );
787              
788             # Warning errors
789             return "warning"
790             if (
791             scalar(
792             grep { /^$code$/ } (
793             PE_INFO, PE_SESSIONEXPIRED,
794             PE_FORMEMPTY, PE_FIRSTACCESS,
795             PE_PP_GRACE, PE_PP_EXP_WARNING,
796             PE_NOTIFICATION, PE_BADURL,
797             PE_CONFIRM, PE_MAILFORMEMPTY,
798             PE_MAILCONFIRMATION_ALREADY_SENT, PE_PASSWORDFORMEMPTY,
799             PE_CAPTCHAEMPTY, PE_REGISTERFORMEMPTY,
800             )
801             )
802             );
803              
804             # Negative errors (default)
805             return "negative";
806             }
807              
808             ##@method void header()
809             # Overload CGI::header() to add Lemonldap::NG cookie.
810             sub header {
811             my $self = shift;
812             unshift @_, '-type' unless ($#_);
813             if ( $self->{cookie} ) {
814             $self->SUPER::header( @_, -cookie => $self->{cookie} );
815             }
816             else {
817             $self->SUPER::header(@_);
818             }
819             }
820              
821             ##@method void redirect()
822             # Overload CGI::redirect() to add Lemonldap::NG cookie.
823             sub redirect {
824             my $self = shift;
825             if ( $self->{cookie} ) {
826             $self->SUPER::redirect( @_, -cookie => $self->{cookie} );
827             }
828             else {
829             $self->SUPER::redirect(@_);
830             }
831             }
832              
833             ## @method protected hashref getApacheSession(string id, boolean noInfo)
834             # Try to recover the session corresponding to id and return session datas.
835             # If $id is set to undef, return a new session.
836             # @param id session reference
837             # @param noInfo do not set Apache REMOTE_USER
838             # return Lemonldap::NG::Common::Session object
839             sub getApacheSession {
840             my ( $self, $id, $noInfo ) = @_;
841              
842             my $apacheSession = Lemonldap::NG::Common::Session->new(
843             {
844             storageModule => $self->{globalStorage},
845             storageModuleOptions => $self->{globalStorageOptions},
846             cacheModule => $self->{localSessionStorage},
847             cacheModuleOptions => $self->{localSessionStorageOptions},
848             id => $id,
849             kind => "SSO",
850             }
851             );
852              
853             if ( $apacheSession->error ) {
854             $self->lmLog( $apacheSession->error, 'debug' );
855             return;
856             }
857              
858             unless ($noInfo) {
859             $self->setApacheUser( $apacheSession->data->{ $self->{whatToTrace} } )
860             if ($id);
861             $self->{id} = $apacheSession->id;
862             }
863             return $apacheSession;
864             }
865              
866             ## @method protected hashref getPersistentSession(string id)
867             # Try to recover the persitent session corresponding to id and return session datas.
868             # If $id is set to undef, return a new session.
869             # @param id session reference
870             # return Lemonldap::NG::Common::Session object
871             sub getPersistentSession {
872             my ( $self, $id ) = splice @_;
873              
874             my $persistentSession = Lemonldap::NG::Common::Session->new(
875             {
876             storageModule => $self->{persistentStorage},
877             storageModuleOptions => $self->{persistentStorageOptions},
878             cacheModule => $self->{localSessionStorage},
879             cacheModuleOptions => $self->{localSessionStorageOptions},
880             id => $id,
881             force => 1,
882             kind => "Persistent",
883             }
884             );
885              
886             if ( $persistentSession->error ) {
887             $self->lmLog( $persistentSession->error, 'debug' );
888             }
889              
890             return $persistentSession;
891             }
892              
893             ## @method protected string _md5hash(string s)
894             # Return md5(s)
895             # @param $s String to hash
896             # @return hashed value
897             sub _md5hash {
898             my ( $self, $s ) = splice @_;
899             return substr( Digest::MD5::md5_hex($s), 0, 32 );
900             }
901              
902             ## @method void updatePersistentSession(hashRef infos, string uid, string id)
903             # Update persistent session.
904             # Call updateSession() and store %$infos in a persistent session.
905             # Note that if the session does not exists, it will be created.
906             # @param infos hash reference of information to update
907             # @param uid optional Unhashed persistent session ID
908             # @param id optional SSO session ID
909             # @return nothing
910             sub updatePersistentSession {
911             my ( $self, $infos, $uid, $id ) = splice @_;
912              
913             # Return if no infos to update
914             return () unless ( ref $infos eq 'HASH' and %$infos );
915              
916             # Update current session
917             $self->updateSession( $infos, $id );
918              
919             $uid ||= $self->{sessionInfo}->{ $self->{whatToTrace} };
920             return () unless ($uid);
921              
922             my $persistentSession =
923             $self->getPersistentSession( $self->_md5hash($uid) );
924              
925             $persistentSession->update($infos);
926              
927             if ( $persistentSession->error ) {
928             $self->lmLog(
929             "Cannot update persistent session " . $self->_md5hash($uid),
930             'error' );
931             $self->lmLog( $persistentSession->error, 'error' );
932             }
933              
934             }
935              
936             ## @method void updateSession(hashRef infos, string id)
937             # Update session stored.
938             # If no id is given, try to get it from cookie.
939             # If the session is available, update datas with $info.
940             # Note that outdated session data may remain some time on
941             # server local cache, if there are several LL::NG servers.
942             # @param infos hash reference of information to update
943             # @param id Session ID
944             # @return nothing
945             sub updateSession {
946             my ( $self, $infos, $id ) = splice @_;
947              
948             # Return if no infos to update
949             return () unless ( ref $infos eq 'HASH' and %$infos );
950              
951             # Recover session ID unless given
952             $id ||= $self->{id};
953             unless ($id) {
954             my %cookies = fetch CGI::Cookie;
955             $id ||= $cookies{ $self->{cookieName} }->value
956             if ( defined $cookies{ $self->{cookieName} } );
957             }
958              
959             if ($id) {
960              
961             # Update sessionInfo data
962             ## sessionInfo updated if $id defined : quite strange !!
963             ## See http://jira.ow2.org/browse/LEMONLDAP-430
964             foreach ( keys %$infos ) {
965             $self->lmLog( "Update sessionInfo $_ with " . $infos->{$_},
966             'debug' );
967             $self->{sessionInfo}->{$_} = $infos->{$_};
968             }
969              
970             # Update session in global storage
971             if ( my $apacheSession = $self->getApacheSession( $id, 1 ) ) {
972              
973             # Store updateTime
974             $infos->{updateTime} = strftime( "%Y%m%d%H%M%S", localtime() );
975              
976             # Store/update session values
977             $apacheSession->update($infos);
978              
979             if ( $apacheSession->error ) {
980             $self->lmLog( "Cannot update session $id", 'error' );
981             $self->lmLog( $apacheSession->error, 'error' );
982             }
983             }
984             }
985             }
986              
987             ## @method void addSessionValue(string key, string value, string id)
988             # Add a value into session key if not already present
989             # @param key Session key
990             # @param value Value to add
991             # @param id optional Session identifier
992             sub addSessionValue {
993             my ( $self, $key, $value, $id ) = splice @_;
994              
995             # Mandatory parameters
996             return () unless defined $key;
997             return () unless defined $value;
998              
999             # Get current key value
1000             my $old_value = $self->{sessionInfo}->{$key};
1001              
1002             # Split old values
1003             if ( defined $old_value ) {
1004             my @old_values = split /\Q$self->{multiValuesSeparator}\E/, $old_value;
1005              
1006             # Do nothing if value already exists
1007             foreach (@old_values) {
1008             return () if ( $_ eq $value );
1009             }
1010              
1011             # Add separator
1012             $old_value .= $self->{multiValuesSeparator};
1013             }
1014             else {
1015             $old_value = "";
1016             }
1017              
1018             # Store new value
1019             my $new_value = $old_value . $value;
1020             $self->updateSession( { $key => $new_value }, $id );
1021              
1022             # Return
1023             return ();
1024             }
1025              
1026             ## @method string getFirstValue(string value)
1027             # Get the first value of a multivaluated session value
1028             # @param value the complete value
1029             # @return first value
1030             sub getFirstValue {
1031             my ( $self, $value ) = splice @_;
1032              
1033             my @values = split /\Q$self->{multiValuesSeparator}\E/, $value;
1034              
1035             return $values[0];
1036             }
1037              
1038             ##@method protected int _subProcess(array @subs)
1039             # Execute methods until an error is returned.
1040             # If $self->{$sub} exists, launch it, else launch $self->$sub
1041             #@param @subs array list of subroutines
1042             #@return Lemonldap::NG::Portal error
1043             sub _subProcess {
1044             my $self = shift;
1045             my @subs = @_;
1046             my $err = undef;
1047              
1048             foreach my $sub (@subs) {
1049             last if ( $err = $self->_sub($sub) );
1050             }
1051             return $err;
1052             }
1053              
1054             ##@method protected void updateStatus()
1055             # Inform status mechanism module.
1056             # If an handler is launched on the same server with "status=>1", inform the
1057             # status module with the result (portal error).
1058             sub updateStatus {
1059             my $self = shift;
1060             print $Lemonldap::NG::Handler::Simple::statusPipe (
1061             $self->{user} ? $self->{user} : $self->ipAddr )
1062             . " => $ENV{SERVER_NAME}$ENV{SCRIPT_NAME} "
1063             . $self->{error} . "\n"
1064             if ($Lemonldap::NG::Handler::Simple::statusPipe);
1065             }
1066              
1067             ##@method protected string notification()
1068             #@return Notification stored by checkNotification()
1069             sub notification {
1070             my $self = shift;
1071             return $self->{_notification};
1072             }
1073              
1074             ##@method protected string get_url()
1075             # Return url parameter
1076             # @return url parameter if good, nothing else.
1077             sub get_url {
1078             my $self = shift;
1079             return $self->{_url};
1080             }
1081              
1082             ##@method protected string get_user()
1083             # Return user parameter
1084             # @return user parameter if good, nothing else.
1085             sub get_user {
1086             my $self = shift;
1087              
1088             return undef unless $self->{user};
1089             unless ( $self->{user} =~ /$self->{userControl}/o ) {
1090             $self->lmLog(
1091             "Value "
1092             . $self->{user}
1093             . " does not match userControl regexp: "
1094             . $self->{userControl},
1095             'warn'
1096             );
1097             return undef;
1098             }
1099              
1100             return $self->{user};
1101             }
1102              
1103             ## @method string get_module(string type)
1104             # Return current used module
1105             # @param type auth/user/password/issuer
1106             # @return module name
1107             sub get_module {
1108             my ( $self, $type ) = splice @_;
1109              
1110             if ( $type =~ /auth/i ) {
1111             if ( defined $self->{_multi}->{stack}->[0] ) {
1112             return $self->{_multi}->{stack}->[0]->[0]->{n};
1113             }
1114             if ( defined $self->{_choice}->{modules} ) {
1115             return $self->{_choice}->{modules}->[0]->{n};
1116             }
1117             else {
1118             return $self->{authentication};
1119             }
1120             }
1121              
1122             if ( $type =~ /user/i ) {
1123             if ( defined $self->{_multi}->{stack}->[1] ) {
1124             return $self->{_multi}->{stack}->[1]->[0]->{n};
1125             }
1126             if ( defined $self->{_choice}->{modules} ) {
1127             return $self->{_choice}->{modules}->[1]->{n};
1128             }
1129             else {
1130             return $self->{userDB};
1131             }
1132             }
1133              
1134             if ( $type =~ /password/i ) {
1135             if ( defined $self->{_choice}->{modules} ) {
1136             return $self->{_choice}->{modules}->[2]->{n};
1137             }
1138             else {
1139             return $self->{passwordDB};
1140             }
1141             }
1142              
1143             if ( $type =~ /issuer/i ) {
1144             return $self->{_activeIssuerDB};
1145             }
1146              
1147             return;
1148             }
1149              
1150             ##@method private Safe safe()
1151             # Provide the security jail.
1152             #@return Safe object
1153             sub safe {
1154             my $self = shift;
1155              
1156             # Test if safe already exists
1157             if ($safe) {
1158              
1159             # Refresh the portal object inside it
1160             $safe->{p} = $self;
1161              
1162             # Refresh environment variables
1163             $safe->share_from( 'main', ['%ENV'] );
1164              
1165             return $safe;
1166             }
1167              
1168             # Else create it
1169             $safe = Lemonldap::NG::Common::Safe->new($self);
1170              
1171             # Get custom functions
1172             my @t =
1173             $self->{customFunctions}
1174             ? split( /\s+/, $self->{customFunctions} )
1175             : ();
1176             foreach (@t) {
1177             my $sub = $_;
1178             unless (/::/) {
1179             $sub = ref($self) . "::$_";
1180             }
1181             else {
1182             s/^.*:://;
1183             }
1184             next if ( $self->can($_) );
1185             eval "sub $_ {
1186             return $sub( '$self->{portal}', \@_ );
1187             }";
1188             $self->lmLog( $@, 'error' ) if ($@);
1189             }
1190              
1191             # Share %ENV
1192             $safe->share_from( 'main', ['%ENV'] );
1193              
1194             # Share Safelib
1195             $safe->share_from( 'Lemonldap::NG::Common::Safelib',
1196             $Lemonldap::NG::Common::Safelib::functions );
1197              
1198             # Share custom functions and &encode_base64
1199             $safe->share( '&encode_base64', @t );
1200              
1201             return $safe;
1202             }
1203              
1204             ##@method private boolean _deleteSession(Lemonldap::NG::Common::Session session, boolean preserveCookie)
1205             # Delete an existing session. If "securedCookie" is set to 2, the http session
1206             # will also be removed.
1207             # @param h tied Apache::Session object
1208             # @param preserveCookie do not delete cookie
1209             # @return True if session has been deleted
1210             sub _deleteSession {
1211             my ( $self, $session, $preserveCookie ) = @_;
1212              
1213             # Invalidate http cookie and session, if set
1214             if ( $self->{securedCookie} >= 2 ) {
1215              
1216             # Try to find a linked http session (securedCookie == 2)
1217             if ( my $id2 = $session->data->{_httpSession} ) {
1218             if ( my $session2 = $self->getApacheSession( $id2, 1 ) ) {
1219             $session2->remove;
1220             if ( $session2->error ) {
1221             $self->lmLog( "Unable to remove linked session $id2",
1222             'debug' );
1223             $self->lmLog( $session2->error, 'debug' );
1224             }
1225             }
1226             }
1227              
1228             # Create an obsolete cookie to remove it
1229             push @{ $self->{cookie} },
1230             $self->cookie(
1231             -name => $self->{cookieName} . 'http',
1232             -value => 0,
1233             -domain => $self->{domain},
1234             -path => "/",
1235             -secure => 0,
1236             -expires => '-1d',
1237             @_,
1238             ) unless ($preserveCookie);
1239             }
1240              
1241             $session->remove;
1242              
1243             # Create an obsolete cookie to remove it
1244             push @{ $self->{cookie} },
1245             $self->cookie(
1246             -name => $self->{cookieName},
1247             -value => 0,
1248             -domain => $self->{domain},
1249             -path => "/",
1250             -secure => 0,
1251             -expires => '-1d',
1252             @_,
1253             ) unless ($preserveCookie);
1254              
1255             # Log
1256             my $user = $self->{sessionInfo}->{ $self->{whatToTrace} };
1257             $self->_sub( 'userNotice', "User $user has been disconnected" )
1258             if $user;
1259              
1260             return $session->error ? 0 : 1;
1261             }
1262              
1263             ##@method private void _dump(void* variable)
1264             # Dump variable in debug mode
1265             # @param $variable
1266             # @return void
1267             sub _dump {
1268             my $self = shift;
1269             my $variable = shift;
1270              
1271             require Data::Dumper;
1272             $self->lmLog( "Dump: " . Data::Dumper::Dumper($variable), 'debug' );
1273              
1274             return;
1275             }
1276              
1277             ##@method protected string info(string t)
1278             # Get or set info to display to the user.
1279             # @param $t optional text to store
1280             # @return HTML text to display
1281             sub info {
1282             my ( $self, $t ) = @_;
1283             $self->{_info} .= $t if ( defined $t );
1284             return $self->{_info};
1285             }
1286              
1287             ##@method protected string loginInfo(string t)
1288             # Get or set info to display to the user on login screen
1289             # @param $t optional text to store
1290             # @return HTML text to display
1291             sub loginInfo {
1292             my ( $self, $t ) = @_;
1293             $self->{_loginInfo} .= $t if ( defined $t );
1294             return $self->{_loginInfo};
1295             }
1296              
1297             ##@method public void printImage(string file, string type)
1298             # Print image to STDOUT
1299             # @param $file The path to the file to print
1300             # @param $type The content-type to use (ie: image/png)
1301             # @return void
1302             sub printImage {
1303             my ( $self, $file, $type ) = @_;
1304             binmode STDOUT;
1305             unless ( open( IMAGE, '<', $file ) ) {
1306             $self->lmLog( "Could not display image '$file'", 'error' );
1307             return;
1308             }
1309             print $self->header(
1310             $type . '; charset=utf-8; content-length=' . ( stat($file) )[10] );
1311             my $buffer = "";
1312             while ( read( IMAGE, $buffer, 4096 ) ) {
1313             print $buffer;
1314             }
1315             close(IMAGE);
1316             }
1317              
1318             sub stamp {
1319             my $self = shift;
1320             return $self->{cipher} ? $self->{cipher}->encrypt( time() ) : 1;
1321             }
1322              
1323             ## @method string convertSec(int sec)
1324             # Convert seconds to hours, minutes, seconds
1325             # @param $sec number of seconds
1326             # @return a formated time
1327             sub convertSec {
1328             my ( $self, $sec ) = splice @_;
1329             my ( $day, $hrs, $min ) = ( 0, 0, 0 );
1330              
1331             # Calculate the minutes
1332             if ( $sec > 60 ) {
1333             $min = $sec / 60, $sec %= 60;
1334             $min = int($min);
1335             }
1336              
1337             # Calculate the hours
1338             if ( $min > 60 ) {
1339             $hrs = $min / 60, $min %= 60;
1340             $hrs = int($hrs);
1341             }
1342              
1343             # Calculate the days
1344             if ( $hrs > 24 ) {
1345             $day = $hrs / 24, $hrs %= 24;
1346             $day = int($day);
1347             }
1348              
1349             # Return the date
1350             return ( $day, $hrs, $min, $sec );
1351             }
1352              
1353             ## @method string getSkin()
1354             # Return skin name
1355             # @return skin name
1356             sub getSkin {
1357             my ($self) = splice @_;
1358              
1359             my $skin = $self->{portalSkin};
1360              
1361             # Fill sessionInfo to eval rule if empty (unauthenticated user)
1362             $self->{sessionInfo}->{_url} ||= $self->{urldc};
1363             $self->{sessionInfo}->{ipAddr} ||= $self->ipAddr;
1364              
1365             # Load specific skin from skinRules
1366             if ( $self->{portalSkinRules} ) {
1367             foreach my $skinRule ( sort keys %{ $self->{portalSkinRules} } ) {
1368             if ( $self->safe->reval($skinRule) ) {
1369             $skin = $self->{portalSkinRules}->{$skinRule};
1370             $self->lmLog( "Skin $skin selected from skin rule", 'debug' );
1371             }
1372             }
1373             }
1374              
1375             return $skin;
1376             }
1377              
1378             ###############################################################
1379             # MAIN subroutine: call all steps until one returns something #
1380             # different than PE_OK #
1381             ###############################################################
1382              
1383             ##@method boolean process()
1384             # Main method calling functions issued from:
1385             # - itself:
1386             # - controlUrlOrigin
1387             # - checkNotifBack
1388             # - controlExistingSession
1389             # - setMacros
1390             # - setLocalGroups
1391             # - setPersistentSessionInfo
1392             # - removeOther
1393             # - grantSession
1394             # - store
1395             # - buildCookie
1396             # - checkNotification
1397             # - autoRedirect
1398             # - updateStatus
1399             # - authentication module:
1400             # - authInit
1401             # - extractFormInfo
1402             # - setAuthSessionInfo
1403             # - authenticate
1404             # - authFinish
1405             # - userDB module:
1406             # - userDBInit
1407             # - getUser
1408             # - setSessionInfo
1409             # - setGroups
1410             # - passwordDB module:
1411             # - passwordDBInit
1412             # - modifyPassword
1413             # - issuerDB module:
1414             # - issuerDBInit
1415             # - issuerForUnAuthUser
1416             # - issuerForAuthUser
1417             # - MailReset:
1418             # - sendPasswordMail
1419             #
1420             #@return 1 if all is OK, 0 if session isn't created or a notification has to be done
1421             sub process {
1422             my ($self) = @_;
1423             $self->{error} = PE_OK;
1424             $self->{error} = $self->_subProcess(
1425             qw(controlUrlOrigin checkNotifBack controlExistingSession
1426             issuerDBInit authInit issuerForUnAuthUser extractFormInfo
1427             userDBInit getUser setAuthSessionInfo passwordDBInit
1428             modifyPassword setSessionInfo setMacros setGroups
1429             setPersistentSessionInfo setLocalGroups sendPasswordMail
1430             authenticate authFinish userDBFinish passwordDBFinish
1431             grantSession removeOther store buildCookie checkNotification
1432             issuerForAuthUser autoRedirect)
1433             );
1434             $self->updateStatus;
1435             return ( ( $self->{error} > 0 ) ? 0 : 1 );
1436             }
1437              
1438             ##@apmethod int controlUrlOrigin()
1439             # If the user was redirected here, loads 'url' parameter.
1440             # Check also confirm parameter.
1441             #@return Lemonldap::NG::Portal constant
1442             sub controlUrlOrigin {
1443             my $self = shift;
1444             if ( my $c = $self->param('confirm') ) {
1445              
1446             # Replace confirm stamp by 1 or -1
1447             $c =~ s/^(-?)(.*)$/${1}1/;
1448              
1449             # Decrypt confirm stamp if cipher available
1450             # and confirm not already decrypted
1451             if ( $self->{cipher} and $2 ne "1" ) {
1452             my $time = time() - $self->{cipher}->decrypt($2);
1453             if ( $time < 600 ) {
1454             $self->lmLog( "Confirm parameter accepted $c", 'debug' );
1455             $self->param( 'confirm', $c );
1456             }
1457             else {
1458             $self->lmLog( 'Confirmation to old, refused', 'notice' );
1459             $self->param( 'confirm', 0 );
1460             }
1461             }
1462             }
1463             $self->{_url} ||= '';
1464             if ( my $url = $self->param('url') ) {
1465              
1466             # REJECT NON BASE64 URL except for CAS IssuerDB
1467             if ( $self->get_module('issuer') ne "CAS" ) {
1468             if ( $url =~ m#[^A-Za-z0-9\+/=]# ) {
1469             $self->lmLog(
1470             "Value must be in BASE64 (param: url | value: $url)",
1471             "warn" );
1472             return PE_BADURL;
1473             }
1474              
1475             $self->{urldc} = decode_base64($url);
1476             $self->{urldc} =~ s/[\r\n]//sg;
1477             }
1478             else { $self->{urldc} = $url; }
1479              
1480             # For logout request, test if Referer comes from an authorizated site
1481             my $tmp =
1482             ( $self->param('logout') ? $ENV{HTTP_REFERER} : $self->{urldc} );
1483              
1484             # XSS attack
1485             if (
1486             $self->checkXSSAttack(
1487             $self->param('logout') ? 'HTTP Referer' : 'urldc',
1488             $self->{urldc}
1489             )
1490             )
1491             {
1492             delete $self->{urldc};
1493             return PE_BADURL;
1494             }
1495              
1496             # Non protected hosts
1497             if ( $tmp and !$self->isTrustedUrl($tmp) ) {
1498             $self->lmLog(
1499             "URL contains a non protected host (param: "
1500             . ( $self->param('logout') ? 'HTTP Referer' : 'urldc' )
1501             . " | value: $tmp)",
1502             "warn"
1503             );
1504             delete $self->{urldc};
1505             return PE_BADURL;
1506             }
1507              
1508             $self->{_url} = $url;
1509             }
1510              
1511             PE_OK;
1512             }
1513              
1514             ##@apmethod int checkNotifBack()
1515             # Checks if a message has been notified to the connected user.
1516             # Call Lemonldap::NG::Common::Notification::checkNotification()
1517             #@return Lemonldap::NG::Portal error code
1518             sub checkNotifBack {
1519             my $self = shift;
1520             if ( $self->{notification} and grep( /^reference/, $self->param() ) ) {
1521             $self->lmLog( "User was on a notification step", 'debug' );
1522             unless ( $self->{notifObject}->checkNotification($self) ) {
1523             $self->lmLog(
1524             "All notifications have not been accepted, display them again",
1525             'debug'
1526             );
1527             $self->{_notification} =
1528             $self->{notifObject}->getNotification($self);
1529             return PE_NOTIFICATION;
1530             }
1531             else {
1532             $self->lmLog(
1533             "All notifications have been accepted, follow the authentication process",
1534             'debug'
1535             );
1536             $self->{error} = $self->_subProcess(
1537             qw(issuerDBInit authInit issuerForAuthUser authFinish autoRedirect)
1538             );
1539             return $self->{error} || PE_DONE;
1540             }
1541             }
1542             PE_OK;
1543             }
1544              
1545             ##@apmethod int controlExistingSession(string id)
1546             # Control existing sessions.
1547             # To overload to control what to do with existing sessions.
1548             # what to do with existing sessions ?
1549             # - nothing: user is authenticated and process returns true (default)
1550             # - delete and create a new session (not implemented)
1551             # - re-authentication (set portalForceAuthn to 1)
1552             #@param $id optional value of the session-id else cookies are examinated.
1553             #@return Lemonldap::NG::Portal constant
1554             sub controlExistingSession {
1555             my ( $self, $id ) = @_;
1556             my %cookies;
1557             %cookies = fetch CGI::Cookie unless ($id);
1558              
1559             # Special request "display captcha"
1560             if ( $self->param("displayCaptcha") ) {
1561              
1562             my $captcha = Lemonldap::NG::Common::Captcha->new(
1563             {
1564             storageModule => $self->{captchaStorage},
1565             storageModuleOptions => $self->{captchaStorageOptions},
1566             md5 => $self->param("displayCaptcha"),
1567             size => $self->{captcha_size},
1568             }
1569             );
1570              
1571             if ( $captcha && $captcha->image ) {
1572             binmode STDOUT;
1573             print $self->header( 'image/png'
1574             . '; charset=utf-8; content-length='
1575             . length( $captcha->image ) );
1576             print $captcha->image;
1577             }
1578             $self->quit();
1579             }
1580              
1581             # Test if Lemonldap::NG cookie is available
1582             if (
1583             $id
1584             or ( $cookies{ $self->{cookieName} }
1585             and $id = $cookies{ $self->{cookieName} }->value )
1586             )
1587             {
1588             my $apacheSession = $self->getApacheSession($id);
1589              
1590             if ($apacheSession) {
1591             %{ $self->{sessionInfo} } = %{ $apacheSession->data };
1592              
1593             # Logout if required
1594             if ( $self->param('logout') ) {
1595              
1596             # Delete session
1597             unless ( $self->_deleteSession($apacheSession) ) {
1598             $self->lmLog( "Unable to delete session $id", 'error' );
1599             $self->lmLog( $apacheSession->error, 'error' );
1600             return PE_ERROR;
1601             }
1602             else {
1603             $self->lmLog( "Session $id deleted from global storage",
1604             'debug' );
1605             }
1606              
1607             # Call issuerDB logout on each used issuerDBmodule
1608             my $issuerDBList = $self->{sessionInfo}->{_issuerDB};
1609             if ( defined $issuerDBList ) {
1610             foreach my $issuerDBtype (
1611             split(
1612             /\Q$self->{multiValuesSeparator}\E/,
1613             $issuerDBList
1614             )
1615             )
1616             {
1617             my $module_name =
1618             'Lemonldap::NG::Portal::IssuerDB' . $issuerDBtype;
1619              
1620             $self->lmLog(
1621             "Process logout for issuerDB module $issuerDBtype",
1622             'debug'
1623             );
1624              
1625             # Load current IssuerDB module
1626             unless ( $self->loadModule($module_name) ) {
1627             $self->lmLog( "Unable to load $module_name",
1628             'error' );
1629             next;
1630             }
1631              
1632             $self->{error} = $self->_subProcess(
1633             $module_name . "::issuerDBInit",
1634             $module_name . '::issuerLogout'
1635             );
1636              
1637             }
1638             }
1639              
1640             # Call logout for the module used to authenticate
1641             $self->lmLog(
1642             "Process logout for authentication module "
1643             . $self->{sessionInfo}->{_auth},
1644             'debug'
1645             );
1646              
1647             if (
1648             $self->{sessionInfo}->{'_auth'} ne $self->get_module('auth')
1649             )
1650             {
1651             my $module_name = 'Lemonldap::NG::Portal::Auth'
1652             . $self->{sessionInfo}->{_auth};
1653              
1654             unless ( $self->loadModule($module_name) ) {
1655             $self->lmLog( "Unable to load $module_name", 'error' );
1656             }
1657             else {
1658             eval {
1659             $self->{error} = $self->_subProcess(
1660             $module_name . "::authInit",
1661             $module_name . "::authLogout"
1662             );
1663             };
1664             }
1665             }
1666             else {
1667             eval {
1668             $self->{error} =
1669             $self->_subProcess( 'authInit', 'authLogout' );
1670             };
1671             }
1672             if ($@) {
1673             $self->lmLog(
1674             "Error when calling authentication logout: $@",
1675             'debug' );
1676             }
1677             return $self->{error} if $self->{error} > 0;
1678              
1679             # Collect logout services and build hidden iFrames
1680             if ( %{ $self->{logoutServices} } ) {
1681              
1682             $self->lmLog(
1683             "Create iFrames to forward logout to services",
1684             'debug' );
1685              
1686             $self->info( "<h3>" . $self->msg(PM_LOGOUT) . "</h3>" );
1687              
1688             foreach ( keys %{ $self->{logoutServices} } ) {
1689             my $logoutServiceName = $_;
1690             my $logoutServiceUrl =
1691             $self->{logoutServices}->{$logoutServiceName};
1692              
1693             $self->lmLog(
1694             "Find logout service $logoutServiceName ($logoutServiceUrl)",
1695             'debug'
1696             );
1697              
1698             my $iframe =
1699             "<iframe src=\"$logoutServiceUrl\""
1700             . " alt=\"$logoutServiceName\" marginwidth=\"0\""
1701             . " marginheight=\"0\" scrolling=\"no\" style=\"border: none;display: hidden;margin: 0\""
1702             . " width=\"0\" height=\"0\" frameborder=\"0\">"
1703             . "</iframe>";
1704              
1705             $self->info($iframe);
1706             }
1707              
1708             # Redirect on logout page if no other target defined
1709             if ( !$self->{urldc} and !$self->{postUrl} ) {
1710             $self->{urldc} = $ENV{SCRIPT_NAME} . "?logout=1";
1711             }
1712             }
1713              
1714             # Redirect or Post if asked by authLogout
1715             return $self->_subProcess(qw(autoRedirect))
1716             if ( $self->{urldc}
1717             and $self->{urldc} ne $self->{portal} );
1718              
1719             return $self->_subProcess(qw(autoPost))
1720             if ( $self->{postUrl} );
1721              
1722             # Display logout message
1723             return PE_LOGOUT_OK;
1724             }
1725              
1726             # If the user wants to purge other sessions
1727             elsif ( $self->param('removeOther') ) {
1728             $self->{notifyDeleted} = 1;
1729             $self->{singleSession} = 1;
1730             $self->_sub( 'removeOther', $id );
1731             }
1732              
1733             # Special ajax request "ping" to check if session is available
1734             if ( $self->param('ping') ) {
1735             print $self->header( -type => 'application/json' )
1736             . '{"auth":true}';
1737             $self->quit();
1738             }
1739              
1740             # Special ajax request "storeAppsListOrder"
1741             if ( $self->param('storeAppsListOrder') ) {
1742             my $order = $self->param('storeAppsListOrder');
1743             $self->lmLog( "Get new apps list order: $order", 'debug' );
1744             $self->updatePersistentSession( { appsListOrder => $order } );
1745             $self->quit();
1746             }
1747              
1748             $self->{id} = $id;
1749              
1750             # A session has been found => call existingSession
1751             my $r = $self->_sub( 'existingSession', $id, $self->{sessionInfo} );
1752             if ( $r == PE_DONE ) {
1753             $self->{error} = $self->_subProcess(
1754             qw(checkNotification issuerDBInit authInit issuerForAuthUser authFinish autoRedirect)
1755             );
1756             return $self->{error} || PE_DONE;
1757             }
1758             else {
1759             return $r;
1760             }
1761             }
1762             }
1763              
1764             # Special ajax request "ping" to check if session is available
1765             if ( $self->param('ping') ) {
1766             print $self->header( -type => 'application/json' ) . '{"auth":false}';
1767             $self->quit();
1768             }
1769              
1770             # Display logout success if logout asked
1771             # and we do not have valid session
1772             return PE_LOGOUT_OK if $self->param('logout');
1773              
1774             # Else continue authentication process
1775             PE_OK;
1776             }
1777              
1778             ## @method int existingSession()
1779             # Launched by controlExistingSession() to know what to do with existing
1780             # sessions.
1781             # Can return:
1782             # - PE_DONE: session is unchanged and process() return true
1783             # - PE_OK: process() return false to display the form
1784             #@return Lemonldap::NG::Portal constant
1785             sub existingSession {
1786             my $self = shift;
1787             my $forceAuthn;
1788              
1789             # Check portalForceAuthn parameter
1790             # and authForce method
1791             eval { $forceAuthn = $self->_sub('authForce'); };
1792             if ($@) {
1793             $self->lmLog( "Error when calling authForce: $@", 'debug' );
1794             }
1795              
1796             $forceAuthn = 1 if ( $self->{portalForceAuthn} );
1797              
1798             if ($forceAuthn) {
1799             my $referer = $self->referer();
1800             my $id = $self->{id};
1801              
1802             # Do not force authentication when password is modified
1803             return PE_DONE if $self->param('newpassword');
1804              
1805             # Do not force authentication if last successful authentication is recent
1806             my $last_authn_utime = $self->{sessionInfo}->{_lastAuthnUTime} || 0;
1807             if ( time() - $last_authn_utime < $self->{portalForceAuthnInterval} ) {
1808             $self->lmLog(
1809             "Authentication is recent, so do not force authentication for session $id",
1810             'debug'
1811             );
1812             return PE_DONE;
1813             }
1814              
1815             # If coming from the portal follow the normal process to update the session
1816             if ( $referer ? ( $referer =~ m#$self->{portal}#i ) : 0 ) {
1817             $self->lmLog( "Portal referer detected for session $id", 'debug' );
1818              
1819             # Set flag to update session timestamp
1820             $self->{updateSession} = 1;
1821              
1822             # Process
1823             $self->{error} = $self->_subProcess(
1824             qw(issuerDBInit authInit issuerForUnAuthUser extractFormInfo
1825             userDBInit getUser setAuthSessionInfo setSessionInfo
1826             setMacros setGroups setPersistentSessionInfo
1827             setLocalGroups authenticate authFinish userDBFinish store)
1828             );
1829             return $self->{error} || PE_DONE;
1830             }
1831             else {
1832             $self->lmLog( "Force reauthentication for session $id", 'debug' );
1833             return PE_OK;
1834             }
1835             }
1836              
1837             # Else return PE_DONE
1838             PE_DONE;
1839             }
1840              
1841             # issuerDBInit(): must be implemented in IssuerDB* module
1842              
1843             # authInit(): must be implemented in Auth* module
1844              
1845             # issuerForUnAuthUser(): must be implemented in IssuerDB* module
1846              
1847             ##@apmethod int extractFormInfo()
1848             # Extract data common to all authentication modules,
1849             # and call extractFormInfo() in Auth* module
1850             # Auth*::extractFormInfo set $self->{user} and in some cases
1851             # authenticate user (done in authenticate() else)
1852             #@return Lemonldap::NG::Portal constant
1853             sub extractFormInfo {
1854             my $self = shift;
1855             return PE_OK if $self->{skipExtractFormInfo};
1856             $self->{checkLogins} = $self->param('checkLogins');
1857             return $self->SUPER::extractFormInfo;
1858             }
1859              
1860             # getUser(): must be implemented in UserDB* module
1861              
1862             ## @apmethod int setAuthSessionInfo()
1863             # Set _auth
1864             # call setAuthSessionInfo in Auth* module
1865             #@return Lemonldap::NG::Portal constant
1866             sub setAuthSessionInfo {
1867             my $self = shift;
1868              
1869             # Get the current authentication module
1870             $self->{sessionInfo}->{_auth} = $self->get_module("auth");
1871              
1872             return $self->SUPER::setAuthSessionInfo();
1873             }
1874              
1875             ## @apmethod int passwordDBInit()
1876             # Set _passwordDB
1877             # call passwordDBInit in passwordDB* module
1878             # @return Lemonldap::NG::Portal constant
1879             sub passwordDBInit {
1880             my $self = shift;
1881              
1882             # Get the current password module
1883             $self->{sessionInfo}->{_passwordDB} = $self->get_module("password");
1884              
1885             return $self->SUPER::passwordDBInit();
1886             }
1887              
1888             ## @apmethod int modifyPassword()
1889             # Call modifyPassword from PasswordDB* module
1890             # Continue auth process if password change is ok
1891             # @return Lemonldap::NG::Portal constant
1892             sub modifyPassword {
1893             my $self = shift;
1894              
1895             my $res = $self->SUPER::modifyPassword();
1896              
1897             if ( $res == PE_PASSWORD_OK ) {
1898              
1899             # Update password in session if needed
1900             $self->lmLog( "Update password in session for " . $self->{user},
1901             'debug' );
1902              
1903             my $infos;
1904             $infos->{_password} = $self->{newpassword};
1905             $self->updateSession($infos) if ( $self->{storePassword} );
1906              
1907             # Set a flag to ignore password change in Menu
1908             $self->{ignorePasswordChange} = 1;
1909              
1910             # Set a flag to allow sending a mail
1911             $self->{passwordWasChanged} = 1;
1912              
1913             # Continue process if password change is ok
1914             return PE_OK;
1915             }
1916              
1917             return $res;
1918             }
1919              
1920             ##@apmethod int setSessionInfo()
1921             # Set ipAddr, startTime, updateTime, _utime and _userDB
1922             # Call setSessionInfo() in UserDB* module
1923             #@return Lemonldap::NG::Portal constant
1924             sub setSessionInfo {
1925             my $self = shift;
1926              
1927             # Get the current user module
1928             $self->{sessionInfo}->{_userDB} = $self->get_module("user");
1929              
1930             # Store IP address from remote address or X-FORWARDED-FOR header
1931             $self->{sessionInfo}->{ipAddr} = $self->ipAddr;
1932              
1933             # Date and time
1934             if ( $self->{updateSession} ) {
1935             $self->{sessionInfo}->{updateTime} =
1936             strftime( "%Y%m%d%H%M%S", localtime() );
1937             }
1938             else {
1939             $self->{sessionInfo}->{_utime} ||= time();
1940             $self->{sessionInfo}->{startTime} =
1941             strftime( "%Y%m%d%H%M%S", localtime() );
1942             }
1943              
1944             # Get environment variables matching exportedVars
1945             foreach ( keys %{ $self->{exportedVars} } ) {
1946             if ( my $tmp = $ENV{ $self->{exportedVars}->{$_} } ) {
1947             $tmp =~ s/[\r\n]/ /gs;
1948             $self->{sessionInfo}->{$_} = $tmp;
1949             delete $self->{exportedVars}->{$_};
1950             }
1951             }
1952              
1953             # Store URL origin in session
1954             $self->{sessionInfo}->{_url} = $self->{urldc};
1955              
1956             # Call UserDB setSessionInfo
1957             if ( my $res = $self->SUPER::setSessionInfo() ) {
1958             return $res;
1959             }
1960              
1961             PE_OK;
1962             }
1963              
1964             ##@apmethod int setMacros()
1965             # Macro mechanism.
1966             # * store macro results in $self->{sessionInfo}
1967             #@return Lemonldap::NG::Portal constant
1968             sub setMacros {
1969             my $self = shift;
1970             foreach ( sort keys %{ $self->{macros} } ) {
1971             $self->{sessionInfo}->{$_} =
1972             $self->safe->reval( $self->{macros}->{$_} );
1973             }
1974             PE_OK;
1975             }
1976              
1977             ##@apmethod int setLocalGroups()
1978             # Groups mechanism.
1979             # * store all groups name that the user match in $self->{sessionInfo}->{groups}
1980             #@return Lemonldap::NG::Portal constant
1981             sub setLocalGroups {
1982             my $self = shift;
1983             foreach ( sort keys %{ $self->{groups} } ) {
1984             $self->{sessionInfo}->{groups} .= $self->{multiValuesSeparator} . $_
1985             if ( $self->safe->reval( $self->{groups}->{$_} ) );
1986             }
1987              
1988             # Clear values separator at the beginning
1989             if ( $self->{sessionInfo}->{groups} ) {
1990             $self->{sessionInfo}->{groups} =~
1991             s/^\Q$self->{multiValuesSeparator}\E//;
1992             }
1993             PE_OK;
1994             }
1995              
1996             # setGroups(): must be implemented in UserDB* module
1997              
1998             ##@apmethod int setPersistentSessionInfo()
1999             # Restore persistent session info
2000             #@return Lemonldap::NG::Portal constant
2001             sub setPersistentSessionInfo {
2002             my $self = shift;
2003              
2004             # Do not restore infos if session already opened
2005             unless ( $self->{id} ) {
2006             my $key = $self->{sessionInfo}->{ $self->{whatToTrace} };
2007              
2008             return PE_OK unless ( $key and length($key) );
2009              
2010             my $persistentSession =
2011             $self->getPersistentSession( $self->_md5hash($key) );
2012              
2013             if ($persistentSession) {
2014             $self->lmLog( "Persistent session found for $key", 'debug' );
2015             foreach my $k ( keys %{ $persistentSession->data } ) {
2016              
2017             # Do not restore some parameters
2018             next if $k =~ /^_session_id$/;
2019             next if $k =~ /^_session_kind$/;
2020             $self->lmLog( "Restore persistent parameter $k", 'debug' );
2021             $self->{sessionInfo}->{$k} = $persistentSession->data->{$k};
2022             }
2023             }
2024             }
2025              
2026             PE_OK;
2027             }
2028              
2029             ## @apmethod sendPasswordMail
2030             # Call sendPasswordMail from MailReset if option is configured
2031             # @return Lemonldap::NG::Portal constant
2032             sub sendPasswordMail {
2033             my $self = shift;
2034              
2035             if ( $self->{mailOnPasswordChange} && $self->{passwordWasChanged} ) {
2036              
2037             $self->lmLog( "Send password by mail requested", 'debug' );
2038              
2039             eval "require Lemonldap::NG::Portal::MailReset";
2040             &Lemonldap::NG::Portal::MailReset::smtpInit($self);
2041             &Lemonldap::NG::Portal::MailReset::sendPasswordMail($self);
2042             }
2043              
2044             # Never stop the process here
2045             return PE_OK;
2046             }
2047              
2048             ##@apmethod int authenticate()
2049             # Call authenticate() in Auth* module, and registerLogin()
2050             # if authentication failed, userNotice() if it succeeded.
2051             #@return Lemonldap::NG::Portal constant
2052             sub authenticate {
2053             my $self = shift;
2054             if ( my $errorCode = $self->SUPER::authenticate() ) {
2055             $self->registerLogin($errorCode);
2056             return $errorCode;
2057             }
2058              
2059             # Log good authentication
2060             my $user = $self->{sessionInfo}->{ $self->{whatToTrace} };
2061             $self->_sub( 'userNotice',
2062             "Good authentication for $user by $self->{sessionInfo}->{_auth}" )
2063             if $user;
2064              
2065             # Set _lastAuthnUTime
2066             $self->{sessionInfo}->{_lastAuthnUTime} = time();
2067              
2068             PE_OK;
2069             }
2070              
2071             ##@method registerLogin
2072             # Store current login in login history
2073             # @param $errorCode Code returned by authenticate()
2074             sub registerLogin {
2075             my ( $self, $errorCode ) = @_;
2076              
2077             if ( $self->{loginHistoryEnabled} ) {
2078             my $history = $self->{sessionInfo}->{loginHistory} ||= {};
2079              
2080             my $type = ( $errorCode ? "failed" : "success" ) . "Login";
2081             $history->{$type} ||= [];
2082             $self->lmLog( "Current login saved into $type", "debug" );
2083              
2084             # Gather current login's parameters
2085             my $login = $self->_sumUpSession( $self->{sessionInfo}, 1 );
2086             $login->{error} = $self->error($errorCode)
2087             if ($errorCode);
2088              
2089             # Add current login into history
2090             unshift @{ $history->{$type} }, $login;
2091              
2092             # Forget oldest logins
2093             splice @{ $history->{$type} }, $self->{ $type . "Number" }
2094             if ( scalar @{ $history->{$type} } > $self->{ $type . "Number" } );
2095              
2096             # Save into persistent session
2097             $self->updatePersistentSession( { loginHistory => $history, } );
2098             }
2099             }
2100              
2101             ##@apmethod int removeOther()
2102             # check singleSession or singleIP parameters, and remove other sessions if needed
2103             #@return Lemonldap::NG::Portal constant
2104             sub removeOther {
2105             my ( $self, $current ) = @_;
2106             $self->{deleted} = [];
2107             $self->{otherSessions} = [];
2108              
2109             my $moduleOptions = $self->{globalStorageOptions} || {};
2110             $moduleOptions->{backend} = $self->{globalStorage};
2111             my $module = "Lemonldap::NG::Common::Apache::Session";
2112              
2113             if ( $self->{singleSession}
2114             or $self->{singleIP}
2115             or $self->{notifyOther} )
2116             {
2117             my $sessions =
2118             $module->searchOn( $moduleOptions, $self->{whatToTrace},
2119             $self->{sessionInfo}->{ $self->{whatToTrace} } );
2120             foreach my $id ( keys %$sessions ) {
2121             next if ( $current and ( $current eq $id ) );
2122             my $session = $self->getApacheSession( $id, 1 ) or next;
2123             if (
2124             $self->{singleSession}
2125             or ( $self->{singleIP}
2126             and $self->{sessionInfo}->{ipAddr} ne
2127             $session->data->{ipAddr} )
2128             )
2129             {
2130             push @{ $self->{deleted} },
2131             $self->_sumUpSession( $session->data );
2132             $self->_deleteSession( $session, 1 );
2133             }
2134             else {
2135             push @{ $self->{otherSessions} },
2136             $self->_sumUpSession( $session->data );
2137             }
2138             }
2139             }
2140             if ( $self->{singleUserByIP} ) {
2141             my $sessions =
2142             $module->searchOn( $moduleOptions, 'ipAddr', $self->ipAddr );
2143             foreach my $id ( keys %$sessions ) {
2144             next if ( $current and $current eq $id );
2145             my $session = $self->getApacheSession( $id, 1 ) or next;
2146             unless ( $self->{sessionInfo}->{ $self->{whatToTrace} } eq
2147             $session->data->{ $self->{whatToTrace} } )
2148             {
2149             push @{ $self->{deleted} },
2150             $self->_sumUpSession( $session->data );
2151             $self->_deleteSession( $session, 1 );
2152             }
2153             }
2154             }
2155             $self->info(
2156             $self->mkSessionArray(
2157             $self->{deleted}, $self->msg(PM_SESSIONS_DELETED), 1
2158             )
2159             ) if ( $self->{notifyDeleted} and @{ $self->{deleted} } );
2160             $self->info(
2161             $self->mkSessionArray( $self->{otherSessions},
2162             $self->msg(PM_OTHER_SESSIONS), 1 )
2163             . $self->_mkRemoveOtherLink()
2164             ) if ( $self->{notifyOther} and @{ $self->{otherSessions} } );
2165              
2166             $self->info(
2167             (
2168             $self->{sessionInfo}->{loginHistory}->{successLogin}
2169             ? $self->mkSessionArray(
2170             $self->{sessionInfo}->{loginHistory}->{successLogin},
2171             $self->msg(PM_LAST_LOGINS),
2172             0, 0
2173             )
2174             : ""
2175             )
2176             . (
2177             $self->{sessionInfo}->{loginHistory}->{failedLogin}
2178             ? $self->mkSessionArray(
2179             $self->{sessionInfo}->{loginHistory}->{failedLogin},
2180             $self->msg(PM_LAST_FAILED_LOGINS),
2181             0, 1
2182             )
2183             : ""
2184             )
2185             ) if ( $self->{checkLogins} );
2186              
2187             PE_OK;
2188             }
2189              
2190             ##@method private hashref _sumUpSession(Lemonldap::NG::Common::Session session)
2191             # put main session data into a hash ref
2192             # @param hashref $session The session to sum up
2193             # @return hashref
2194             sub _sumUpSession {
2195             my ( $self, $session, $withoutUser ) = @_;
2196             my $res =
2197             $withoutUser
2198             ? {}
2199             : { user => $session->{ $self->{whatToTrace} } };
2200             $res->{$_} = $session->{$_}
2201             foreach ( "_utime", "ipAddr", keys %{ $self->{sessionDataToRemember} } );
2202             return $res;
2203             }
2204              
2205             ##@method private string mkSessionArray(string title,array datas)
2206             # Build an HTML array to display sessions
2207             # @param $sessions Array ref of hash ref containing sessions datas
2208             # @param $title Title of the array
2209             # @param $displayUser To display "User" column
2210             # @param $displaError To display "Error" column
2211             # @return HTML string
2212             sub mkSessionArray {
2213             my ( $self, $sessions, $title, $displayUser, $displayError ) = @_;
2214              
2215             return "" unless ( ref $sessions eq "ARRAY" and @$sessions );
2216              
2217             my $tmp = $title ? "<h3>$title</h3>" : "";
2218             $tmp .= "<table class=\"info\"><tbody>";
2219              
2220             $tmp .= "<tr>";
2221             $tmp .= "<th>" . $self->msg(PM_USER) . "</th>"
2222             if ($displayUser);
2223             $tmp .= "<th>" . $self->msg(PM_DATE) . "</th>";
2224             $tmp .= "<th>" . $self->msg(PM_IP) . "</th>";
2225             $tmp .= "<th>" . $self->{sessionDataToRemember}->{$_} . "</th>"
2226             foreach ( keys %{ $self->{sessionDataToRemember} } );
2227             $tmp .= '<th>' . $self->msg(PM_ERROR_MSG) . '</th>'
2228             if ($displayError);
2229             $tmp .= '</tr>';
2230              
2231             foreach my $session (@$sessions) {
2232             $tmp .= "<tr>";
2233             $tmp .= "<td>$session->{user}</td>" if ($displayUser);
2234             $tmp .=
2235             "<td><script type=\"text/javascript\">var _date=new Date($session->{_utime}*1000);document.write(_date.toLocaleString());</script></td>";
2236             $tmp .= "<td>$session->{ipAddr}</td>";
2237             $tmp .= "<td>" . ( $session->{$_} || "" ) . "</td>"
2238             foreach ( keys %{ $self->{sessionDataToRemember} } );
2239             $tmp .= "<td>$session->{error}</td>" if ($displayError);
2240             $tmp .= "</tr>";
2241             }
2242             $tmp .= '</tbody></table>';
2243             return $tmp;
2244             }
2245              
2246             ## @method private string _mkRemoveOtherLink()
2247             # Build the removeOther link
2248             # Last part of URL is built trough javascript
2249             # @return removeOther link in HTML code
2250             sub _mkRemoveOtherLink {
2251             my $self = shift;
2252              
2253             my $link = $self->{portal} . "?removeOther=1";
2254              
2255             return
2256             "<p class=\"removeOther\"><a href=\"$link\" onclick=\"_go=0\">"
2257             . $self->msg(PM_REMOVE_OTHER_SESSIONS)
2258             . "</a></p>";
2259             }
2260              
2261             ##@apmethod int grantSession()
2262             # Check grantSessionRule to allow session creation.
2263             #@return Lemonldap::NG::Portal constant
2264             sub grantSession {
2265             my ($self) = @_;
2266              
2267             if ( defined $self->{grantSessionRule} ) {
2268              
2269             # Eval grantSessionRule
2270             # Kept for backward compatibility with LL::NG 1.1.2 and previous
2271             my $grantSessionRule = $self->{grantSessionRule};
2272              
2273             unless ( $self->safe->reval($grantSessionRule) ) {
2274             $self->lmLog(
2275             "User " . $self->{user} . " was not granted to open session",
2276             'error' );
2277             $self->registerLogin(PE_SESSIONNOTGRANTED);
2278             return PE_SESSIONNOTGRANTED;
2279             }
2280             }
2281              
2282             # Eval grantSessionRules sorted by comments
2283             sub sortByComment {
2284             my $A = ( $a =~ /^.*?##(.*)$/ )[0];
2285             my $B = ( $b =~ /^.*?##(.*)$/ )[0];
2286             return !$A ? 1 : !$B ? -1 : $A cmp $B;
2287             }
2288             foreach ( sort sortByComment keys %{ $self->{grantSessionRules} } ) {
2289             $self->lmLog( "Grant session condition \"$_\" checked", "debug" );
2290             unless ( $self->safe->reval($_) ) {
2291             $self->lmLog(
2292             "User " . $self->{user} . " was not granted to open session",
2293             'error' );
2294             my $msg = $self->safe->reval( $self->{grantSessionRules}->{$_} );
2295             $msg = $self->{grantSessionRules}->{$_} if ($@);
2296             $self->{ "error_" . PE_SESSIONNOTGRANTED } = $msg if ($msg);
2297             $self->registerLogin(PE_SESSIONNOTGRANTED);
2298             return PE_SESSIONNOTGRANTED;
2299             }
2300             }
2301              
2302             my $user = $self->{sessionInfo}->{ $self->{whatToTrace} };
2303             $self->_sub( 'userNotice', "Session granted for $user" ) if ($user);
2304             $self->registerLogin(PE_OK);
2305             return PE_OK;
2306             }
2307              
2308             ##@apmethod int store()
2309             # Store user's datas in sessions database.
2310             # Now, the user is known, authenticated and session variable are evaluated.
2311             # It's time to store his parameters with Apache::Session::* module
2312             #@return Lemonldap::NG::Portal constant
2313             sub store {
2314             my ($self) = @_;
2315              
2316             # Now, user is authenticated => inform Apache
2317             $self->setApacheUser( $self->{sessionInfo}->{ $self->{whatToTrace} } );
2318              
2319             # Create second session for unsecure cookie
2320             if ( $self->{securedCookie} == 2 ) {
2321             my $session2 = $self->getApacheSession( undef, 1 );
2322              
2323             my %infos = %{ $self->{sessionInfo} };
2324             $infos{_httpSessionType} = 1;
2325              
2326             $session2->update( \%infos );
2327              
2328             $self->{sessionInfo}->{_httpSession} = $session2->id;
2329             }
2330              
2331             # Main session
2332             my $session = $self->getApacheSession( $self->{id} );
2333             return PE_APACHESESSIONERROR unless ($session);
2334              
2335             # Compute unsecure cookie value if needed
2336             if ( $self->{securedCookie} == 3 ) {
2337             $self->{sessionInfo}->{_httpSession} =
2338             $self->{cipher}->encryptHex( $self->{id}, "http" );
2339             }
2340              
2341             # Fill session
2342             my $infos = {};
2343             foreach my $k ( keys %{ $self->{sessionInfo} } ) {
2344             next unless defined $self->{sessionInfo}->{$k};
2345             my $displayValue = $self->{sessionInfo}->{$k};
2346             if ( $self->{hiddenAttributes} =~ /\b$k\b/ ) {
2347             $displayValue = '****';
2348             }
2349             $self->lmLog( "Store $displayValue in session key $k", 'debug' );
2350             $infos->{$k} = $self->{sessionInfo}->{$k};
2351             }
2352             $session->update($infos);
2353              
2354             PE_OK;
2355             }
2356              
2357             ## @apmethod int authFinish
2358             # Call authFinish method from authentication module
2359             # @return Lemonldap::NG::Portal constant
2360             sub authFinish {
2361             my $self = shift;
2362              
2363             eval { $self->{error} = $self->SUPER::authFinish; };
2364             if ($@) {
2365             $self->lmLog(
2366             "Optional authFinish method not defined in current authentication module: $@",
2367             'debug'
2368             );
2369             return PE_OK;
2370             }
2371              
2372             return $self->{error};
2373             }
2374              
2375             ## @apmethod int userDBFinish
2376             # Call userDBFinish method from userDB module
2377             # @return Lemonldap::NG::Portal constant
2378             sub userDBFinish {
2379             my $self = shift;
2380              
2381             eval { $self->{error} = $self->SUPER::userDBFinish; };
2382             if ($@) {
2383             $self->lmLog(
2384             "Optional userDBFinish method not defined in current userDB module: $@",
2385             'debug'
2386             );
2387             return PE_OK;
2388             }
2389              
2390             return $self->{error};
2391             }
2392              
2393             ## @apmethod int passwordDBFinish
2394             # Call passwordDBFinish method from passwordDB module
2395             # @return Lemonldap::NG::Portal constant
2396             sub passwordDBFinish {
2397             my $self = shift;
2398              
2399             eval { $self->{error} = $self->SUPER::passwordDBFinish; };
2400             if ($@) {
2401             $self->lmLog(
2402             "Optional passwordDBFinish method not defined in current passwordDB module: $@",
2403             'debug'
2404             );
2405             return PE_OK;
2406             }
2407              
2408             return $self->{error};
2409             }
2410              
2411             ##@apmethod int buildCookie()
2412             # Build the Lemonldap::NG cookie.
2413             #@return Lemonldap::NG::Portal constant
2414             sub buildCookie {
2415             my $self = shift;
2416             push @{ $self->{cookie} },
2417             $self->cookie(
2418             -name => $self->{cookieName},
2419             -value => $self->{id},
2420             -domain => $self->{domain},
2421             -path => "/",
2422             -secure => $self->{securedCookie},
2423             -httponly => $self->{httpOnly},
2424             -expires => $self->{cookieExpiration},
2425             @_,
2426             );
2427             if ( $self->{securedCookie} >= 2 ) {
2428             push @{ $self->{cookie} },
2429             $self->cookie(
2430             -name => $self->{cookieName} . "http",
2431             -value => $self->{sessionInfo}->{_httpSession},
2432             -domain => $self->{domain},
2433             -path => "/",
2434             -secure => 0,
2435             -httponly => $self->{httpOnly},
2436             -expires => $self->{cookieExpiration},
2437             @_,
2438             );
2439             }
2440             PE_OK;
2441             }
2442              
2443             ##@apmethod int checkNotification()
2444             # Check if messages has to be notified.
2445             # Call Lemonldap::NG::Common::Notification::getNotification().
2446             #@return Lemonldap::NG::Portal constant
2447             sub checkNotification {
2448             my $self = shift;
2449             if ( $self->{notification}
2450             and $self->{_notification} ||=
2451             $self->{notifObject}->getNotification($self) )
2452             {
2453             return PE_NOTIFICATION;
2454             }
2455             return PE_OK;
2456             }
2457              
2458             ## @apmethod int issuerForAuthUser()
2459             # Check IssuerDB activation rule
2460             # Register used module in user session
2461             # @return Lemonldap::NG::Portal constant
2462             sub issuerForAuthUser {
2463             my $self = shift;
2464              
2465             # User information
2466             my $user = $self->{sessionInfo}->{ $self->{whatToTrace} } || 'unknown';
2467              
2468             # Get active module
2469             my $issuerDBtype = $self->get_module('issuer');
2470              
2471             # Eval activation rule
2472             my $rule = $self->{ 'issuerDB' . $issuerDBtype . 'Rule' };
2473              
2474             if ( defined $rule ) {
2475              
2476             $self->lmLog( "Applying rule: $rule", 'debug' );
2477              
2478             unless ( $self->safe->reval($rule) ) {
2479             $self->lmLog(
2480             "User $user was not allowed to use IssuerDB $issuerDBtype",
2481             'warn' );
2482              
2483             return PE_OK;
2484             }
2485              
2486             }
2487             else {
2488             $self->lmLog( "No rule found for IssuerDB $issuerDBtype", 'debug' );
2489             }
2490              
2491             $self->lmLog( "User $user allowed to use IssuerDB $issuerDBtype", 'debug' );
2492              
2493             # Register IssuerDB module in session
2494             $self->addSessionValue( '_issuerDB', $issuerDBtype, $self->{id} );
2495              
2496             # Call IssuerDB module method
2497             return $self->SUPER::issuerForAuthUser();
2498             }
2499              
2500             ##@apmethod int autoRedirect()
2501             # If the user was redirected to the portal, we will now redirect him
2502             # to the requested URL.
2503             #@return Lemonldap::NG::Portal constant
2504             sub autoRedirect {
2505             my $self = shift;
2506             $self->clearHiddenFormValue();
2507              
2508             # Default redirection URL
2509             $self->{urldc} ||= $self->{portal}
2510             if ( $self->{mustRedirect} or $self->info() );
2511              
2512             # Display info before redirecting
2513             if ( $self->info() ) {
2514             $self->{infoFormMethod} = $self->param('method') || "get";
2515             $self->clearHiddenFormValue();
2516             my ($query_string) = ( $self->{urldc} =~ /.+?\?(.+)/ );
2517             if ($query_string) {
2518             $self->lmLog(
2519             "Transform query string $query_string into hidden form values",
2520             'debug'
2521             );
2522             my $query = CGI->new($query_string);
2523             my $formFields = $query->Vars;
2524             foreach ( keys %$formFields ) {
2525             $self->setHiddenFormValue( $_, $formFields->{$_}, "", 0 );
2526             }
2527             }
2528             return PE_INFO;
2529             }
2530              
2531             # Redirection should be made if
2532             # - urldc defined
2533             if ( $self->{urldc} ) {
2534              
2535             # Cross-domain mechanism
2536             if ( $self->{cda}
2537             and $self->{id}
2538             and $self->{urldc} !~ m#^https?://[^/]*$self->{domain}(:\d+)?/#oi
2539             and $self->isTrustedUrl( $self->{urldc} ) )
2540             {
2541             my $ssl = $self->{urldc} =~ /^https/;
2542             $self->lmLog( 'CDA request', 'debug' );
2543             $self->{urldc} .= ( $self->{urldc} =~ /\?/ ? '&' : '?' )
2544             . (
2545             ( $self->{securedCookie} < 2 or $ssl )
2546             ? $self->{cookieName} . "=" . $self->{id}
2547             : $self->{cookieName} . "http="
2548             . $self->{sessionInfo}->{_httpSession}
2549             );
2550             }
2551              
2552             $self->updateStatus;
2553              
2554             if ( $self->safe->reval( $self->{jsRedirect} ) ) {
2555             $self->{redirectFormMethod} = "get";
2556             return PE_REDIRECT;
2557             }
2558             else {
2559             print $self->redirect(
2560             -status => '303 See Other',
2561             -location => $self->{urldc},
2562             );
2563             $self->quit();
2564             }
2565             }
2566             PE_OK;
2567             }
2568              
2569             ## @method void returnSOAPMessage()
2570             # Print SOAP message
2571             # @return void
2572             sub returnSOAPMessage {
2573             my $self = shift;
2574              
2575             # Quit if no SOAP message
2576             $self->quit() unless $self->{SOAPMessage};
2577              
2578             # Print HTTP header and SOAP message
2579             binmode( STDOUT, ":bytes" );
2580             print $self->header( -type => 'application/xml' );
2581             print $self->{SOAPMessage};
2582              
2583             # Exit
2584             $self->quit();
2585             }
2586              
2587             ## @method void autoPost()
2588             # Transfer POST data with auto submit
2589             # @return void
2590             sub autoPost {
2591             my $self = shift;
2592              
2593             # Get URL and Form fields
2594             $self->{urldc} = $self->{postUrl};
2595             my $formFields = $self->{postFields};
2596              
2597             $self->clearHiddenFormValue();
2598             foreach ( keys %$formFields ) {
2599             $self->setHiddenFormValue( $_, $formFields->{$_}, "", 0 );
2600             }
2601              
2602             # Display info before redirecting
2603             if ( $self->info() ) {
2604             $self->{infoFormMethod} = $self->param('method') || "post";
2605             return PE_INFO;
2606             }
2607              
2608             $self->{redirectFormMethod} = "post";
2609             return PE_REDIRECT;
2610             }
2611              
2612             ## @method HASHREF getCustomTemplateParameters()
2613             # Find custom templates parameters
2614             # @return Custom parameters
2615             sub getCustomTemplateParameters {
2616              
2617             my $self = shift;
2618             my $customTplParams = {};
2619              
2620             foreach ( keys %$self ) {
2621             next unless ( $_ =~ /^tpl_(.+)$/ );
2622             my $tplParam = $1;
2623             my $tplValue = $self->{ "tpl_" . $tplParam };
2624             $self->lmLog( "Set custom template parameter $tplParam with $tplValue",
2625             'debug' );
2626              
2627             $customTplParams->{$tplParam} = $tplValue;
2628             }
2629              
2630             return $customTplParams;
2631             }
2632              
2633             1;
2634              
2635             __END__
2636              
2637             =head1 NAME
2638              
2639             =encoding utf8
2640              
2641             Lemonldap::NG::Portal::Simple - Base module for building Lemonldap::NG compatible portals
2642              
2643             =head1 SYNOPSIS
2644              
2645             use Lemonldap::NG::Portal::Simple;
2646             my $portal = new Lemonldap::NG::Portal::Simple(
2647             domain => 'example.com',
2648             globalStorage => 'Apache::Session::MySQL',
2649             globalStorageOptions => {
2650             DataSource => 'dbi:mysql:database=dbname;host=127.0.0.1',
2651             UserName => 'db_user',
2652             Password => 'db_password',
2653             TableName => 'sessions',
2654             LockDataSource => 'dbi:mysql:database=dbname;host=127.0.0.1',
2655             LockUserName => 'db_user',
2656             LockPassword => 'db_password',
2657             },
2658             ldapServer => 'ldap.domaine.com,ldap-backup.domaine.com',
2659             securedCookie => 1,
2660             exportedVars => {
2661             uid => 'uid',
2662             cn => 'cn',
2663             mail => 'mail',
2664             appli => 'appli',
2665             },
2666             # Activate SOAP service
2667             Soap => 1
2668             );
2669            
2670             if($portal->process()) {
2671             # Write here the menu with CGI methods. This page is displayed ONLY IF
2672             # the user was not redirected here.
2673             print $portal->header('text/html; charset=utf-8'); # DON'T FORGET THIS (see L<CGI(3)>)
2674             print "...";
2675              
2676             # or redirect the user to the menu
2677             print $portal->redirect( -uri => 'https://portal/menu');
2678             }
2679             else {
2680             # Write here the html form used to authenticate with CGI methods.
2681             # $portal->error returns the error message if athentification failed
2682             # Warning: by defaut, input names are "user" and "password"
2683             print $portal->header('text/html; charset=utf-8'); # DON'T FORGET THIS (see L<CGI(3)>)
2684             print "...";
2685             print '<form method="POST">';
2686             # In your form, the following value is required for redirection
2687             print '<input type="hidden" name="url" value="'.$portal->param('url').'">';
2688             # Next, login and password
2689             print 'Login : <input name="user"><br>';
2690             print 'Password : <input name="password" type="password" autocomplete="off">';
2691             print '<input type="submit" value="go" />';
2692             print '</form>';
2693             }
2694              
2695             SOAP mode authentication (client) :
2696              
2697             #!/usr/bin/perl -l
2698            
2699             use SOAP::Lite;
2700             use Data::Dumper;
2701            
2702             my $soap =
2703             SOAP::Lite->proxy('http://auth.example.com/')
2704             ->uri('urn:/Lemonldap::NG::Common::CGI::SOAPService');
2705             my $r = $soap->getCookies( 'user', 'password' );
2706            
2707             # Catch SOAP errors
2708             if ( $r->fault ) {
2709             print STDERR "SOAP Error: " . $r->fault->{faultstring};
2710             }
2711             else {
2712             my $res = $r->result();
2713            
2714             # If authentication failed, display error
2715             if ( $res->{error} ) {
2716             print STDERR "Error: " . $soap->error( $res->{error} )->result();
2717             }
2718            
2719             # print session-ID
2720             else {
2721             print "Cookie: lemonldap=" . $res->{cookies}->{lemonldap};
2722             }
2723             }
2724              
2725             =head1 DESCRIPTION
2726              
2727             Lemonldap::NG::Portal::Simple is the base module for building Lemonldap::NG
2728             compatible portals. You can use it either by inheritance or by writing
2729             anonymous methods like in the example above.
2730              
2731             See L<Lemonldap::NG::Portal::SharedConf> for a complete example of use of
2732             Lemonldap::Portal::* libraries.
2733              
2734             =head1 METHODS
2735              
2736             =head2 Constructor (new)
2737              
2738             =head3 Args
2739              
2740             =over
2741              
2742             =item * ldapServer: server(s) used to retrive session information and to valid
2743             credentials (localhost by default). More than one server can be set here
2744             separated by commas. The servers will be tested in the specifies order.
2745             To use TLS, set "ldap+tls://server" and to use LDAPS, set "ldaps://server"
2746             instead of server name. If you use TLS, you can set any of the
2747             Net::LDAP->start_tls() sub like this:
2748             "ldap/tls://server/verify=none&capath=/etc/ssl"
2749             You can also use caFile and caPath parameters.
2750              
2751             =item * ldapPort: tcp port used by ldap server.
2752              
2753             =item * ldapBase: base of the ldap directory.
2754              
2755             =item * managerDn: dn to used to connect to ldap server. By default, anonymous
2756             bind is used.
2757              
2758             =item * managerPassword: password to used to connect to ldap server. By
2759             default, anonymous bind is used.
2760              
2761             =item * securedCookie: set it to 1 if you want to protect user cookies.
2762              
2763             =item * cookieName: name of the cookie used by Lemonldap::NG (lemon by default).
2764              
2765             =item * domain: cookie domain. You may have to give it else the SSO will work
2766             only on your server.
2767              
2768             =item * globalStorage: required: L<Apache::Session> library to used to store
2769             session information.
2770              
2771             =item * globalStorageOptions: parameters to bind to L<Apache::Session> module
2772              
2773             =item * authentication: sheme to authenticate users (default: "ldap"). It can
2774             be set to:
2775              
2776             =over
2777              
2778             =item * B<SSL>: See L<Lemonldap::NG::Portal::AuthSSL>.
2779              
2780             =back
2781              
2782             =item * caPath, caFile: if you use ldap+tls you can overwrite cafile or capath
2783             options with those parameters. This is useful if you use a shared
2784             configuration.
2785              
2786             =item * ldapPpolicyControl: set it to 1 if you want to use LDAP Password Policy
2787              
2788             =item * grantSessionRule: rule applied to grant session opening for a user. Can
2789             use all exported attributes, macros, groups and custom functions.
2790              
2791             =back
2792              
2793             =head2 Methods that can be overloaded
2794              
2795             All the functions above can be overloaded to adapt Lemonldap::NG to your
2796             environment. They MUST return one of the exported constants (see above)
2797             and are called in this order by process().
2798              
2799             =head3 controlUrlOrigin
2800              
2801             If the user was redirected by a Lemonldap::NG handler, stores the url that will be
2802             used to redirect the user after authentication.
2803              
2804             =head3 controlExistingSession
2805              
2806             Controls if a previous session is always available. If true, it call the sub
2807             C<existingSession> with two parameters: id and a scalar tied on Apache::Session
2808             module choosed to store sessions. See bellow
2809              
2810             =head3 existingSession
2811              
2812             This sub is called only if a previous session exists and is available. By
2813             defaults, it returns PE_OK so user is re-authenticated. You can overload it:
2814             for example if existingSession just returns PE_DONE: authenticated users are
2815             not re-authenticated and C<>process> returns true.
2816              
2817             =head3 extractFormInfo
2818              
2819             Method implemented into Lemonldap::NG::Portal::Auth* modules. By default
2820             (ldap bind), converts form input into object variables ($self->{user} and
2821             $self->{password}).
2822              
2823             =head3 formateParams
2824              
2825             Does nothing. To be overloaded if needed.
2826              
2827             =head3 formateFilter
2828              
2829             Creates the ldap filter using $self->{user}. By default :
2830              
2831             $self->{filter} = "(&(uid=" . $self->{user} . ")(objectClass=inetOrgPerson))";
2832              
2833             If $self->{AuthLDAPFilter} is set, it is used instead of this. This is used by
2834             Lemonldap::NG::Portal::Auth* modules to overload filter.
2835              
2836             =head3 connectLDAP
2837              
2838             Connects to LDAP server.
2839              
2840             =head3 bind
2841              
2842             Binds to the LDAP server using $self->{managerDn} and $self->{managerPassword}
2843             if exist. Anonymous bind is provided else.
2844              
2845             =head3 search
2846              
2847             Retrives the LDAP entry corresponding to the user using $self->{filter}.
2848              
2849             =head3 setAuthSessionInfo
2850              
2851             Same as setSessionInfo but implemented in Lemonldap::NG::Portal::Auth* modules.
2852              
2853             =head3 setSessionInfo
2854              
2855             Prepares variables to store in central cache (stored temporarily in
2856             C<$self->{sessionInfo}>). It use C<exportedVars> entry (passed to the new sub)
2857             if defined to know what to store else it stores uid, cn and mail attributes.
2858              
2859             =head3 getSessionInfo
2860              
2861             Pick up an information stored in session.
2862              
2863             =head3 setGroups
2864              
2865             Does nothing by default.
2866              
2867             =head3 authenticate
2868              
2869             Method implemented in Lemonldap::NG::Portal::Auth* modules. By default (ldap),
2870             authenticates the user by rebinding to the LDAP server using the dn retrived
2871             with search() and the password.
2872              
2873             =head3 grantSession
2874              
2875             Use grantSessionRule parameter to allow session opening.
2876              
2877             =head3 store
2878              
2879             Stores information collected by setSessionInfo into the central cache.
2880             The portal connects the cache using the L<Apache::Session> module passed by
2881             the globalStorage parameters (see constructor).
2882              
2883             =head3 unbind
2884              
2885             Disconnects from the LDAP server.
2886              
2887             =head3 buildCookie
2888              
2889             Creates the Lemonldap::NG cookie.
2890              
2891             =head3 log
2892              
2893             Does nothing. To be overloaded if wanted.
2894              
2895             =head3 autoRedirect
2896              
2897             Redirects the user to the url stored by controlUrlOrigin().
2898              
2899             =head2 Other methods
2900              
2901             =head3 process
2902              
2903             Main method.
2904              
2905             =head3 error
2906              
2907             Returns the error message corresponding to the error returned by the methods
2908             described above
2909              
2910             =head3 error_type
2911              
2912             Give the type of the error (positive, warning or positive)
2913              
2914             =head3 _bind( $ldap, $dn, $password )
2915              
2916             Method used to bind to the ldap server.
2917              
2918             =head3 header
2919              
2920             Overloads the CGI::header method to add Lemonldap::NG cookie.
2921              
2922             =head3 redirect
2923              
2924             Overloads the CGI::redirect method to add Lemonldap::NG cookie.
2925              
2926             =head2 EXPORT
2927              
2928             =head3 Constants
2929              
2930             =over 5
2931              
2932             =item * B<PE_OK>: all is good
2933              
2934             =item * B<PE_SESSIONEXPIRED>: the user session has expired
2935              
2936             =item * B<PE_FORMEMPTY>: Nothing was entered in the login form
2937              
2938             =item * B<PE_USERNOTFOUND>: the user was not found in the (ldap) directory
2939              
2940             =item * B<PE_WRONGMANAGERACCOUNT>: the account used to bind to LDAP server in order to
2941             find the user distinguished name (dn) was refused by the server
2942              
2943             =item * B<PE_BADCREDENTIALS>: bad login or password
2944              
2945             =item * B<PE_LDAPERROR>: abnormal error from ldap
2946              
2947             =item * B<PE_APACHESESSIONERROR>: abnormal error from Apache::Session
2948              
2949             =item * B<PE_FIRSTACCESS>: First access to the portal
2950              
2951             =item * B<PE_BADCERTIFICATE>: Wrong certificate
2952              
2953             =item * PE_PP_ACCOUNT_LOCKED: account locked
2954              
2955             =item * PE_PP_PASSWORD_EXPIRED: password axpired
2956              
2957             =item * PE_CERTIFICATEREQUIRED: certificate required
2958              
2959             =item * PE_ERROR: unclassified error
2960              
2961             =back
2962              
2963             =head1 SEE ALSO
2964              
2965             L<Lemonldap::NG::Handler>, L<Lemonldap::NG::Portal::SharedConf>, L<CGI>,
2966             L<http://lemonldap-ng.org/>
2967              
2968             =head1 AUTHOR
2969              
2970             =over
2971              
2972             =item Clement Oudot, E<lt>clem.oudot@gmail.comE<gt>
2973              
2974             =item François-Xavier Deltombe, E<lt>fxdeltombe@gmail.com.E<gt>
2975              
2976             =item Xavier Guimard, E<lt>x.guimard@free.frE<gt>
2977              
2978             =item Sandro Cazzaniga, E<lt>cazzaniga.sandro@gmail.comE<gt>
2979              
2980             =item Thomas Chemineau, E<lt>thomas.chemineau@gmail.comE<gt>
2981              
2982             =back
2983              
2984             =head1 BUG REPORT
2985              
2986             Use OW2 system to report bug or ask for features:
2987             L<http://jira.ow2.org>
2988              
2989             =head1 DOWNLOAD
2990              
2991             Lemonldap::NG is available at
2992             L<http://forge.objectweb.org/project/showfiles.php?group_id=274>
2993              
2994             =head1 COPYRIGHT AND LICENSE
2995              
2996             =over
2997              
2998             =item Copyright (C) 2006, 2007, 2008, 2009, 2010, 2012 by Xavier Guimard, E<lt>x.guimard@free.frE<gt>
2999              
3000             =item Copyright (C) 2012 by Sandro Cazzaniga, E<lt>cazzaniga.sandro@gmail.comE<gt>
3001              
3002             =item Copyright (C) 2012, 2012, 2013 by François-Xavier Deltombe, E<lt>fxdeltombe@gmail.com.E<gt>
3003              
3004             =item Copyright (C) 2006, 2008, 2009, 2010, 2011, 2012, 2012, 2013 by Clement Oudot, E<lt>clem.oudot@gmail.comE<gt>
3005              
3006             =item Copyright (C) 2010, 2011 by Thomas Chemineau, E<lt>thomas.chemineau@gmail.comE<gt>
3007              
3008             =back
3009              
3010             This library is free software; you can redistribute it and/or modify
3011             it under the terms of the GNU General Public License as published by
3012             the Free Software Foundation; either version 2, or (at your option)
3013             any later version.
3014              
3015             This program is distributed in the hope that it will be useful,
3016             but WITHOUT ANY WARRANTY; without even the implied warranty of
3017             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3018             GNU General Public License for more details.
3019              
3020             You should have received a copy of the GNU General Public License
3021             along with this program. If not, see L<http://www.gnu.org/licenses/>.
3022              
3023             =cut