Commit | Line | Data |
5c5af345 |
1 | package Catalyst::Authentication::Realm; |
646ea5b1 |
2 | |
3 | use strict; |
4 | use warnings; |
1489b476 |
5 | |
646ea5b1 |
6 | use base qw/Class::Accessor::Fast/; |
7 | |
8 | BEGIN { |
9 | __PACKAGE__->mk_accessors(qw/store credential name config/); |
10 | }; |
11 | |
128321cc |
12 | ## Add use_session config item to realm. |
13 | |
646ea5b1 |
14 | sub new { |
15 | my ($class, $realmname, $config, $app) = @_; |
16 | |
17 | my $self = { config => $config }; |
18 | bless $self, $class; |
19 | |
20 | $self->name($realmname); |
21 | |
128321cc |
22 | if (!exists($self->config->{'use_session'})) { |
23 | if (exists($app->config->{'Plugin::Authentication'}{'use_session'})) { |
24 | $self->config->{'use_session'} = $app->config->{'Plugin::Authentication'}{'use_session'}; |
25 | } else { |
26 | $self->config->{'use_session'} = 1; |
27 | } |
28 | } |
1629387e |
29 | |
646ea5b1 |
30 | $app->log->debug("Setting up auth realm $realmname") if $app->debug; |
5ef7a3dc |
31 | |
a2fb5d49 |
32 | # use the Null store as a default - Don't complain if the realm class is being overridden, |
33 | # as the new realm may behave differently. |
f0312fca |
34 | if( ! exists($config->{store}{class}) ) { |
39ce54e8 |
35 | $config->{store}{class} = '+Catalyst::Authentication::Store::Null'; |
f0312fca |
36 | if (! exists($config->{class})) { |
37 | $app->log->debug( qq(No Store specified for realm "$realmname", using the Null store.) ); |
38 | } |
646ea5b1 |
39 | } |
646ea5b1 |
40 | my $storeclass = $config->{'store'}{'class'}; |
41 | |
42 | ## follow catalyst class naming - a + prefix means a fully qualified class, otherwise it's |
43 | ## taken to mean C::P::A::Store::(specifiedclass) |
44 | if ($storeclass !~ /^\+(.*)$/ ) { |
39ce54e8 |
45 | $storeclass = "Catalyst::Authentication::Store::${storeclass}"; |
646ea5b1 |
46 | } else { |
47 | $storeclass = $1; |
48 | } |
646ea5b1 |
49 | |
50 | # a little niceness - since most systems seem to use the password credential class, |
51 | # if no credential class is specified we use password. |
39ce54e8 |
52 | $config->{credential}{class} ||= '+Catalyst::Authentication::Credential::Password'; |
646ea5b1 |
53 | |
54 | my $credentialclass = $config->{'credential'}{'class'}; |
55 | |
56 | ## follow catalyst class naming - a + prefix means a fully qualified class, otherwise it's |
39ce54e8 |
57 | ## taken to mean C::A::Credential::(specifiedclass) |
646ea5b1 |
58 | if ($credentialclass !~ /^\+(.*)$/ ) { |
39ce54e8 |
59 | $credentialclass = "Catalyst::Authentication::Credential::${credentialclass}"; |
646ea5b1 |
60 | } else { |
61 | $credentialclass = $1; |
62 | } |
63 | |
39ce54e8 |
64 | # if we made it here - we have what we need to load the classes |
65 | |
66 | ### BACKWARDS COMPATIBILITY - DEPRECATION WARNING: |
67 | ### we must eval the ensure_class_loaded - because we might need to try the old-style |
68 | ### ::Plugin:: module naming if the standard method fails. |
69 | |
8a7bd676 |
70 | ## Note to self - catch second exception and bitch in detail? |
71 | |
39ce54e8 |
72 | eval { |
73 | Catalyst::Utils::ensure_class_loaded( $credentialclass ); |
74 | }; |
75 | |
76 | if ($@) { |
eedf45ae |
77 | # If the file is missing, then try the old-style fallback, |
78 | # but re-throw anything else for the user to deal with. |
79 | die unless $@ =~ /^Can't locate/; |
39ce54e8 |
80 | $app->log->warn( qq(Credential class "$credentialclass" not found, trying deprecated ::Plugin:: style naming. ) ); |
8a7bd676 |
81 | my $origcredentialclass = $credentialclass; |
39ce54e8 |
82 | $credentialclass =~ s/Catalyst::Authentication/Catalyst::Plugin::Authentication/; |
8a7bd676 |
83 | |
84 | eval { Catalyst::Utils::ensure_class_loaded( $credentialclass ); }; |
85 | if ($@) { |
eedf45ae |
86 | # Likewise this croak is useful if the second exception is also "not found", |
87 | # but would be confusing if it's anything else. |
88 | die unless $@ =~ /^Can't locate/; |
8a7bd676 |
89 | Carp::croak "Unable to load credential class, " . $origcredentialclass . " OR " . $credentialclass . |
90 | " in realm " . $self->name; |
91 | } |
39ce54e8 |
92 | } |
93 | |
94 | eval { |
95 | Catalyst::Utils::ensure_class_loaded( $storeclass ); |
96 | }; |
97 | |
98 | if ($@) { |
eedf45ae |
99 | # If the file is missing, then try the old-style fallback, |
100 | # but re-throw anything else for the user to deal with. |
101 | die unless $@ =~ /^Can't locate/; |
39ce54e8 |
102 | $app->log->warn( qq(Store class "$storeclass" not found, trying deprecated ::Plugin:: style naming. ) ); |
8a7bd676 |
103 | my $origstoreclass = $storeclass; |
39ce54e8 |
104 | $storeclass =~ s/Catalyst::Authentication/Catalyst::Plugin::Authentication/; |
8a7bd676 |
105 | eval { Catalyst::Utils::ensure_class_loaded( $storeclass ); }; |
106 | if ($@) { |
eedf45ae |
107 | # Likewise this croak is useful if the second exception is also "not found", |
108 | # but would be confusing if it's anything else. |
109 | die unless $@ =~ /^Can't locate/; |
8a7bd676 |
110 | Carp::croak "Unable to load store class, " . $origstoreclass . " OR " . $storeclass . |
111 | " in realm " . $self->name; |
112 | } |
39ce54e8 |
113 | } |
646ea5b1 |
114 | |
115 | # BACKWARDS COMPATIBILITY - if the store class does not define find_user, we define it in terms |
116 | # of get_user and add it to the class. this is because the auth routines use find_user, |
117 | # and rely on it being present. (this avoids per-call checks) |
118 | if (!$storeclass->can('find_user')) { |
119 | no strict 'refs'; |
120 | *{"${storeclass}::find_user"} = sub { |
121 | my ($self, $info) = @_; |
122 | my @rest = @{$info->{rest}} if exists($info->{rest}); |
123 | $self->get_user($info->{id}, @rest); |
124 | }; |
125 | } |
126 | |
127 | ## a little cruft to stay compatible with some poorly written stores / credentials |
128 | ## we'll remove this soon. |
129 | if ($storeclass->can('new')) { |
130 | $self->store($storeclass->new($config->{'store'}, $app, $self)); |
131 | } else { |
132 | $app->log->error("THIS IS DEPRECATED: $storeclass has no new() method - Attempting to use uninstantiated"); |
133 | $self->store($storeclass); |
134 | } |
135 | if ($credentialclass->can('new')) { |
136 | $self->credential($credentialclass->new($config->{'credential'}, $app, $self)); |
137 | } else { |
138 | $app->log->error("THIS IS DEPRECATED: $credentialclass has no new() method - Attempting to use uninstantiated"); |
139 | $self->credential($credentialclass); |
140 | } |
141 | |
142 | return $self; |
143 | } |
144 | |
145 | sub find_user { |
146 | my ( $self, $authinfo, $c ) = @_; |
147 | |
148 | my $res = $self->store->find_user($authinfo, $c); |
149 | |
4bd1f581 |
150 | if (!$res) { |
68c40c8b |
151 | if ($self->config->{'auto_create_user'} && $self->store->can('auto_create_user') ) { |
152 | $res = $self->store->auto_create_user($authinfo, $c); |
4bd1f581 |
153 | } |
68c40c8b |
154 | } elsif ($self->config->{'auto_update_user'} && $self->store->can('auto_update_user')) { |
155 | $res = $self->store->auto_update_user($authinfo, $c, $res); |
4bd1f581 |
156 | } |
646ea5b1 |
157 | |
158 | return $res; |
159 | } |
160 | |
161 | sub authenticate { |
162 | my ($self, $c, $authinfo) = @_; |
163 | |
164 | my $user = $self->credential->authenticate($c, $self, $authinfo); |
165 | if (ref($user)) { |
166 | $c->set_authenticated($user, $self->name); |
167 | return $user; |
168 | } else { |
169 | return undef; |
170 | } |
171 | } |
172 | |
8a7bd676 |
173 | sub user_is_restorable { |
174 | my ($self, $c) = @_; |
175 | |
176 | return unless |
622e71d9 |
177 | $c->can('session') |
128321cc |
178 | and $self->config->{'use_session'} |
8a7bd676 |
179 | and $c->session_is_valid; |
646ea5b1 |
180 | |
8a7bd676 |
181 | return $c->session->{__user}; |
182 | } |
183 | |
184 | sub restore_user { |
185 | my ($self, $c, $frozen_user) = @_; |
646ea5b1 |
186 | |
8a7bd676 |
187 | $frozen_user ||= $self->user_is_restorable($c); |
188 | return unless defined($frozen_user); |
189 | |
1629387e |
190 | my $user = $self->from_session( $c, $frozen_user ); |
8a7bd676 |
191 | |
1629387e |
192 | if ($user) { |
193 | $c->_user( $user ); |
8a7bd676 |
194 | |
1629387e |
195 | # this sets the realm the user originated in. |
196 | $user->auth_realm($self->name); |
eea5667a |
197 | } |
198 | else { |
199 | $self->failed_user_restore($c) || |
200 | $c->error("Store claimed to have a restorable user, but restoration failed. Did you change the user's id_field?"); |
1629387e |
201 | } |
202 | |
8a7bd676 |
203 | return $user; |
204 | } |
205 | |
66c62c8f |
206 | ## this occurs if there is a session but the thing the session refers to |
207 | ## can not be found. Do what you must do here. |
eea5667a |
208 | ## Return true if you can fix the situation and find a user, false otherwise |
66c62c8f |
209 | sub failed_user_restore { |
210 | my ($self, $c) = @_; |
211 | |
212 | $self->remove_persisted_user($c); |
eea5667a |
213 | return; |
66c62c8f |
214 | } |
215 | |
8a7bd676 |
216 | sub persist_user { |
217 | my ($self, $c, $user) = @_; |
218 | |
219 | if ( |
622e71d9 |
220 | $c->can('session') |
128321cc |
221 | and $self->config->{'use_session'} |
8a7bd676 |
222 | and $user->supports("session") |
223 | ) { |
224 | $c->session->{__user_realm} = $self->name; |
225 | |
226 | # we want to ask the store for a user prepared for the session. |
227 | # but older modules split this functionality between the user and the |
228 | # store. We try the store first. If not, we use the old method. |
229 | if ($self->store->can('for_session')) { |
230 | $c->session->{__user} = $self->store->for_session($c, $user); |
231 | } else { |
232 | $c->session->{__user} = $user->for_session; |
233 | } |
646ea5b1 |
234 | } |
8a7bd676 |
235 | return $user; |
236 | } |
237 | |
238 | sub remove_persisted_user { |
239 | my ($self, $c) = @_; |
240 | |
241 | if ( |
622e71d9 |
242 | $c->can('session') |
128321cc |
243 | and $self->config->{'use_session'} |
8a7bd676 |
244 | and $c->session_is_valid |
245 | ) { |
246 | delete @{ $c->session }{qw/__user __user_realm/}; |
247 | } |
248 | } |
249 | |
250 | ## backwards compatibility - I don't think many people wrote realms since they |
251 | ## have only existed for a short time - but just in case. |
252 | sub save_user_in_session { |
253 | my ( $self, $c, $user ) = @_; |
254 | |
255 | return $self->persist_user($c, $user); |
646ea5b1 |
256 | } |
257 | |
258 | sub from_session { |
259 | my ($self, $c, $frozen_user) = @_; |
260 | |
261 | return $self->store->from_session($c, $frozen_user); |
262 | } |
263 | |
264 | |
265 | __PACKAGE__; |
266 | |
52a3537a |
267 | __END__ |
1489b476 |
268 | |
269 | =pod |
270 | |
271 | =head1 NAME |
272 | |
5c5af345 |
273 | Catalyst::Authentication::Realm - Base class for realm objects. |
1489b476 |
274 | |
275 | =head1 DESCRIPTION |
276 | |
5afc0dde |
277 | =head1 CONFIGURATION |
1489b476 |
278 | |
279 | =over 4 |
280 | |
5afc0dde |
281 | =item class |
282 | |
8a7bd676 |
283 | By default this class is used by |
284 | L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication> for all |
285 | realms. The class parameter allows you to choose a different class to use for |
286 | this realm. Creating a new Realm class can allow for authentication methods |
287 | that fall outside the normal credential/store methodology. |
85593aa9 |
288 | |
5afc0dde |
289 | =item auto_create_user |
1489b476 |
290 | |
85593aa9 |
291 | Set this to true if you wish this realm to auto-create user accounts when the |
292 | user doesn't exist (most useful for remote authentication schemes). |
293 | |
5afc0dde |
294 | =item auto_update_user |
1489b476 |
295 | |
85593aa9 |
296 | Set this to true if you wish this realm to auto-update user accounts after |
297 | authentication (most useful for remote authentication schemes). |
298 | |
bf4d93a4 |
299 | =item use_session |
300 | |
301 | Sets session usage for this particular realm - overriding the global use_sesion setting. |
302 | |
303 | |
5afc0dde |
304 | =back |
305 | |
306 | =head1 METHODS |
1489b476 |
307 | |
8a7bd676 |
308 | =head2 new( $realmname, $config, $app ) |
1489b476 |
309 | |
85593aa9 |
310 | Instantiantes this realm, plus the specified store and credential classes. |
311 | |
312 | =head2 store( ) |
313 | |
8a7bd676 |
314 | Returns an instance of the store object for this realm. |
85593aa9 |
315 | |
316 | =head2 credential( ) |
317 | |
8a7bd676 |
318 | Returns an instance of the credential object for this realm. |
85593aa9 |
319 | |
8a7bd676 |
320 | =head2 find_user( $authinfo, $c ) |
5afc0dde |
321 | |
8a7bd676 |
322 | Retrieves the user given the authentication information provided. This |
323 | is most often called from the credential. The default realm class simply |
324 | delegates this call the store object. If enabled, auto-creation and |
325 | auto-updating of users is also handled here. |
85593aa9 |
326 | |
8a7bd676 |
327 | =head2 authenticate( $c, $authinfo) |
5afc0dde |
328 | |
8a7bd676 |
329 | Performs the authentication process for the current realm. The default |
330 | realm class simply delegates this to the credential and sets |
331 | the authenticated user on success. Returns the authenticated user object; |
85593aa9 |
332 | |
bf4d93a4 |
333 | =head1 USER PERSISTENCE |
5afc0dde |
334 | |
bfbbe6e7 |
335 | The Realm class allows complete control over the persistance of users |
336 | between requests. By default the realm attempts to use the Catalyst |
337 | session system to accomplish this. By overriding the methods below |
338 | in a custom Realm class, however, you can handle user persistance in |
339 | any way you see fit. |
85593aa9 |
340 | |
96671824 |
341 | =head2 persist_user($c, $user) |
342 | |
bfbbe6e7 |
343 | persist_user is the entry point for saving user information between requests |
344 | in most cases this will utilize the session. By default this uses the |
345 | catalyst session system to store the user by calling for_session on the |
346 | active store. The user object must be a subclass of |
347 | Catalyst::Authentication::User. If you have updated the user object, you |
348 | must call persist_user again to ensure that the persisted user object reflects |
349 | your updates. |
96671824 |
350 | |
351 | =head2 remove_persisted_user($c) |
352 | |
bfbbe6e7 |
353 | Removes any persisted user data. By default, removes the user from the session. |
96671824 |
354 | |
bfbbe6e7 |
355 | =head2 user_is_restorable( $c ) |
96671824 |
356 | |
bfbbe6e7 |
357 | Returns whether there is a persisted user that may be restored. Returns |
358 | a token used to restore the user. With the default session persistance |
359 | it returns the raw frozen user information. |
96671824 |
360 | |
bfbbe6e7 |
361 | =head2 restore_user($c, [$frozen_user]) |
362 | |
363 | Restores the user from the given frozen_user parameter, or if not provided, |
364 | using the response from $self->user_is_restorable(); Uses $self->from_session() |
365 | to decode the frozen user. |
96671824 |
366 | |
9c469e37 |
367 | =head2 failed_user_restore($c) |
368 | |
369 | If there is a session to restore, but the restore fails for any reason then this method |
370 | is called. This method supplied just removes the persisted user, but can be overridden |
371 | if required to have more complex logic (e.g. finding a the user by their 'old' username). |
96671824 |
372 | |
8a7bd676 |
373 | =head2 from_session($c, $frozenuser ) |
1489b476 |
374 | |
bfbbe6e7 |
375 | Decodes the frozenuser information provided and returns an instantiated |
376 | user object. By default, this call is delegated to $store->from_session(). |
85593aa9 |
377 | |
bfbbe6e7 |
378 | =head2 save_user_in_session($c, $user) |
1489b476 |
379 | |
bfbbe6e7 |
380 | DEPRECATED. Use persist_user instead. (this simply calls persist_user) |
381 | |
382 | =cut |