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