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   1889 use warnings;
  3         6  
  3         82  
2 3     3   16 use strict;
  3         4  
  3         106  
3              
4             package Mojolicious::Plugin::Authentication;
5              
6             our $VERSION = '1.39';
7              
8 3     3   15 use Mojo::Base 'Mojolicious::Plugin';
  3         7  
  3         19  
9 3     3   525 use Mojo::Promise;
  3         6  
  3         56  
10              
11             sub register {
12 7     7 1 22818 my ($self, $app, $args) = @_;
13              
14 7 50       13 $args = { %{ $args || {} } }; # copy as mutating
  7         39  
15              
16 7         19 for my $cb_name (qw(load_user validate_user)) {
17 14         32 my $p_name = $cb_name."_p";
18             die __PACKAGE__, ": missing '$cb_name' subroutine ref in parameters\n"
19 14 50       61 unless grep ref eq 'CODE', @$args{$cb_name, $p_name};
20             $args->{$p_name} = sub {
21 7     7   12 my @r = eval { $args->{$cb_name}->(@_) };
  7         38  
22 7 50       79 $@ ? Mojo::Promise->reject($@) : Mojo::Promise->resolve(@r);
23 14 100       60 } if !$args->{$p_name};
24             }
25              
26 7 50       24 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     22 my $autoload_user = $args->{autoload_user} // 0;
35 7   50     33 my $session_key = $args->{session_key} || 'auth_data';
36 7   50     30 my $our_stash_key = $args->{stash_key} || '__authentication__';
37 7   50     26 my $current_user_fn = $args->{current_user_fn} || 'current_user';
38 7         13 my $load_user_cb = $args->{load_user};
39 7         10 my $validate_user_cb = $args->{validate_user};
40 7         13 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   35 ? $args->{fail_render} : sub { $args->{fail_render} };
  4         12  
45              
46 7         19 my $user_loader_sub = user_loader_closure(
47             $our_stash_key, $session_key, $load_user_cb,
48             );
49 7         18 my $user_loader_sub_p = user_loader_closure_p(
50             $our_stash_key, $session_key, $load_user_cb_p,
51             );
52              
53 7         55 my $current_user = user_stash_extractor_closure(
54             $our_stash_key, $user_loader_sub,
55             );
56 7         23 my $current_user_p = user_stash_extractor_closure_p(
57             $our_stash_key, $user_loader_sub_p,
58             );
59              
60 7         18 $app->helper(authenticate => authenticate_closure(
61             $our_stash_key, $session_key, $validate_user_cb, $current_user,
62             ));
63 7         624 $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       369 $app->hook(before_dispatch => $user_loader_sub_p) if $autoload_user;
68              
69             $app->routes->add_condition(authenticated => sub {
70 8     8   36724 my ($r, $c, $captures, $required) = @_;
71 8   66     44 my $res = (!$required or $c->is_user_authenticated);
72              
73 8 100       20 unless ($res) {
74 4         12 my $fail = $fail_render->(@_);
75 4 100       12 $c->render(%{$fail}) if $fail;
  1         4  
76             }
77 8         390 return $res;
78 7         81 });
79              
80             $app->routes->add_condition(signed => sub {
81 2     2   320 my ($r, $c, $captures, $required) = @_;
82 2   66     11 return (!$required or $c->signature_exists);
83 7         146 });
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         92 });
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         382 });
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         350 });
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         355 });
112              
113             $app->helper(signature_exists => sub {
114 5     5   45347 my $c = shift;
115 5         16 return !!$c->session($session_key);
116 7         342 });
117              
118             $app->helper(is_user_authenticated => sub {
119 16     16   29976 my $c = shift;
120 16         54 return defined $current_user->($c);
121 7         343 });
122             $app->helper(is_user_authenticated_p => sub {
123 1     1   8972 my $c = shift;
124             $current_user_p->($c)->then(sub {
125 1         100 return defined $_[0];
126 1         4 });
127 7         349 });
128              
129 7         332 $app->helper($current_user_fn => $current_user);
130 7         347 $app->helper($current_user_fn."_p" => $current_user_p);
131              
132             $app->helper(logout => sub {
133 2     2   11089 my $c = shift;
134 2         13 delete $c->stash->{$our_stash_key};
135 2         19 delete $c->session->{$session_key};
136 2         393 return 1;
137 7         370 });
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   32 my $c = shift;
145 21         49 my $uid = $c->session($session_key);
146 21 100       2341 return if !defined $uid;
147 12         37 my $user = $load_user_cb->($c, $uid);
148 12 50       110 if ($user) {
149 12         44 $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 15 my ($our_stash_key, $session_key, $load_user_cb_p) = @_;
159             sub {
160 24     24   202844 my $c = shift;
161 24         72 my $uid = $c->session($session_key);
162 24 100       5108 return Mojo::Promise->resolve if !defined $uid;
163             $load_user_cb_p->($c, $uid)->then(sub {
164 10         7698 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         52 });
173 7         24 };
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 18 my ($our_stash_key, $user_loader_sub) = @_;
180             sub {
181 24     24   1347 my ($c, $user) = @_;
182             # Allow setting the current_user
183 24 100       62 if ( defined $user ) {
184 1         5 $c->stash($our_stash_key => { user => $user });
185 1         14 return;
186             }
187 23         55 my $stash = $c->stash($our_stash_key);
188             $user_loader_sub->($c)
189 23 100 66     287 unless $stash->{no_user} or defined $stash->{user};
190 23         213 $stash = $c->stash($our_stash_key);
191 23         230 return $stash->{user};
192 7         23 };
193             }
194             sub user_stash_extractor_closure_p {
195 7     7 0 16 my ($our_stash_key, $user_loader_sub_p) = @_;
196             sub {
197 6     6   1725 my ($c, $user) = @_;
198             # Allow setting the current_user
199 6 100       17 if ( defined $user ) {
200             return Mojo::Promise->resolve->then(sub {
201 1         92 $c->stash($our_stash_key => { user => $user });
202 1         5 });
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     71 ? Mojo::Promise->resolve
207             : $user_loader_sub_p->($c);
208             $promise->then(sub {
209 5         1874 $stash = $c->stash($our_stash_key);
210 5         46 return $stash->{user};
211 5         663 });
212 7         23 };
213             }
214              
215             sub authenticate_closure {
216 7     7 0 17 my ($our_stash_key, $session_key, $validate_user_cb, $current_user) = @_;
217             sub {
218 7     7   84445 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     40 $validate_user_cb->($c, $user, $pass, $extradata);
226 7 100       87 return undef if !defined $uid;
227 5         21 $c->session($session_key => $uid);
228             # Clear stash to force reload of any already loaded user object
229 5         615 delete $c->stash->{$our_stash_key};
230 5 50       43 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 20 my ($our_stash_key, $session_key, $validate_user_cb_p, $current_user_p) = @_;
236             sub {
237 3     3   25807 my ($c, $user, $pass, $extradata) = @_;
238 3   50     22 $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         7105 my ($uid) = @_;
244 3 100       14 return undef if !defined $uid;
245 2         20 $c->session($session_key => $uid);
246             # Clear stash to force reload of any already loaded user object
247 2         211 delete $c->stash->{$our_stash_key};
248 2         17 $current_user_p->($c);
249             })->then(sub {
250 3 100       423 defined $_[0] ? 1 : undef;
251 3         409 });
252 7         43 };
253             }
254              
255             1;
256              
257             __END__