1# -*- indent-tabs-mode: nil; -*-
2# vim:ft=perl:et:sw=4
3# $Id$
4
5# Sympa - SYsteme de Multi-Postage Automatique
6#
7# Copyright (c) 1997, 1998, 1999 Institut Pasteur & Christophe Wolfhugel
8# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
9# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites
10# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER
11# Copyright 2018, 2019 The Sympa Community. See the AUTHORS.md file at
12# the top-level directory of this distribution and at
13# <https://github.com/sympa-community/sympa.git>.
14#
15# This program is free software; you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation; either version 2 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program.  If not, see <http://www.gnu.org/licenses/>.
27
28package Sympa::WWW::Session;
29
30use strict;
31use warnings;
32use CGI::Cookie;
33use Digest::MD5;
34
35use Conf;
36use Sympa::DatabaseManager;
37use Sympa::Language;
38use Sympa::Log;
39use Sympa::Tools::Data;
40use Sympa::Tools::Password;
41
42# this structure is used to define which session attributes are stored in a
43# dedicated database col where others are compiled in col 'data_session'
44my %session_hard_attributes = (
45    'id_session'   => 1,
46    'prev_id'      => 1,
47    'date'         => 1,
48    'refresh_date' => 1,
49    'remote_addr'  => 1,
50    'robot'        => 1,
51    'email'        => 1,
52    'start_date'   => 1,
53    'hit'          => 1,
54    'new_session'  => 1,
55);
56
57my $log = Sympa::Log->instance;
58
59sub new {
60    my $pkg     = shift;
61    my $robot   = shift;
62    my $context = shift;
63
64    my $cookie = $context->{'cookie'};
65    my $action = $context->{'action'};
66    my $rss    = $context->{'rss'};
67    #my $ajax = $context->{'ajax'};
68
69    $log->syslog('debug', '(%s, %s, %s)', $robot, $cookie, $action);
70    my $self = {'robot' => $robot};    # set current robot
71    bless $self, $pkg;
72
73    unless ($robot) {
74        $log->syslog('err',
75            'Missing robot parameter, cannot create session object');
76        return undef;
77    }
78
79    # passive_session are session not stored in the database, they are used
80    # for crawler bots and action such as css, wsdl, ajax and rss
81    if (_is_a_crawler($robot)) {
82        $self->{'is_a_crawler'}    = 1;
83        $self->{'passive_session'} = 1;
84    }
85    $self->{'passive_session'} = 1
86        if $rss
87        or $action and ($action eq 'wsdl' or $action eq 'css');
88
89    # if a session cookie exist, try to restore an existing session, don't
90    # store sessions from bots
91    if ($cookie and !$self->{'passive_session'}) {
92        my $status;
93        $status = $self->load($cookie);
94        unless (defined $status) {
95            return undef;
96        }
97        if ($status eq 'not_found') {
98            # Start a Sympa::WWW::Session->new(may be a fake cookie).
99            $log->syslog('info', 'Ignoring unknown session cookie "%s"',
100                $cookie);
101            return (Sympa::WWW::Session->new($robot));
102        }
103    } else {
104        # create a new session context
105        $self->{'new_session'} =
106            1;    ## Tag this session as new, ie no data in the DB exist
107        $self->{'id_session'}  = Sympa::Tools::Password::get_random();
108        $self->{'email'}       = 'nobody';
109        $self->{'remote_addr'} = $ENV{'REMOTE_ADDR'};
110        $self->{'date'} = $self->{'refresh_date'} = $self->{'start_date'} =
111            time;
112        $self->{'hit'}  = 1;
113        $self->{'data'} = '';
114    }
115    return $self;
116}
117
118sub load {
119    $log->syslog('debug2', '(%s, %s)', @_);
120    my $self   = shift;
121    my $cookie = shift;
122
123    my $sdm = Sympa::DatabaseManager->instance;
124    my $sth;
125
126    my $session_id = _cookie2id($cookie);
127    unless ($session_id) {
128        $log->syslog('info', 'Undefined session ID in cookie "%s"', $cookie);
129        return undef;
130    }
131
132    ## Cookie may contain current or previous session ID.
133    unless (
134        $sdm
135        and $sth = $sdm->do_prepared_query(
136            q{SELECT id_session AS id_session, prev_id_session AS prev_id,
137                     date_session AS "date",
138                     remote_addr_session AS remote_addr,
139                     email_session AS email,
140                     data_session AS data, hit_session AS hit,
141                     start_date_session AS start_date,
142                     refresh_date_session AS refresh_date
143              FROM session_table
144              WHERE id_session = ? AND prev_id_session IS NOT NULL OR
145                    prev_id_session = ?},
146            $session_id,
147            $session_id
148        )
149    ) {
150        $log->syslog('err', 'Unable to load session %s', $session_id);
151        return undef;
152    }
153
154    my $session = $sth->fetchrow_hashref('NAME_lc');
155    return 'not_found' unless $session;
156    if ($sth->fetchrow_hashref('NAME_lc')) {
157        $log->syslog('err',
158            'The SQL statement did return more than one session');
159        $session->{'email'} = '';    #FIXME
160    }
161    $sth->finish;
162
163    my @keys;
164
165    my %datas = Sympa::Tools::Data::string_2_hash($session->{'data'});
166    @keys = keys %datas;
167    @{$self}{@keys} = @datas{@keys};
168    # Canonicalize lang if possible.
169    $self->{lang} =
170        Sympa::Language::canonic_lang($self->{lang}) || $self->{lang}
171        if $self->{lang};
172
173    @keys = qw(id_session prev_id date refresh_date start_date hit
174        remote_addr email);
175    @{$self}{@keys} = @{$session}{@keys};
176    # Update hit count.
177    $self->{hit}++;
178
179    return $self;
180}
181
182# Get correct session ID from sympa_session cookie value.
183sub _cookie2id {
184    my $cookie = shift;
185
186    return undef unless $cookie;
187    return $1 if $cookie =~ /\A5e55([0-9]{14,16})\z/;    #  Compat. < 6.2.42
188    return $cookie if $cookie =~ /\A[0-9]{14,16}\z/;
189    return undef;
190}
191
192## This method will both store the session information in the database
193sub store {
194    my $self = shift;
195    $log->syslog('debug', '');
196
197    return undef unless ($self->{'id_session'});
198    # do not create a session in session table for crawlers;
199    return
200        if ($self->{'is_a_crawler'});
201    # do not create a session in session table for action such as RSS or CSS
202    # or wsdlthat do not require this sophistication;
203    return
204        if ($self->{'passive_session'});
205
206    my %hash;
207    foreach my $var (keys %$self) {
208        next if ($session_hard_attributes{$var});
209        next unless ($var);
210        $hash{$var} = $self->{$var};
211    }
212    my $data_string = Sympa::Tools::Data::hash_2_string(\%hash);
213    my $time        = time;
214
215    my $sdm = Sympa::DatabaseManager->instance;
216
217    ## If this is a new session, then perform an INSERT
218    if ($self->{'new_session'}) {
219        # Store the new session ID in the DB
220        # Previous session ID is set to be same as new session ID.
221        unless (
222            $sdm
223            and $sdm->do_prepared_query(
224                q{INSERT INTO session_table
225                  (id_session, prev_id_session,
226                   date_session, refresh_date_session,
227                   remote_addr_session, robot_session,
228                   email_session, start_date_session, hit_session,
229                   data_session)
230                  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)},
231                $self->{'id_session'}, $self->{'id_session'},
232                $time,                 $time,
233                $ENV{'REMOTE_ADDR'},   $self->{'robot'},
234                $self->{'email'}, $self->{'start_date'}, $self->{'hit'},
235                $data_string
236            )
237        ) {
238            $log->syslog('err',
239                'Unable to add new information for session %s in database',
240                $self->{'id_session'});
241            return undef;
242        }
243
244        $self->{'prev_id'} = $self->{'id_session'};
245    } else {
246        ## If the session already exists in DB, then perform an UPDATE
247
248        ## Cookie may contain previous session ID.
249        my $sth;
250        unless (
251            $sdm
252            and $sth = $sdm->do_prepared_query(
253                q{SELECT id_session
254                  FROM session_table
255                  WHERE prev_id_session = ?},
256                $self->{'id_session'}
257            )
258        ) {
259            $log->syslog('err',
260                'Unable to update session information in database');
261            return undef;
262        }
263        my $new_id;
264        ($new_id) = $sth->fetchrow_array;
265        $sth->finish;
266        if ($new_id) {
267            $self->{'prev_id'}    = $self->{'id_session'};
268            $self->{'id_session'} = $new_id;
269        }
270
271        ## Update the new session in the DB
272        unless (
273            $sdm->do_prepared_query(
274                q{UPDATE session_table
275                  SET date_session = ?, remote_addr_session = ?,
276                      robot_session = ?, email_session = ?,
277                      start_date_session = ?, hit_session = ?, data_session = ?
278                  WHERE id_session = ? AND prev_id_session IS NOT NULL OR
279                        prev_id_session = ?},
280                $time,            $ENV{'REMOTE_ADDR'},
281                $self->{'robot'}, $self->{'email'},
282                $self->{'start_date'}, $self->{'hit'}, $data_string,
283                $self->{'id_session'},
284                $self->{'id_session'}
285            )
286        ) {
287            $log->syslog('err',
288                'Unable to update information for session %s in database',
289                $self->{'id_session'});
290            return undef;
291        }
292    }
293
294    return 1;
295}
296
297## This method will renew the session ID
298sub renew {
299    my $self = shift;
300    $log->syslog('debug', '(id_session=%s)', $self->{'id_session'});
301
302    return undef unless ($self->{'id_session'});
303    # do not create a session in session table for crawlers;
304    return
305        if ($self->{'is_a_crawler'});
306    # do not create a session in session table for action such as RSS or CSS
307    # or wsdlthat do not require this sophistication;
308    return
309        if ($self->{'passive_session'});
310
311    my %hash;
312    foreach my $var (keys %$self) {
313        next if ($session_hard_attributes{$var});
314        next unless ($var);
315        $hash{$var} = $self->{$var};
316    }
317
318    my $sth;
319    my $sdm = Sympa::DatabaseManager->instance;
320
321    # Cookie may contain previous session ID.
322    unless (
323        $sdm
324        and $sth = $sdm->do_prepared_query(
325            q{SELECT id_session
326              FROM session_table
327              WHERE prev_id_session = ?},
328            $self->{'id_session'}
329        )
330    ) {
331        $log->syslog('err',
332            'Unable to update information for session %s in database',
333            $self->{'id_session'});
334        return undef;
335    }
336    my $new_id;
337    ($new_id) = $sth->fetchrow_array;
338    $sth->finish;
339    if ($new_id) {
340        $self->{'prev_id'}    = $self->{'id_session'};
341        $self->{'id_session'} = $new_id;
342    }
343
344    ## Renew the session ID in order to prevent session hijacking
345    $new_id = Sympa::Tools::Password::get_random();
346
347    ## Do refresh the session ID when remote address was changed or refresh
348    ## interval was past.  Conditions also are checked by SQL so that
349    ## simultaneous processes will be prevented renewing cookie.
350    my $time        = time;
351    my $remote_addr = $ENV{'REMOTE_ADDR'};
352    my $refresh_term;
353    if ($Conf::Conf{'cookie_refresh'} == 0) {
354        $refresh_term = $time;
355    } else {
356        my $cookie_refresh = $Conf::Conf{'cookie_refresh'};
357        $refresh_term =
358            int($time - $cookie_refresh * 0.25 - rand($cookie_refresh * 0.5));
359    }
360    unless ($self->{'remote_addr'} ne $remote_addr
361        or $self->{'refresh_date'} <= $refresh_term) {
362        return 0;
363    }
364
365    ## First insert DB entry with new session ID,
366    # Note: prepared query cannot be used, because use of placeholder (?) as
367    # selected value is not portable.
368    $sth = $sdm->do_query(
369        q{INSERT INTO session_table
370          (id_session, prev_id_session,
371           start_date_session, date_session, refresh_date_session,
372           remote_addr_session, robot_session, email_session,
373           hit_session, data_session)
374          SELECT %s, id_session,
375                 start_date_session, date_session, %d,
376                 %s, %s, email_session,
377                 hit_session, data_session
378          FROM session_table
379          WHERE (id_session = %s AND prev_id_session IS NOT NULL OR
380                 prev_id_session = %s) AND
381                (remote_addr_session <> %s OR refresh_date_session <= %d)},
382        $sdm->quote($new_id),
383        $time,
384        $sdm->quote($remote_addr),
385        $sdm->quote($self->{'robot'}),
386        $sdm->quote($self->{'id_session'}),
387        $sdm->quote($self->{'id_session'}),
388        $sdm->quote($remote_addr), $refresh_term
389    );
390    unless ($sth) {
391        $log->syslog('err', 'Unable to renew session ID for session %s',
392            $self->{'id_session'});
393        return undef;
394    }
395    unless ($sth->rows) {
396        return 0;
397    }
398    ## Keep previous ID to prevent crosstalk, clearing grand-parent ID.
399    $sdm->do_prepared_query(
400        q{UPDATE session_table
401          SET prev_id_session = NULL
402          WHERE id_session = ?},
403        $self->{'id_session'}
404    );
405    ## Remove record of grand-parent ID.
406    $sdm->do_prepared_query(
407        q{DELETE FROM session_table
408          WHERE id_session = ? AND prev_id_session IS NULL},
409        $self->{'prev_id'}
410    );
411
412    ## Renew the session ID in order to prevent session hijacking
413    $log->syslog(
414        'info',
415        '[robot %s] [session %s] [client %s]%s new session %s',
416        $self->{'robot'},
417        $self->{'id_session'},
418        $remote_addr,
419        ($self->{'email'} ? sprintf(' [user %s]', $self->{'email'}) : ''),
420        $new_id
421    );
422    $self->{'prev_id'}      = $self->{'id_session'};
423    $self->{'id_session'}   = $new_id;
424    $self->{'refresh_date'} = $time;
425    $self->{'remote_addr'}  = $remote_addr;
426
427    return 1;
428}
429
430# Deprecated. Use purge_session_table() in task_manager.pl.
431#sub purge_old_sessions;
432
433# Moved to: Sympa::Ticket::purge_old_tickets().
434#sub purge_old_tickets;
435
436# list sessions for $robot where last access is newer then $delay. List is
437# limited to connected users if $connected_only
438sub list_sessions {
439    $log->syslog('debug3', '(%s, %s, %s)', @_);
440    my $delay          = shift;
441    my $robot          = shift;
442    my $connected_only = shift;
443
444    my @sessions;
445    my $sth;
446    my $sdm = Sympa::DatabaseManager->instance;
447    unless ($sdm) {
448        $log->syslog('err', 'Unavailable database connection');
449        return undef;
450    }
451
452    my @conditions;
453    push @conditions, sprintf('robot_session = %s', $sdm->quote($robot))
454        if $robot and $robot ne '*';
455    push @conditions, sprintf('%d < date_session ', time - $delay) if $delay;
456    push @conditions, " email_session <> 'nobody' "
457        if $connected_only and $connected_only eq 'on';
458
459    my $condition = join ' AND ', @conditions, 'prev_id_session IS NOT NULL';
460
461    my $statement =
462        sprintf q{SELECT remote_addr_session, email_session, robot_session,
463                         date_session AS date_epoch,
464                         start_date_session AS start_date_epoch, hit_session
465                  FROM session_table
466                  WHERE %s}, $condition;
467    $log->syslog('debug', 'Statement = %s', $statement);
468
469    unless ($sth = $sdm->do_query($statement)) {
470        $log->syslog('err', 'Unable to get the list of sessions for robot %s',
471            $robot);
472        return undef;
473    }
474
475    while (my $session = ($sth->fetchrow_hashref('NAME_lc'))) {
476        push @sessions, $session;
477    }
478
479    return \@sessions;
480}
481
482###############################
483# Subroutines to read cookies #
484###############################
485
486## Subroutine to get session cookie value
487sub get_session_cookie {
488    my $http_cookie = shift;
489    return Sympa::WWW::Session::_generic_get_cookie($http_cookie,
490        'sympa_session');
491}
492
493## Generic subroutine to set a cookie
494## Set user $email cookie, ckecksum use $secret, expire=(now|session|#sec)
495## domain=(localhost|<a domain>)
496sub set_cookie {
497    $log->syslog('debug', '(%s, %s, %s, %s)', @_);
498    my $self    = shift;
499    my $dom     = shift;
500    my $expires = shift;
501    my $use_ssl = shift;
502
503    $expires = $Conf::Conf{'cookie_expire'} unless defined $expires;
504
505    my $expiration;
506    if ($expires eq '0' or $expires eq 'session') {
507        $expiration = '';
508    } elsif ($expires =~ /now/i) {    #FIXME: Perhaps never used.
509        ## 10 years ago
510        $expiration = '-10y';
511    } else {
512        $expiration = '+' . $expires . 'm';
513    }
514
515    my $cookie = CGI::Cookie->new(
516        -name     => 'sympa_session',
517        -domain   => (($dom eq 'localhost') ? '' : $dom),
518        -path     => '/',
519        -secure   => $use_ssl,
520        -httponly => 1,
521        -value    => $self->{id_session},
522        ($expiration ? (-expires => $expiration) : ()),
523    );
524
525    # Send cookie to the client.
526    printf "Set-Cookie: %s\n", $cookie->as_string;
527}
528
529# Build an HTTP cookie value to be sent to a SOAP client
530sub soap_cookie2 {
531    my ($session_id, $http_domain, $expire) = @_;
532    my $cookie;
533
534    ## With set-cookie2 max-age of 0 means removing the cookie
535    ## Maximum cookie lifetime is the session
536    $expire ||= 600;    ## 10 minutes
537
538    if ($http_domain eq 'localhost') {
539        $cookie = CGI::Cookie->new(
540            -name  => 'sympa_session',
541            -value => $session_id,
542            -path  => '/',
543        );
544        $cookie->max_age(time + $expire);    # needs CGI >= 3.51.
545    } else {
546        $cookie = CGI::Cookie->new(
547            -name   => 'sympa_session',
548            -value  => $session_id,
549            -domain => $http_domain,
550            -path   => '/',
551        );
552        $cookie->max_age(time + $expire);    # needs CGI >= 3.51.
553    }
554
555    ## Return the cookie value
556    return $cookie->as_string;
557}
558
559# Moved to Sympa::Tools::Password::get_random().
560#sub get_random;
561
562## Return the session object content, as a hashref
563sub as_hashref {
564    my $self = shift;
565    my $data;
566
567    foreach my $key (keys %{$self}) {
568        $data->{$key} = $self->{$key};
569    }
570
571    return $data;
572}
573
574## Return 1 if the Session object corresponds to an anonymous session.
575sub is_anonymous {
576    my $self = shift;
577    if ($self->{'email'} eq 'nobody' || $self->{'email'} eq '') {
578        return 1;
579    } else {
580        return 0;
581    }
582}
583
584## Generate cookie from session ID.
585# No longer used.
586#sub encrypt_session_id;
587
588## Get session ID from cookie.
589# No longer used
590#sub decrypt_session_id;
591
592## Generic subroutine to set a cookie
593# DEPRECATED: No longer used.  Use CGI::Cookie::new().
594# Old name: cookielib::generic_set_cookie()
595#sub generic_set_cookie(
596#    name=>NAME, value=>VALUE, expires=>EXPIRES, domain=>DOMAIN, path=>PATH);
597
598# Sets an HTTP cookie to be sent to a SOAP client
599# DEPRECATED: Use Sympa::WWW::Session::soap_cookie2().
600#sub set_cookie_soap($session_id, $http_domain, $expire);
601
602## returns Message Authentication Check code
603# Old name: cookielib::get_mac(), Sympa::CookieLib::get_mac().
604# DEPRECATED: No longer used.
605#sub _get_mac;
606
607# Old name:
608# cookielib::set_cookie_extern(), Sympa::CookieLib::set_cookie_extern().
609# DEPRECATED: No longer used.
610#sub set_cookie_extern;
611
612###############################
613# Subroutines to read cookies #
614###############################
615
616## Generic subroutine to get a cookie value
617# Old name:
618# cookielib::generic_get_cookie(), Sympa::CookieLib::generic_get_cookie().
619sub _generic_get_cookie {
620    my $http_cookie = shift;
621    my $cookie_name = shift;
622
623    if ($http_cookie and $http_cookie =~ /\S+/g) {
624        my %cookies = CGI::Cookie->parse($http_cookie);
625        foreach my $cookie (values %cookies) {
626            next unless $cookie->name eq $cookie_name;
627            return ($cookie->value);
628        }
629    }
630    return undef;
631}
632
633## Returns user information extracted from the cookie
634# DEPRECATED: No longer used.
635# Old name: cookielib::check_cookie().
636#sub check_cookie(($http_cookie, $secret);
637
638# Old name:
639# cookielib::check_cookie_extern(), Sympa::CookieLib::check_cookie_extern().
640# DEPRECATED: No longer used.
641#sub check_cookie_extern;
642
643# input user agent string and IP. return 1 if suspected to be a crawler.
644# initial version based on rawlers_dtection.conf file only
645# later : use Session table to identify those who create a lot of sessions
646#FIXME: Robot context is ignored.
647sub _is_a_crawler {
648    my $robot = shift;
649
650    my $ua = $ENV{'HTTP_USER_AGENT'};
651    return undef unless defined $ua;
652    return $Conf::Conf{'crawlers_detection'}{'user_agent_string'}{$ua};
653}
654
655sub confirm_action {
656    my $self     = shift;
657    my $action   = shift;
658    my $response = shift || '';
659    my %opts     = @_;
660
661    if ($response eq 'init') {
662        # Check if action in session matches current action.
663        unless ($self->{confirm_action}
664            and $self->{confirm_action} eq $action) {
665            delete @{$self}{qw(confirm_action confirm_id previous_action)};
666        }
667        return;
668    }
669
670    my $id = Digest::MD5::md5_hex($opts{arg} || '');
671    my $default_home = Conf::get_robot_conf($self->{robot}, 'default_home');
672    unless ($response
673        and $self->{confirm_action}
674        and $self->{confirm_action} eq $action
675        and $self->{confirm_id}
676        and $self->{confirm_id} eq $id) {
677        # Not yet confirmed / dismissed: Save parameters in session.
678        @{$self}{qw(confirm_action confirm_id previous_action)} =
679            ($action, $id, ($opts{previous_action} || $default_home));
680        return 'confirm_action';
681    } elsif ($response eq 'confirm') {
682        # Action is confirmed: Clear parameters in session.
683        delete @{$self}{qw(confirm_action confirm_id previous_action)};
684        return 1;
685    } else {
686        # Action is dismissed: Clear parameters in session then returns name
687        # of previous action.
688        my $previous_action = $self->{previous_action} || $default_home;
689        delete @{$self}{qw(confirm_action confirm_id previous_action)};
690        return $previous_action;
691    }
692}
693
6941;
695__END__
696
697=encoding utf-8
698
699=head1 NAME
700
701Sympa::WWW::Session - Web session
702
703=head1 SYNOPSIS
704
705  use Sympa::WWW::Session;
706
707  my $session = Sympa::WWW::Session->new($robot,
708      {cookie => Sympa::WWW::Session::get_session_cookie($ENV{'HTTP_COOKIE'})}
709  );
710  $session->renew();
711  $session->store();
712
713=head2 Confirmation
714
715  $session->confirm_action($action, 'init');
716
717  sub do_myaction {
718
719      # Validate arguments...
720
721      $param->{arg} = $arg;
722      my $next_action = $session->confirm_action($action, $response,
723          $arg, $previous_action);
724      return $next_action unless $next_action eq '1';
725
726      # Process action...
727
728  }
729
730=head1 DESCRIPTION
731
732L<Sympa::WWW::Session> provides web session for Sympa web interface.
733HTTP cookie is required to determine users.
734Session store is used to keep users' personal data.
735
736=head2 Methods
737
738=over
739
740=item new ( $robot, { [ cookie =E<gt> $cookie ], ... } )
741
742I<Constructor>.
743Creates new instance and loads user data from session store.
744
745Parameters:
746
747=over
748
749=item $robot
750
751Context of the session.
752
753=item { cookie =E<gt> $cookie }
754
755HTTP cookie.
756
757=back
758
759Returns:
760
761A new instance.
762
763=item as_hashref ( )
764
765I<Instance method>.
766Casts the instance to hashref.
767
768Parameters:
769
770None.
771
772Returns:
773
774A hashref including attributes of instance (see L</Attributes>).
775
776=item confirm_action ( $action, $response, [ arg =E<gt> $arg, ]
777[ previous_action =E<gt> $previous_action ] )
778
779I<Instance method>.
780Check if action has been confirmed.
781
782Confirmation follows two steps:
783
784=over
785
786=item 1.
787
788The method is called with no (undefined) response.
789The action, hash of argument and previous_action are stored into
790session store.
791And then this method returns C<'confirm_action'>.
792
793=item 2.
794
795The method is called with C<'confirm'> or other true value as response.
796I<If> action and hash of argument match with those in session store, and:
797
798=over
799
800=item *
801
802If C<'confirm'> is given, returns C<1>.
803
804=item *
805
806If other true value is given, returns previous action stored in
807session store (previous_action given in argument is ignored).
808
809=back
810
811In both cases session store is cleared.
812
813=back
814
815Anytime when the action submitted by user is determined,
816This method may be called with response as C<'init'>.
817In this case, if action doesn't match with that in session store,
818session store will be cleared.
819
820Parameters:
821
822=over
823
824=item $action
825
826Action to be checked.
827
828=item $response
829
830Response from user:
831C<'init'>, false value (not yet checked), C<'confirm'> and others (cancelled).
832This may typically be given by user using C<response_action> parameter.
833
834=item arg =E<gt> $arg
835
836Argument(s) of action.
837
838=item previous_action => $previous_action
839
840The action users will be redirected when action is confirmed.
841This may typically given by user using C<previous_action> parameter.
842
843=back
844
845=item is_anonymous ( )
846
847I<Instance method>.
848TBD.
849
850=item renew ( )
851
852I<Instance method>.
853Renews the session.
854Updates internal session ID and HTTP cookie.
855
856=item store ( )
857
858I<Instance method>.
859Stores session into session store.
860
861=back
862
863=head2 Functions
864
865=over
866
867=item check_cookie_extern ( )
868
869I<Function>.
870Deprecated.
871
872=item decrypt_session_id ( )
873
874I<Function>.
875Deprecated.
876
877=item encrypt_session_id ( )
878
879I<Function>.
880Deprecated.
881
882=item list_sessions ( )
883
884I<Function>.
885TBD.
886
887=item purge_old_sessions ( )
888
889I<Function>.
890Deprecated.
891
892=item set_cookie ( $cookie_domain, $expires, [ $use_ssl ] )
893
894I<Instance method>.
895TBD.
896
897=item set_cookie_extern ( $cookie_domain, [ $use_ssl ] )
898
899I<Instance method>.
900Deprecated.
901
902=back
903
904=head2 Attributes
905
906TBD.
907
908=head1 SEE ALSO
909
910L<Sympa::DatabaseManager>.
911
912=head1 HISTORY
913
914L<SympaSession> appeared on Sympa 5.4a3.
915
916It was renamed to L<Sympa::Session> on Sympa 6.2a.41,
917then L<Sympa::WWW::Session> on Sympa 6.2.26.
918
919L</"confirm_action"> method was added on Sympa 6.2.17.
920
921=cut
922
923