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 4     4   3896 use warnings;
  4         10  
  4         188  
2 4     4   22 use strict;
  4         11  
  4         218  
3              
4             package Mojolicious::Plugin::Authentication;
5              
6             our $VERSION = '1.37';
7              
8 4     4   593 use Mojo::Base 'Mojolicious::Plugin';
  4         213923  
  4         37  
9 4     4   2620 use Mojo::Promise;
  4         182978  
  4         49  
10              
11             sub register {
12 7     7 1 22613 my ($self, $app, $args) = @_;
13              
14 7 50       22 $args = { %{ $args || {} } }; # copy as mutating
  7         58  
15              
16 7         34 for my $cb_name (qw(load_user validate_user)) {
17 14         42 my $p_name = $cb_name."_p";
18             die __PACKAGE__, ": missing '$cb_name' subroutine ref in parameters\n"
19 14 50       91 unless grep ref eq 'CODE', @$args{$cb_name, $p_name};
20             $args->{$p_name} = sub {
21 7     7   26 my @r = eval { $args->{$cb_name}->(@_) };
  7         87  
22 7 50       161 $@ ? Mojo::Promise->reject($@) : Mojo::Promise->resolve(@r);
23 14 100       94 } if !$args->{$p_name};
24             }
25              
26 7 50       43 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     34 my $autoload_user = $args->{autoload_user} // 0;
35 7   50     53 my $session_key = $args->{session_key} || 'auth_data';
36 7   50     50 my $our_stash_key = $args->{stash_key} || '__authentication__';
37 7   50     40 my $current_user_fn = $args->{current_user_fn} || 'current_user';
38 7         24 my $load_user_cb = $args->{load_user};
39 7         20 my $validate_user_cb = $args->{validate_user};
40 7         17 my $load_user_cb_p = $args->{load_user_p};
41 7         22 my $validate_user_cb_p= $args->{validate_user_p};
42              
43             my $fail_render = ref $args->{fail_render} eq 'CODE'
44 7 50   7   55 ? $args->{fail_render} : sub { $args->{fail_render} };
  7         23  
45              
46 7         30 my $user_loader_sub = user_loader_closure(
47             $our_stash_key, $session_key, $load_user_cb,
48             );
49 7         31 my $user_loader_sub_p = user_loader_closure_p(
50             $our_stash_key, $session_key, $load_user_cb_p,
51             );
52              
53 7         63 my $current_user = user_stash_extractor_closure(
54             $our_stash_key, $user_loader_sub,
55             );
56 7         37 my $current_user_p = user_stash_extractor_closure_p(
57             $our_stash_key, $user_loader_sub_p,
58             );
59              
60 7         33 $app->helper(authenticate => authenticate_closure(
61             $our_stash_key, $session_key, $validate_user_cb, $current_user,
62             ));
63 7         927 $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       560 $app->hook(before_dispatch => $user_loader_sub_p) if $autoload_user;
68              
69             $app->routes->add_condition(authenticated => sub {
70 11     11   50075 my ($r, $c, $captures, $required) = @_;
71 11   66     87 my $res = (!$required or $c->is_user_authenticated);
72              
73 11 100       43 unless ($res) {
74 7         29 my $fail = $fail_render->(@_);
75 7 100       22 $c->render(%{$fail}) if $fail;
  5         26  
76             }
77 11         1966 return $res;
78 7         136 });
79              
80             $app->routes->add_condition(signed => sub {
81 2     2   463 my ($r, $c, $captures, $required) = @_;
82 2   66     15 return (!$required or $c->signature_exists);
83 7         269 });
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         145 });
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         500 });
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         482 });
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         455 });
112              
113             $app->helper(signature_exists => sub {
114 5     5   57046 my $c = shift;
115 5         23 return !!$c->session($session_key);
116 7         441 });
117              
118             $app->helper(is_user_authenticated => sub {
119 19     19   40290 my $c = shift;
120 19         86 return defined $current_user->($c);
121 7         438 });
122             $app->helper(is_user_authenticated_p => sub {
123 1     1   9782 my $c = shift;
124             $current_user_p->($c)->then(sub {
125 1         143 return defined $_[0];
126 1         4 });
127 7         492 });
128              
129 7         438 $app->helper($current_user_fn => $current_user);
130 7         432 $app->helper($current_user_fn."_p" => $current_user_p);
131              
132             $app->helper(logout => sub {
133 2     2   12882 my $c = shift;
134 2         15 delete $c->stash->{$our_stash_key};
135 2         31 delete $c->session->{$session_key};
136 2         457 return 1;
137 7         464 });
138             }
139              
140             # Unconditionally load the user based on uid in session
141             sub user_loader_closure {
142 7     7 0 26 my ($our_stash_key, $session_key, $load_user_cb) = @_;
143             sub {
144 24     24   53 my $c = shift;
145 24         92 my $uid = $c->session($session_key);
146 24 100       3233 return if !defined $uid;
147 12         62 my $user = $load_user_cb->($c, $uid);
148 12 50       179 if ($user) {
149 12         82 $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         38 };
156             }
157             sub user_loader_closure_p {
158 7     7 0 23 my ($our_stash_key, $session_key, $load_user_cb_p) = @_;
159             sub {
160 27     27   278238 my $c = shift;
161 27         155 my $uid = $c->session($session_key);
162 27 100       8572 return Mojo::Promise->resolve if !defined $uid;
163             $load_user_cb_p->($c, $uid)->then(sub {
164 10         12887 my $user = $_[0];
165 10 50       57 if ($user) {
166 10         69 $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         96 });
173 7         40 };
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 24 my ($our_stash_key, $user_loader_sub) = @_;
180             sub {
181 27     27   2362 my ($c, $user) = @_;
182             # Allow setting the current_user
183 27 100       103 if ( defined $user ) {
184 1         8 $c->stash($our_stash_key => { user => $user });
185 1         19 return;
186             }
187 26         89 my $stash = $c->stash($our_stash_key);
188             $user_loader_sub->($c)
189 26 100 66     483 unless $stash->{no_user} or defined $stash->{user};
190 26         287 $stash = $c->stash($our_stash_key);
191 26         332 return $stash->{user};
192 7         35 };
193             }
194             sub user_stash_extractor_closure_p {
195 7     7 0 23 my ($our_stash_key, $user_loader_sub_p) = @_;
196             sub {
197 6     6   2587 my ($c, $user) = @_;
198             # Allow setting the current_user
199 6 100       28 if ( defined $user ) {
200             return Mojo::Promise->resolve->then(sub {
201 1         120 $c->stash($our_stash_key => { user => $user });
202 1         6 });
203             }
204 5         19 my $stash = $c->stash($our_stash_key);
205             my $promise = ($stash->{no_user} or defined $stash->{user})
206 5 100 66     103 ? Mojo::Promise->resolve
207             : $user_loader_sub_p->($c);
208             $promise->then(sub {
209 5         2417 $stash = $c->stash($our_stash_key);
210 5         73 return $stash->{user};
211 5         959 });
212 7         36 };
213             }
214              
215             sub authenticate_closure {
216 7     7 0 25 my ($our_stash_key, $session_key, $validate_user_cb, $current_user) = @_;
217             sub {
218 7     7   100094 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     64 $extradata ||= {};
224             my $uid = $extradata->{auto_validate} //
225 7   100     68 $validate_user_cb->($c, $user, $pass, $extradata);
226 7 100       123 return undef if !defined $uid;
227 5         33 $c->session($session_key => $uid);
228             # Clear stash to force reload of any already loaded user object
229 5         826 delete $c->stash->{$our_stash_key};
230 5 50       59 return 1 if defined $current_user->($c);
231 0         0 return undef;
232 7         102 };
233             }
234             sub authenticate_closure_p {
235 7     7 0 26 my ($our_stash_key, $session_key, $validate_user_cb_p, $current_user_p) = @_;
236             sub {
237 3     3   34449 my ($c, $user, $pass, $extradata) = @_;
238 3   50     27 $extradata ||= {};
239             my $promise = defined($extradata->{auto_validate})
240             ? Mojo::Promise->resolve($extradata->{auto_validate})
241 3 50       24 : $validate_user_cb_p->($c, $user, $pass, $extradata);
242             $promise->then(sub {
243 3         77713 my ($uid) = @_;
244 3 100       20 return undef if !defined $uid;
245 2         17 $c->session($session_key => $uid);
246             # Clear stash to force reload of any already loaded user object
247 2         290 delete $c->stash->{$our_stash_key};
248 2         22 $current_user_p->($c);
249             })->then(sub {
250 3 100       559 defined $_[0] ? 1 : undef;
251 3         597 });
252 7         63 };
253             }
254              
255             1;
256              
257             __END__