Add obj() as shorthand method for get_object()
[catagits/Catalyst-Plugin-Authentication.git] / lib / Catalyst / Authentication / Realm.pm
CommitLineData
e0499ed6 1package Catalyst::Authentication::Realm;
810966b5 2
3use strict;
4use warnings;
2c7d23af 5
810966b5 6use base qw/Class::Accessor::Fast/;
7
8BEGIN {
9 __PACKAGE__->mk_accessors(qw/store credential name config/);
10};
11
9a37ffba 12## Add use_session config item to realm.
13
810966b5 14sub new {
15 my ($class, $realmname, $config, $app) = @_;
16
17 my $self = { config => $config };
18 bless $self, $class;
19
20 $self->name($realmname);
21
9a37ffba 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 }
9c08483f 29
810966b5 30 $app->log->debug("Setting up auth realm $realmname") if $app->debug;
8513d51b 31
10c0d683 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}) ) {
0f673ca7 35 $config->{store}{class} = '+Catalyst::Authentication::Store::Null';
8513d51b 36 $app->log->debug( qq(No Store specified for realm "$realmname", using the Null store.) );
810966b5 37 }
810966b5 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 !~ /^\+(.*)$/ ) {
0f673ca7 43 $storeclass = "Catalyst::Authentication::Store::${storeclass}";
810966b5 44 } else {
45 $storeclass = $1;
46 }
810966b5 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.
0f673ca7 50 $config->{credential}{class} ||= '+Catalyst::Authentication::Credential::Password';
810966b5 51
52 my $credentialclass = $config->{'credential'}{'class'};
53
54 ## follow catalyst class naming - a + prefix means a fully qualified class, otherwise it's
0f673ca7 55 ## taken to mean C::A::Credential::(specifiedclass)
810966b5 56 if ($credentialclass !~ /^\+(.*)$/ ) {
0f673ca7 57 $credentialclass = "Catalyst::Authentication::Credential::${credentialclass}";
810966b5 58 } else {
59 $credentialclass = $1;
60 }
61
0f673ca7 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
71486cb0 68 ## Note to self - catch second exception and bitch in detail?
69
0f673ca7 70 eval {
71 Catalyst::Utils::ensure_class_loaded( $credentialclass );
72 };
73
74 if ($@) {
607d5cc0 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/;
0f673ca7 78 $app->log->warn( qq(Credential class "$credentialclass" not found, trying deprecated ::Plugin:: style naming. ) );
71486cb0 79 my $origcredentialclass = $credentialclass;
0f673ca7 80 $credentialclass =~ s/Catalyst::Authentication/Catalyst::Plugin::Authentication/;
71486cb0 81
82 eval { Catalyst::Utils::ensure_class_loaded( $credentialclass ); };
83 if ($@) {
607d5cc0 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/;
71486cb0 87 Carp::croak "Unable to load credential class, " . $origcredentialclass . " OR " . $credentialclass .
88 " in realm " . $self->name;
89 }
0f673ca7 90 }
91
92 eval {
93 Catalyst::Utils::ensure_class_loaded( $storeclass );
94 };
95
96 if ($@) {
607d5cc0 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/;
0f673ca7 100 $app->log->warn( qq(Store class "$storeclass" not found, trying deprecated ::Plugin:: style naming. ) );
71486cb0 101 my $origstoreclass = $storeclass;
0f673ca7 102 $storeclass =~ s/Catalyst::Authentication/Catalyst::Plugin::Authentication/;
71486cb0 103 eval { Catalyst::Utils::ensure_class_loaded( $storeclass ); };
104 if ($@) {
607d5cc0 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/;
71486cb0 108 Carp::croak "Unable to load store class, " . $origstoreclass . " OR " . $storeclass .
109 " in realm " . $self->name;
110 }
0f673ca7 111 }
810966b5 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
4437366d 148 if (!$res) {
9b840849 149 if ($self->config->{'auto_create_user'} && $self->store->can('auto_create_user') ) {
150 $res = $self->store->auto_create_user($authinfo, $c);
4437366d 151 }
9b840849 152 } elsif ($self->config->{'auto_update_user'} && $self->store->can('auto_update_user')) {
153 $res = $self->store->auto_update_user($authinfo, $c, $res);
4437366d 154 }
810966b5 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
71486cb0 171sub user_is_restorable {
172 my ($self, $c) = @_;
173
174 return unless
385a82f9 175 $c->can('session')
9a37ffba 176 and $self->config->{'use_session'}
71486cb0 177 and $c->session_is_valid;
810966b5 178
71486cb0 179 return $c->session->{__user};
180}
181
182sub restore_user {
183 my ($self, $c, $frozen_user) = @_;
810966b5 184
71486cb0 185 $frozen_user ||= $self->user_is_restorable($c);
186 return unless defined($frozen_user);
187
9c08483f 188 my $user = $self->from_session( $c, $frozen_user );
71486cb0 189
9c08483f 190 if ($user) {
191 $c->_user( $user );
71486cb0 192
9c08483f 193 # this sets the realm the user originated in.
194 $user->auth_realm($self->name);
04ca89c6 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?");
9c08483f 199 }
200
71486cb0 201 return $user;
202}
203
bd45aa3d 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.
04ca89c6 206## Return true if you can fix the situation and find a user, false otherwise
bd45aa3d 207sub failed_user_restore {
208 my ($self, $c) = @_;
209
210 $self->remove_persisted_user($c);
04ca89c6 211 return;
bd45aa3d 212}
213
71486cb0 214sub persist_user {
215 my ($self, $c, $user) = @_;
216
217 if (
385a82f9 218 $c->can('session')
9a37ffba 219 and $self->config->{'use_session'}
71486cb0 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 }
810966b5 232 }
71486cb0 233 return $user;
234}
235
236sub remove_persisted_user {
237 my ($self, $c) = @_;
238
239 if (
385a82f9 240 $c->can('session')
9a37ffba 241 and $self->config->{'use_session'}
71486cb0 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);
810966b5 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
b72f8c2e 265__END__
2c7d23af 266
267=pod
268
269=head1 NAME
270
e0499ed6 271Catalyst::Authentication::Realm - Base class for realm objects.
2c7d23af 272
273=head1 DESCRIPTION
274
d2ca09b8 275=head1 CONFIGURATION
2c7d23af 276
277=over 4
278
d2ca09b8 279=item class
280
71486cb0 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.
deebc899 286
d2ca09b8 287=item auto_create_user
2c7d23af 288
deebc899 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
d2ca09b8 292=item auto_update_user
2c7d23af 293
deebc899 294Set this to true if you wish this realm to auto-update user accounts after
295authentication (most useful for remote authentication schemes).
296
928722c0 297=item use_session
298
299Sets session usage for this particular realm - overriding the global use_sesion setting.
300
301
d2ca09b8 302=back
303
304=head1 METHODS
2c7d23af 305
71486cb0 306=head2 new( $realmname, $config, $app )
2c7d23af 307
deebc899 308Instantiantes this realm, plus the specified store and credential classes.
309
310=head2 store( )
311
71486cb0 312Returns an instance of the store object for this realm.
deebc899 313
314=head2 credential( )
315
71486cb0 316Returns an instance of the credential object for this realm.
deebc899 317
71486cb0 318=head2 find_user( $authinfo, $c )
d2ca09b8 319
71486cb0 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.
deebc899 324
71486cb0 325=head2 authenticate( $c, $authinfo)
d2ca09b8 326
71486cb0 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;
deebc899 330
928722c0 331=head1 USER PERSISTENCE
d2ca09b8 332
a98ce95b 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.
deebc899 338
bc119a79 339=head2 persist_user($c, $user)
340
a98ce95b 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.
bc119a79 348
349=head2 remove_persisted_user($c)
350
a98ce95b 351Removes any persisted user data. By default, removes the user from the session.
bc119a79 352
a98ce95b 353=head2 user_is_restorable( $c )
bc119a79 354
a98ce95b 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.
bc119a79 358
a98ce95b 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.
bc119a79 364
2fb8865d 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).
bc119a79 370
71486cb0 371=head2 from_session($c, $frozenuser )
2c7d23af 372
a98ce95b 373Decodes the frozenuser information provided and returns an instantiated
374user object. By default, this call is delegated to $store->from_session().
deebc899 375
a98ce95b 376=head2 save_user_in_session($c, $user)
2c7d23af 377
a98ce95b 378DEPRECATED. Use persist_user instead. (this simply calls persist_user)
379
380=cut