Update Changes
[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;
eb8e53a4 19
646ea5b1 20 $self->name($realmname);
eb8e53a4 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
eb8e53a4 32 # use the Null store as a default - Don't complain if the realm class is being overridden,
a2fb5d49 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 }
eb8e53a4 39 }
646ea5b1 40 my $storeclass = $config->{'store'}{'class'};
eb8e53a4 41
646ea5b1 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
eb8e53a4 50 # a little niceness - since most systems seem to use the password credential class,
646ea5b1 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'};
eb8e53a4 55
646ea5b1 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 }
eb8e53a4 63
39ce54e8 64 # if we made it here - we have what we need to load the classes
eb8e53a4 65
66 ### BACKWARDS COMPATIBILITY - DEPRECATION WARNING:
39ce54e8 67 ### we must eval the ensure_class_loaded - because we might need to try the old-style
eb8e53a4 68 ### ::Plugin:: module naming if the standard method fails.
69
8a7bd676 70 ## Note to self - catch second exception and bitch in detail?
eb8e53a4 71
39ce54e8 72 eval {
73 Catalyst::Utils::ensure_class_loaded( $credentialclass );
74 };
eb8e53a4 75
39ce54e8 76 if ($@) {
eb8e53a4 77 # If the file is missing, then try the old-style fallback,
eedf45ae 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/;
eb8e53a4 89 Carp::croak "Unable to load credential class, " . $origcredentialclass . " OR " . $credentialclass .
8a7bd676 90 " in realm " . $self->name;
91 }
39ce54e8 92 }
eb8e53a4 93
39ce54e8 94 eval {
95 Catalyst::Utils::ensure_class_loaded( $storeclass );
96 };
eb8e53a4 97
39ce54e8 98 if ($@) {
eb8e53a4 99 # If the file is missing, then try the old-style fallback,
eedf45ae 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/;
eb8e53a4 110 Carp::croak "Unable to load store class, " . $origstoreclass . " OR " . $storeclass .
8a7bd676 111 " in realm " . $self->name;
112 }
39ce54e8 113 }
eb8e53a4 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,
646ea5b1 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 }
eb8e53a4 126
646ea5b1 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 }
eb8e53a4 141
646ea5b1 142 return $self;
143}
144
145sub find_user {
146 my ( $self, $authinfo, $c ) = @_;
147
148 my $res = $self->store->find_user($authinfo, $c);
eb8e53a4 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);
eb8e53a4 156 }
157
646ea5b1 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) = @_;
eb8e53a4 175
8a7bd676 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) = @_;
eb8e53a4 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 );
eb8e53a4 191
1629387e 192 if ($user) {
193 $c->_user( $user );
eb8e53a4 194
1629387e 195 # this sets the realm the user originated in.
196 $user->auth_realm($self->name);
eb8e53a4 197 }
eea5667a 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?");
eb8e53a4 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 {
eb8e53a4 210 my ($self, $c) = @_;
211
212 $self->remove_persisted_user($c);
213 return;
66c62c8f 214}
215
8a7bd676 216sub persist_user {
217 my ($self, $c, $user) = @_;
eb8e53a4 218
8a7bd676 219 if (
622e71d9 220 $c->can('session')
128321cc 221 and $self->config->{'use_session'}
eb8e53a4 222 and $user->supports("session")
8a7bd676 223 ) {
224 $c->session->{__user_realm} = $self->name;
eb8e53a4 225
8a7bd676 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) = @_;
eb8e53a4 240
8a7bd676 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/};
eb8e53a4 247 }
8a7bd676 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) = @_;
eb8e53a4 260
646ea5b1 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
eb8e53a4 322Retrieves the user given the authentication information provided. This
8a7bd676 323is most often called from the credential. The default realm class simply
eb8e53a4 324delegates this call the store object. If enabled, auto-creation and
8a7bd676 325auto-updating of users is also handled here.
85593aa9 326
8a7bd676 327=head2 authenticate( $c, $authinfo)
5afc0dde 328
eb8e53a4 329Performs the authentication process for the current realm. The default
330realm class simply delegates this to the credential and sets
8a7bd676 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
eb8e53a4 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
eb8e53a4 344in most cases this will utilize the session. By default this uses the
bfbbe6e7 345catalyst session system to store the user by calling for_session on the
eb8e53a4 346active store. The user object must be a subclass of
347Catalyst::Authentication::User. If you have updated the user object, you
bfbbe6e7 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
eb8e53a4 369If there is a session to restore, but the restore fails for any reason then this method
9c469e37 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
eb8e53a4 375Decodes the frozenuser information provided and returns an instantiated
bfbbe6e7 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