File Coverage

blib/lib/Mojolicious/Plugin/Authentication.pm
Criterion Covered Total %
statement 131 146 89.7
branch 35 44 79.5
condition 18 27 66.6
subroutine 25 29 86.2
pod 1 7 14.2
total 210 253 83.0


line stmt bran cond sub pod time code
1 3     3   1807 use warnings;
  3         5  
  3         74  
2 3     3   13 use strict;
  3         5  
  3         94  
3              
4             package Mojolicious::Plugin::Authentication;
5              
6             our $VERSION = '1.38';
7              
8 3     3   12 use Mojo::Base 'Mojolicious::Plugin';
  3         5  
  3         18  
9 3     3   517 use Mojo::Promise;
  3         5  
  3         30  
10              
11             sub register {
12 7     7 1 22064 my ($self, $app, $args) = @_;
13              
14 7 50       15 $args = { %{ $args || {} } }; # copy as mutating
  7         39  
15              
16 7         39 for my $cb_name (qw(load_user validate_user)) {
17 14         34 my $p_name = $cb_name."_p";
18             die __PACKAGE__, ": missing '$cb_name' subroutine ref in parameters\n"
19 14 50       64 unless grep ref eq 'CODE', @$args{$cb_name, $p_name};
20             $args->{$p_name} = sub {
21 7     7   14 my @r = eval { $args->{$cb_name}->(@_) };
  7         40  
22 7 50       93 $@ ? Mojo::Promise->reject($@) : Mojo::Promise->resolve(@r);
23 14 100       56 } if !$args->{$p_name};
24             }
25              
26 7 50       28 if (defined $args->{lazy}) {
27 0         0 warn __PACKAGE__,
28             ": the 'lazy' option is deprecated, ",
29             "use 'autoload_user' instead\n";
30              
31 0         0 $args->{autoload_user} = delete $args->{lazy};
32             }
33              
34 7   50     20 my $autoload_user = $args->{autoload_user} // 0;
35 7   50     38 my $session_key = $args->{session_key} || 'auth_data';
36 7   50     37 my $our_stash_key = $args->{stash_key} || '__authentication__';
37 7   50     29 my $current_user_fn = $args->{current_user_fn} || 'current_user';
38 7         14 my $load_user_cb = $args->{load_user};
39 7         10 my $validate_user_cb = $args->{validate_user};
40 7         15 my $load_user_cb_p = $args->{load_user_p};
41 7         11 my $validate_user_cb_p= $args->{validate_user_p};
42              
43             my $fail_render = ref $args->{fail_render} eq 'CODE'
44 7 50   4   38 ? $args->{fail_render} : sub { $args->{fail_render} };
  4         21  
45              
46 7         23 my $user_loader_sub = user_loader_closure(
47             $our_stash_key, $session_key, $load_user_cb,
48             );
49 7         21 my $user_loader_sub_p = user_loader_closure_p(
50             $our_stash_key, $session_key, $load_user_cb_p,
51             );
52              
53 7         36 my $current_user = user_stash_extractor_closure(
54             $our_stash_key, $user_loader_sub,
55             );
56 7         24 my $current_user_p = user_stash_extractor_closure_p(
57             $our_stash_key, $user_loader_sub_p,
58             );
59              
60 7         24 $app->helper(authenticate => authenticate_closure(
61             $our_stash_key, $session_key, $validate_user_cb, $current_user,
62             ));
63 7         595 $app->helper(authenticate_p => authenticate_closure_p(
64             $our_stash_key, $session_key, $validate_user_cb_p, $current_user_p,
65             ));
66              
67 7 100       327 $app->hook(before_dispatch => $user_loader_sub_p) if $autoload_user;
68              
69             $app->routes->add_condition(authenticated => sub {
70 8     8   32100 my ($r, $c, $captures, $required) = @_;
71 8   66     47 my $res = (!$required or $c->is_user_authenticated);
72              
73 8 100       22 unless ($res) {
74 4         15 my $fail = $fail_render->(@_);
75 4 100       14 $c->render(%{$fail}) if $fail;
  1         7  
76             }
77 8         485 return $res;
78 7         84 });
79              
80             $app->routes->add_condition(signed => sub {
81 2     2   265 my ($r, $c, $captures, $required) = @_;
82 2   66     8 return (!$required or $c->signature_exists);
83 7         186 });
84              
85             # deprecation handling
86             $app->helper(user_exists => sub {
87 0     0   0 warn __PACKAGE__,
88             ": the 'user_exists' helper is deprecated, ",
89             "use 'is_user_authenticated' instead\n";
90 0         0 return shift->is_user_authenticated(@_);
91 7         91 });
92              
93             $app->helper(user => sub {
94 0     0   0 warn __PACKAGE__,
95             ": the 'user' helper is deprecated, ",
96             "use '$current_user_fn' instead\n";
97 0         0 return shift->$current_user_fn(@_);
98 7         338 });
99              
100             $app->helper(reload_user => sub {
101 0     0   0 my $c = shift;
102             # Clear stash to force a reload of the user object
103 0         0 delete $c->stash->{$our_stash_key};
104 0         0 return $current_user->($c);
105 7         347 });
106             $app->helper(reload_user_p => sub {
107 0     0   0 my $c = shift;
108             # Clear stash to force a reload of the user object
109 0         0 delete $c->stash->{$our_stash_key};
110 0         0 return $current_user_p->($c);
111 7         318 });
112              
113             $app->helper(signature_exists => sub {
114 5     5   36851 my $c = shift;
115 5         21 return !!$c->session($session_key);
116 7         317 });
117              
118             $app->helper(is_user_authenticated => sub {
119 16     16   27500 my $c = shift;
120 16         48 return defined $current_user->($c);
121 7         307 });
122             $app->helper(is_user_authenticated_p => sub {
123 1     1   7136 my $c = shift;
124             $current_user_p->($c)->then(sub {
125 1         88 return defined $_[0];
126 1         3 });
127 7         312 });
128              
129 7         291 $app->helper($current_user_fn => $current_user);
130 7         292 $app->helper($current_user_fn."_p" => $current_user_p);
131              
132             $app->helper(logout => sub {
133 2     2   9813 my $c = shift;
134 2         11 delete $c->stash->{$our_stash_key};
135 2         17 delete $c->session->{$session_key};
136 2         378 return 1;
137 7         347 });
138             }
139              
140             # Unconditionally load the user based on uid in session
141             sub user_loader_closure {
142 7     7 0 17 my ($our_stash_key, $session_key, $load_user_cb) = @_;
143             sub {
144 21     21   35 my $c = shift;
145 21         63 my $uid = $c->session($session_key);
146 21 100       1973 return if !defined $uid;
147 12         39 my $user = $load_user_cb->($c, $uid);
148 12 50       112 if ($user) {
149 12         43 $c->stash($our_stash_key => { user => $user });
150             }
151             else {
152             # cache result that user does not exist
153 0         0 $c->stash($our_stash_key => { no_user => 1 });
154             }
155 7         25 };
156             }
157             sub user_loader_closure_p {
158 7     7 0 20 my ($our_stash_key, $session_key, $load_user_cb_p) = @_;
159             sub {
160 24     24   192796 my $c = shift;
161 24         107 my $uid = $c->session($session_key);
162 24 100       5242 return Mojo::Promise->resolve if !defined $uid;
163             $load_user_cb_p->($c, $uid)->then(sub {
164 10         7447 my $user = $_[0];
165 10 50       27 if ($user) {
166 10         43 $c->stash($our_stash_key => { user => $user });
167             }
168             else {
169             # cache result that user does not exist
170 0         0 $c->stash($our_stash_key => { no_user => 1 });
171             }
172 10         53 });
173 7         25 };
174             }
175              
176             # Fetch the current user object from the stash - loading it if
177             # not already loaded
178             sub user_stash_extractor_closure {
179 7     7 0 17 my ($our_stash_key, $user_loader_sub) = @_;
180             sub {
181 24     24   1419 my ($c, $user) = @_;
182             # Allow setting the current_user
183 24 100       61 if ( defined $user ) {
184 1         6 $c->stash($our_stash_key => { user => $user });
185 1         15 return;
186             }
187 23         59 my $stash = $c->stash($our_stash_key);
188             $user_loader_sub->($c)
189 23 100 66     326 unless $stash->{no_user} or defined $stash->{user};
190 23         192 $stash = $c->stash($our_stash_key);
191 23         197 return $stash->{user};
192 7         22 };
193             }
194             sub user_stash_extractor_closure_p {
195 7     7 0 13 my ($our_stash_key, $user_loader_sub_p) = @_;
196             sub {
197 6     6   1779 my ($c, $user) = @_;
198             # Allow setting the current_user
199 6 100       18 if ( defined $user ) {
200             return Mojo::Promise->resolve->then(sub {
201 1         80 $c->stash($our_stash_key => { user => $user });
202 1         3 });
203             }
204 5         12 my $stash = $c->stash($our_stash_key);
205             my $promise = ($stash->{no_user} or defined $stash->{user})
206 5 100 66     63 ? Mojo::Promise->resolve
207             : $user_loader_sub_p->($c);
208             $promise->then(sub {
209 5         1626 $stash = $c->stash($our_stash_key);
210 5         39 return $stash->{user};
211 5         595 });
212 7         25 };
213             }
214              
215             sub authenticate_closure {
216 7     7 0 19 my ($our_stash_key, $session_key, $validate_user_cb, $current_user) = @_;
217             sub {
218 7     7   74227 my ($c, $user, $pass, $extradata) = @_;
219             # if extradata contains "auto_validate", assume the passed username
220             # is in fact valid, and auto_validate contains the uid; used for
221             # OAuth and other stuff that does not work with usernames and
222             # passwords; use this with extreme care if you must
223 7   100     46 $extradata ||= {};
224             my $uid = $extradata->{auto_validate} //
225 7   100     48 $validate_user_cb->($c, $user, $pass, $extradata);
226 7 100       96 return undef if !defined $uid;
227 5         24 $c->session($session_key => $uid);
228             # Clear stash to force reload of any already loaded user object
229 5         583 delete $c->stash->{$our_stash_key};
230 5 50       39 return 1 if defined $current_user->($c);
231 0         0 return undef;
232 7         52 };
233             }
234             sub authenticate_closure_p {
235 7     7 0 21 my ($our_stash_key, $session_key, $validate_user_cb_p, $current_user_p) = @_;
236             sub {
237 3     3   22156 my ($c, $user, $pass, $extradata) = @_;
238 3   50     19 $extradata ||= {};
239             my $promise = defined($extradata->{auto_validate})
240             ? Mojo::Promise->resolve($extradata->{auto_validate})
241 3 50       17 : $validate_user_cb_p->($c, $user, $pass, $extradata);
242             $promise->then(sub {
243 3         6080 my ($uid) = @_;
244 3 100       15 return undef if !defined $uid;
245 2         9 $c->session($session_key => $uid);
246             # Clear stash to force reload of any already loaded user object
247 2         188 delete $c->stash->{$our_stash_key};
248 2         55 $current_user_p->($c);
249             })->then(sub {
250 3 100       388 defined $_[0] ? 1 : undef;
251 3         400 });
252 7         40 };
253             }
254              
255             1;
256              
257             __END__