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