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