1 package Koha::CookieManager;
3 # Copyright 2022 Rijksmuseum, Koha Development Team
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
22 # use Data::Dumper qw( Dumper );
23 # use List::MoreUtils qw( uniq );
27 use constant DENY_LIST_VAR => 'do_not_remove_cookie';
33 Koha::CookieManager - Object for unified handling of cookies in Koha
37 use Koha::CookieManager;
38 my $mgr = Koha::CookieManager->new;
41 $cookie_list = $mgr->replace_in_list( [ $cookie1, $cookie2_old ], $cookie2_new );
43 # Clear cookies (governed by deny list entries in koha-conf)
44 $cookie_list = $mgr->clear_unless( $cookie1, $cookie2, $cookie3_name );
48 The current object allows you to clear cookies in a list based on the deny list
49 in koha-conf.xml. It also offers a method to replace the old version of a cookie
52 It could be extended by (gradually) routing cookie creation through it in order
53 to consistently fill cookie parameters like httponly, secure and samesite flag,
54 etc. And could serve to register all our cookies in a central location.
60 my $mgr = Koha::CookieManager->new({}); # parameters for extensions
65 my ( $class, $params ) = @_;
66 my $self = bless $params//{}, $class;
67 my $denied = C4::Context->config(DENY_LIST_VAR) || []; # expecting scalar or arrayref
68 $denied = [ $denied ] if ref($denied) eq q{};
69 $self->{_remove_unless} = { map { $_ => 1 } @$denied };
70 $self->{_secure} = C4::Context->https_enabled;
76 $cookies = $self->clear_unless( $query->cookie, @$cookies );
78 Arguments: either cookie names or cookie objects (CGI::Cookie).
79 Note: in the example above $query->cookie is a list of cookie names as returned
82 Returns an arrayref of cookie objects: empty, expired cookies for those passed
83 by name or objects that are not on the deny list, together with the remaining
84 (untouched) cookie objects that are on the deny list.
89 my ( $self, @cookies ) = @_;
92 foreach my $c ( @cookies ) {
95 if( $type eq 'CGI::Cookie' ) {
97 } elsif( $type ) { # not expected: ignore
104 # Try stripping _\d+ from name for cookiea like catalogue_editor_123
105 my $stripped_name = $name;
106 $stripped_name =~ s/_\d+$/_/;
108 if( !$self->{_remove_unless}->{$stripped_name} && !$self->{_remove_unless}->{$name} ) {
109 next if $seen->{$name};
110 push @rv, CGI::Cookie->new(
111 # -expires explicitly omitted to create shortlived 'session' cookie
112 # -HttpOnly explicitly set to 0: not really needed here for the
113 # cleared httponly cookies, while the js cookies should be 0
114 -name => $name, -value => q{}, -HttpOnly => 0,
115 $self->{_secure} ? ( -secure => 1 ) : (),
117 $seen->{$name} = 1; # prevent duplicates
118 } elsif( $type eq 'CGI::Cookie' ) { # keep the last occurrence
119 @rv = @{ $self->replace_in_list( \@rv, $c ) };
125 =head2 replace_in_list
127 $list2 = $mgr->replace_in_list( $list1, $cookie );
129 Add $cookie to $list1, removing older occurrences in list1.
130 $list1 is a list of CGI::Cookie objects.
131 $cookie must be a CGI::Cookie object; if it is not, only
132 cookie objects in list1 are returned (filtering list1).
134 Returns an arrayref of CGI::Cookie objects.
138 sub replace_in_list {
139 my ( $self, $list, $cookie ) = @_;
140 my $name = ref($cookie) eq 'CGI::Cookie' ? $cookie->name : q{};
143 foreach my $c ( @$list ) {
144 next if ref($c) ne 'CGI::Cookie';
145 push @result, $c if !$name or $c->name ne $name;
147 push @result, $cookie if $name;
151 =head1 INTERNAL ROUTINES