File Coverage

blib/lib/HTTP/Session2/ClientStore2.pm
Criterion Covered Total %
statement 106 111 95.5
branch 11 18 61.1
condition 3 3 100.0
subroutine 22 23 95.6
pod 1 7 14.2
total 143 162 88.2


line stmt bran cond sub pod time code
1             package HTTP::Session2::ClientStore2;
2 4     4   133028 use strict;
  4         11  
  4         153  
3 4     4   19 use warnings;
  4         8  
  4         119  
4 4     4   3502 use utf8;
  4         35  
  4         25  
5 4     4   219 use 5.008_001;
  4         16  
  4         165  
6              
7 4     4   3794 use Cookie::Baker ();
  4         17353  
  4         106  
8 4     4   16170 use Storable ();
  4         20523  
  4         113  
9 4     4   4002 use MIME::Base64 ();
  4         12247  
  4         112  
10 4     4   4181 use Digest::HMAC ();
  4         2714  
  4         91  
11 4     4   2512 use HTTP::Session2::Expired;
  4         16  
  4         131  
12 4     4   2619 use HTTP::Session2::Random;
  4         13  
  4         147  
13 4     4   4983 use Data::MessagePack;
  4         7499  
  4         141  
14 4     4   3015 use Crypt::CBC;
  4         17904  
  4         137  
15              
16 4     4   39 use Mouse;
  4         9  
  4         45  
17              
18             extends 'HTTP::Session2::Base';
19              
20             our $MESSAGE_PACK = Data::MessagePack->new();
21              
22             has serializer => (
23             is => 'ro',
24             default => sub {
25             sub {
26             $MESSAGE_PACK->pack($_[0]);
27             }
28             },
29             );
30              
31             has deserializer => (
32             is => 'ro',
33             default => sub {
34             sub {
35             $MESSAGE_PACK->unpack($_[0])
36             }
37             },
38             );
39              
40             has cipher => (
41             is => 'ro',
42             isa => 'Crypt::CBC',
43             required => 1,
44             );
45              
46             has ignore_old => (
47             is => 'ro',
48             );
49              
50 4     4   2384 no Mouse;
  4         10  
  4         21  
51              
52             # HMAC timing attack
53             sub _compare {
54 3     3   91 my ( $s1, $s2 ) = @_;
55              
56 3 50       12 return unless defined $s2;
57 3 50       15 return if length $s1 != length $s2;
58 3         7 my $r = 0;
59 3         13 for my $i ( 0 .. length($s1) - 1 ) {
60 240         380 $r |= ord( substr $s1, $i ) ^ ord( substr $s2, $i );
61             }
62              
63 3         17 return $r == 0;
64             }
65              
66             sub sig {
67 10     10 0 24 my($self, $b64) = @_;
68 10 50       68 $self->secret or die "Missing secret. ABORT";
69 10         81 Digest::HMAC::hmac_hex($b64, $self->secret, $self->hmac_function);
70             }
71              
72             sub load_session {
73 11     11 0 21 my $self = shift;
74              
75             # Load from cookie.
76 11         87 my $cookies = Cookie::Baker::crush_cookie($self->env->{HTTP_COOKIE});
77 11         564 my $session_cookie = $cookies->{$self->session_cookie->{name}};
78 11 100       71 if (defined $session_cookie) {
79 7         11 my $textified = $session_cookie;
80 7         46 my $encrypted = MIME::Base64::decode_base64url($textified);
81 7         98 my $serialized_and_sig = eval { $self->cipher->decrypt($encrypted) };
  7         44  
82 7 100       1994 if ($@) {
83 4         88 warn $@;
84 4         25 return;
85             }
86 3         16 my ($sig, $serialized) = @{$self->deserializer->($serialized_and_sig)};
  3         18  
87 3 50       17 _compare($self->sig($serialized), $sig) or do {
88 0         0 return;
89             };
90 3         9 my ($time, $id, $data) = @{$self->deserializer->($serialized)};
  3         27  
91              
92 3 50       19 if (defined $self->ignore_old) {
93 0 0       0 if ($time < $self->ignore_old()) {
94 0         0 return;
95             }
96             }
97              
98 3         8 $self->{id} = $id;
99 3         10 $self->{_data} = $data;
100 3         22 return 1;
101             }
102             }
103              
104             sub create_session {
105 6     6 0 11 my $self = shift;
106              
107 6         31 $self->{id} = HTTP::Session2::Random::generate_session_id();
108 6         30 $self->{_data} = +{};
109             }
110              
111             sub regenerate_id {
112 1     1 0 577 my ($self) = @_;
113              
114             # Load original session first.
115 1         5 $self->load_session();
116              
117             # Create new session.
118 1         5 $self->{id} = HTTP::Session2::Random::generate_session_id();
119 1         5 $self->is_dirty(1);
120 1         5 $self->necessary_to_send(1);
121             }
122              
123             sub xsrf_token {
124 0     0 1 0 my $self = shift;
125 0         0 return $self->id;
126             }
127              
128             sub expire {
129 1     1 0 592 my $self = shift;
130              
131             # Load original session first.
132 1         4 $self->load_session();
133              
134             # Rebless to expired object.
135 1         5 bless $self, 'HTTP::Session2::Expired';
136              
137 1         3 return;
138             }
139              
140             sub finalize {
141 10     10 0 19 my ($self) = @_;
142              
143 10 100 100     147 return () unless $self->necessary_to_send || $self->is_dirty;
144              
145 7         14 my @cookies;
146              
147             # Finalize session cookie
148             {
149 7         11 my %cookie = %{$self->session_cookie};
  7         12  
  7         76  
150 7         22 my $name = delete $cookie{name};
151 7         45 my $value = $self->_serialize($self->id, $self->_data);
152 7         58 push @cookies, $name => +{
153             %cookie,
154             value => $value,
155             };
156             }
157              
158             # Finalize XSRF cookie
159             {
160 7         18 my %cookie = %{$self->xsrf_cookie};
  7         11  
  7         124  
161 7         24 my $name = delete $cookie{name};
162 7         68 push @cookies, $name => +{
163             %cookie,
164             value => $self->id,
165             };
166             }
167              
168 7         37 return @cookies;
169             }
170              
171             sub _serialize {
172 7     7   15 my ($self, $id, $data) = @_;
173              
174 7         48 my $serialized = $self->serializer->([time(), $id, $data]);
175 7         28 my $sig = $self->sig($serialized);
176 7         285 my $joined = $self->serializer->([$sig, $serialized]);
177 7         47 my $encrypted = $self->cipher->encrypt($joined);
178 7         9555 $encrypted = MIME::Base64::encode_base64url($encrypted);
179 7         115 return $encrypted;
180             }
181              
182             1;
183             __END__