A better fix for the realm class name warning problem.
[catagits/Catalyst-Plugin-Authentication.git] / lib / Catalyst / Authentication / Realm.pm
CommitLineData
5c5af345 1package Catalyst::Authentication::Realm;
646ea5b1 2
3use strict;
4use warnings;
1489b476 5
646ea5b1 6use base qw/Class::Accessor::Fast/;
7
8BEGIN {
9 __PACKAGE__->mk_accessors(qw/store credential name config/);
10};
11
128321cc 12## Add use_session config item to realm.
13
646ea5b1 14sub 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
145sub 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
161sub 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 173sub 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
184sub 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 209sub failed_user_restore {
210 my ($self, $c) = @_;
211
212 $self->remove_persisted_user($c);
eea5667a 213 return;
66c62c8f 214}
215
8a7bd676 216sub 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
238sub 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.
252sub save_user_in_session {
253 my ( $self, $c, $user ) = @_;
254
255 return $self->persist_user($c, $user);
646ea5b1 256}
257
258sub 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 273Catalyst::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 283By default this class is used by
284L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication> for all
285realms. The class parameter allows you to choose a different class to use for
286this realm. Creating a new Realm class can allow for authentication methods
287that fall outside the normal credential/store methodology.
85593aa9 288
5afc0dde 289=item auto_create_user
1489b476 290
85593aa9 291Set this to true if you wish this realm to auto-create user accounts when the
292user doesn't exist (most useful for remote authentication schemes).
293
5afc0dde 294=item auto_update_user
1489b476 295
85593aa9 296Set this to true if you wish this realm to auto-update user accounts after
297authentication (most useful for remote authentication schemes).
298
bf4d93a4 299=item use_session
300
301Sets 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 310Instantiantes this realm, plus the specified store and credential classes.
311
312=head2 store( )
313
8a7bd676 314Returns an instance of the store object for this realm.
85593aa9 315
316=head2 credential( )
317
8a7bd676 318Returns an instance of the credential object for this realm.
85593aa9 319
8a7bd676 320=head2 find_user( $authinfo, $c )
5afc0dde 321
8a7bd676 322Retrieves the user given the authentication information provided. This
323is most often called from the credential. The default realm class simply
324delegates this call the store object. If enabled, auto-creation and
325auto-updating of users is also handled here.
85593aa9 326
8a7bd676 327=head2 authenticate( $c, $authinfo)
5afc0dde 328
8a7bd676 329Performs the authentication process for the current realm. The default
330realm class simply delegates this to the credential and sets
331the authenticated user on success. Returns the authenticated user object;
85593aa9 332
bf4d93a4 333=head1 USER PERSISTENCE
5afc0dde 334
bfbbe6e7 335The Realm class allows complete control over the persistance of users
336between requests. By default the realm attempts to use the Catalyst
337session system to accomplish this. By overriding the methods below
338in a custom Realm class, however, you can handle user persistance in
339any way you see fit.
85593aa9 340
96671824 341=head2 persist_user($c, $user)
342
bfbbe6e7 343persist_user is the entry point for saving user information between requests
344in most cases this will utilize the session. By default this uses the
345catalyst session system to store the user by calling for_session on the
346active store. The user object must be a subclass of
347Catalyst::Authentication::User. If you have updated the user object, you
348must call persist_user again to ensure that the persisted user object reflects
349your updates.
96671824 350
351=head2 remove_persisted_user($c)
352
bfbbe6e7 353Removes any persisted user data. By default, removes the user from the session.
96671824 354
bfbbe6e7 355=head2 user_is_restorable( $c )
96671824 356
bfbbe6e7 357Returns whether there is a persisted user that may be restored. Returns
358a token used to restore the user. With the default session persistance
359it returns the raw frozen user information.
96671824 360
bfbbe6e7 361=head2 restore_user($c, [$frozen_user])
362
363Restores the user from the given frozen_user parameter, or if not provided,
364using the response from $self->user_is_restorable(); Uses $self->from_session()
365to decode the frozen user.
96671824 366
9c469e37 367=head2 failed_user_restore($c)
368
369If there is a session to restore, but the restore fails for any reason then this method
370is called. This method supplied just removes the persisted user, but can be overridden
371if 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 375Decodes the frozenuser information provided and returns an instantiated
376user 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 380DEPRECATED. Use persist_user instead. (this simply calls persist_user)
381
382=cut