File Coverage

blib/lib/Mojolicious/Sessions/ThreeS.pm
Criterion Covered Total %
statement 76 87 87.3
branch 29 42 69.0
condition 11 21 52.3
subroutine 15 15 100.0
pod 9 9 100.0
total 140 174 80.4


line stmt bran cond sub pod time code
1             package Mojolicious::Sessions::ThreeS;
2             $Mojolicious::Sessions::ThreeS::VERSION = '0.004';
3 2     2   6 use strict;
  2         2  
  2         46  
4 2     2   6 use warnings;
  2         2  
  2         37  
5              
6 2     2   23 use Carp;
  2         3  
  2         114  
7 2     2   7 use Mojo::Base 'Mojolicious::Sessions';
  2         2  
  2         9  
8              
9 2     2   630 use Mojolicious::Sessions::ThreeS::State::Cookie;
  2         2  
  2         18  
10 2     2   852 use Mojolicious::Sessions::ThreeS::SidGen::Simple;
  2         4  
  2         16  
11              
12             =head1 NAME
13              
14             Mojolicious::Sessions:ThreeS - A Mojolicious Sessions manager that supports controlling Storage, State and Sid generation.
15              
16             =head1 SYNOPSIS
17              
18             You can use this directly when you build your mojolicious App:
19              
20             package My::App;
21             use Mojo::Base 'Mojolicious';
22              
23             use Mojolicious::Sessions:ThreeS;
24              
25             sub startup{
26             my ($app) = @_;
27             ...
28             $app->sessions( Mojolicious::Sessions:ThreeS->new({ storage => ... , state => ... , sidgen => ... } ) );
29             ...
30             }
31              
32             Or as a plugin, with exactly the same arguments. See L.
33              
34             =cut
35              
36             has 'storage';
37             has 'state' => sub{
38             return Mojolicious::Sessions::ThreeS::State::Cookie->new();
39             };
40             has 'sidgen' => sub{
41             return Mojolicious::Sessions::ThreeS::SidGen::Simple->new();
42             };
43              
44             =head2 new
45              
46             Builds an instance of this given the following properties (all optional):
47              
48             =over
49              
50             =item storage
51              
52             An instance of a subclass of L. Defaults to C
53              
54             When not given, this will consider itself inactive and fallback to the default L behaviour.
55              
56             =item state
57              
58             An instance of a subclass of L. Defaults to
59             an instance of L.
60              
61             =item sidgen
62              
63             An Session ID generator, instance of L. Defaults
64             to an instance of L.
65              
66             =back
67              
68             =cut
69              
70             sub new{
71 3     3 1 4 my ( $class ) = ( shift );
72              
73 3         11 my $self = $class->SUPER::new( @_ );
74              
75 3         23 return $self;
76             }
77              
78             =head2 was_set
79              
80             This was set with explicit store, storage and sid generator.
81              
82             Usage:
83              
84             if ( $this->was_set() ){
85             ...
86             }
87              
88             =cut
89              
90             sub was_set{
91 80     80 1 70 my ($self) = @_;
92 80   66     118 return $self->storage() && $self->state();
93             }
94              
95             =head2 cookie_domain
96              
97             From L. Delegate to the underlying Cookie
98             based state. Use this only if you know the state object supports cookies.
99              
100             =cut
101              
102             sub cookie_domain{
103 2     2 1 184 my ($self, @rest ) = @_;
104 2 50       6 unless( $self->was_set() ){ return $self->SUPER::cookie_domain( @rest ); }
  2         21  
105 0         0 return $self->state->cookie_domain( @rest );
106             }
107              
108              
109             =head2 cookie_name
110              
111             From L. Delegate to the underlying Cookie
112             based state. Use this only if you know the state object supports cookies.
113              
114             =cut
115              
116             sub cookie_name{
117 8     8 1 138 my ($self, @rest ) = @_;
118 8 100       14 unless( $self->was_set() ){ return $self->SUPER::cookie_name( @rest ); }
  6         35  
119 2         35 return $self->state->cookie_name( @rest );
120             }
121              
122             =head2 cookie_path
123              
124             From L. Delegate to the underlying Cookie
125             based state. Use this only if you know the state object supports cookies.
126              
127             =cut
128              
129             sub cookie_path{
130 4     4 1 35 my ($self, @rest ) = @_;
131 4 100       8 unless( $self->was_set() ){ return $self->SUPER::cookie_path( @rest ); }
  2         20  
132 2         20 return $self->state->cookie_path( @rest );
133             }
134              
135             =head2 secure
136              
137             From L. Delegate to the underlying Cookie
138             based state. Use this only if you know the state object supports cookies.
139              
140             =cut
141              
142             sub secure{
143 2     2 1 14 my ($self, @rest ) = @_;
144 2 50       3 unless( $self->was_set() ){ return $self->SUPER::secure( @rest ); }
  2         13  
145 0         0 return $self->state->secure( @rest );
146             }
147              
148             =head2 load
149              
150             Implements load from L
151              
152             =cut
153              
154             sub load{
155 39     39 1 132489 my ($self, $controller) = @_;
156              
157 39 100       71 unless( $self->was_set() ){ return $self->SUPER::load( $controller ); }
  3         28  
158              
159             # Stuff was set, we need to use it.
160 36         317 my $session_id = $self->state()->get_session_id( $controller );
161 36 100       2433 unless( $session_id ){
162 26         34 return;
163             }
164              
165 10         21 my $session = $self->storage->get_session( $session_id );
166 10 50       84 unless( $session ){
167 0         0 return;
168             }
169              
170             # We just want to set the session in the stash, as required
171             # by Mojolicious::Controller::session
172              
173             # Expiration management.
174             # This is the 'Policy' setting.
175 10 50       37 my $expiration = defined( $session->{expiration} ) ? $session->{expiration} : $self->default_expiration();
176              
177             # This is the actual date at which the session should expire.
178 10         40 my $expires = delete $session->{expires};
179              
180 10 50 33     53 if( $expiration &&
181             ! $expires ){
182             # No expiry time, but there should be one. Delete the session_id and return
183 0         0 $self->storage()->remove_session_id( $session_id );
184 0         0 return;
185             }
186 10 50 33     35 if( defined $expires && $expires <= time() ){
187             # Session as expired.
188 0         0 $self->storage()->remove_session_id( $session_id );
189 0         0 return;
190             }
191              
192             # If the session is empty, we dont want it to be marked active.
193 10 50       101 return unless $controller->stash()->{'mojo.active_session'} = scalar( keys %$session );
194             # Note that mojo.active_session acts both way. As a number, it indicates that
195             # the stored session was not empty at some point. As a key that just 'exists',
196             # it prevents subsequent loading of the session in the second call
197             # to $c->session();
198              
199             # This is the sessing of the session hash in the stash
200 10         77 $controller->stash()->{'mojo.session'} = $session;
201             # And we transfer the flash if anything has flashed something in some previous requests
202             # under the key 'new_flash'. See Mojolicious::Controller::flash
203 10 100       59 $session->{flash} = delete $session->{new_flash} if $session->{new_flash};
204 10         15 return;
205             }
206              
207             =head2 store
208              
209             Implements store from L
210              
211             =cut
212              
213             sub store{
214 25     25 1 6548 my ($self, $controller) = @_;
215              
216              
217 25 100       43 unless( $self->was_set() ){ return $self->SUPER::store( $controller ); }
  3         23  
218              
219             # Stuff was set, we need to use it.
220             # Grab the session from the stash and see if we should really save it.
221 22         202 my $stash = $controller->stash();
222 22         107 my $session = $stash->{'mojo.session'};
223              
224             # No session, no storing needed.
225 22 50       32 unless( $session ){ return ; }
  0         0  
226              
227 22 50 66     55 unless( keys %$session || $stash->{'mojo.active_session'} ){
228             # The session has never contained anything for the whole duration of this
229             # request. No need to store
230 8         19 return;
231             }
232              
233 14         16 my $old_flash = delete $session->{flash};
234              
235 14 50       24 if( $stash->{'mojo.static'} ){
236             # Mojo is serving a static resource (like a file).
237             # This is marked with mojo.static being set on the stash.
238             # Behave as if a new_flash was set against the session
239 0         0 $session->{new_flash} = \%{ $old_flash };
  0         0  
240             }
241              
242             # Clear the new_flash if it contains nothing
243 14 100       12 unless( keys %{ $session->{new_flash} || {} } ){
  14 100       55  
244 10         11 delete $session->{new_flash};
245             }
246              
247 14         31 my $session_id = $self->get_session_id( $session , $controller );
248              
249 14 50       35 if( defined( $session->{'mojox.sessions3s.old_id'} ) ){
250             # Session id has changed. Clear the old one.
251 0         0 $self->storage->remove_session_id( $session->{'mojox.session3s.old_id'} );
252             }
253              
254              
255 14 50       50 my $expiration = defined( $session->{expiration} ) ? $session->{expiration} : $self->default_expiration();
256 14         51 my $set_expires = delete $session->{expires};
257              
258 14 50 33     25 if( $expiration || $set_expires ){
259             # There is an expiration policy or expires was set explicitely.
260 14   66     42 $session->{expires} = $set_expires || time + $expiration ;
261             }
262              
263             # Do the standard thing. Store the session.
264 14         67 $self->storage->store_session( $session_id , $session );
265             # And then inject the session id as a client state.
266 14         1371 $self->state->set_session_id( $controller , $session_id , { expires => $session->{expires} } );
267             }
268              
269             =head2 get_session_id
270              
271             Get the current session ID or generate a fresh one and store it in the given session object,
272             using the given controller.
273              
274             Usage:
275              
276             my $session_id = $this->session_id( $session, $controller );
277              
278             =cut
279              
280             sub get_session_id{
281 18     18 1 28 my ($self, $session, $controller ) = @_;
282 18   66     47 return $session->{'mojox.sessions3s.id'} ||= $self->sidgen()->generate_sid( $controller );
283             }
284              
285              
286             1;
287