Update tests prereqs to include Test::Exception (RT #36339)
[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 }
29 print STDERR "use session is " . $self->config->{'use_session'} . "\n";
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 ($@) {
74 $app->log->warn( qq(Credential class "$credentialclass" not found, trying deprecated ::Plugin:: style naming. ) );
8a7bd676 75 my $origcredentialclass = $credentialclass;
39ce54e8 76 $credentialclass =~ s/Catalyst::Authentication/Catalyst::Plugin::Authentication/;
8a7bd676 77
78 eval { Catalyst::Utils::ensure_class_loaded( $credentialclass ); };
79 if ($@) {
80 Carp::croak "Unable to load credential class, " . $origcredentialclass . " OR " . $credentialclass .
81 " in realm " . $self->name;
82 }
39ce54e8 83 }
84
85 eval {
86 Catalyst::Utils::ensure_class_loaded( $storeclass );
87 };
88
89 if ($@) {
90 $app->log->warn( qq(Store class "$storeclass" not found, trying deprecated ::Plugin:: style naming. ) );
8a7bd676 91 my $origstoreclass = $storeclass;
39ce54e8 92 $storeclass =~ s/Catalyst::Authentication/Catalyst::Plugin::Authentication/;
8a7bd676 93 eval { Catalyst::Utils::ensure_class_loaded( $storeclass ); };
94 if ($@) {
95 Carp::croak "Unable to load store class, " . $origstoreclass . " OR " . $storeclass .
96 " in realm " . $self->name;
97 }
39ce54e8 98 }
646ea5b1 99
100 # BACKWARDS COMPATIBILITY - if the store class does not define find_user, we define it in terms
101 # of get_user and add it to the class. this is because the auth routines use find_user,
102 # and rely on it being present. (this avoids per-call checks)
103 if (!$storeclass->can('find_user')) {
104 no strict 'refs';
105 *{"${storeclass}::find_user"} = sub {
106 my ($self, $info) = @_;
107 my @rest = @{$info->{rest}} if exists($info->{rest});
108 $self->get_user($info->{id}, @rest);
109 };
110 }
111
112 ## a little cruft to stay compatible with some poorly written stores / credentials
113 ## we'll remove this soon.
114 if ($storeclass->can('new')) {
115 $self->store($storeclass->new($config->{'store'}, $app, $self));
116 } else {
117 $app->log->error("THIS IS DEPRECATED: $storeclass has no new() method - Attempting to use uninstantiated");
118 $self->store($storeclass);
119 }
120 if ($credentialclass->can('new')) {
121 $self->credential($credentialclass->new($config->{'credential'}, $app, $self));
122 } else {
123 $app->log->error("THIS IS DEPRECATED: $credentialclass has no new() method - Attempting to use uninstantiated");
124 $self->credential($credentialclass);
125 }
126
127 return $self;
128}
129
130sub find_user {
131 my ( $self, $authinfo, $c ) = @_;
132
133 my $res = $self->store->find_user($authinfo, $c);
134
4bd1f581 135 if (!$res) {
68c40c8b 136 if ($self->config->{'auto_create_user'} && $self->store->can('auto_create_user') ) {
137 $res = $self->store->auto_create_user($authinfo, $c);
4bd1f581 138 }
68c40c8b 139 } elsif ($self->config->{'auto_update_user'} && $self->store->can('auto_update_user')) {
140 $res = $self->store->auto_update_user($authinfo, $c, $res);
4bd1f581 141 }
646ea5b1 142
143 return $res;
144}
145
146sub authenticate {
147 my ($self, $c, $authinfo) = @_;
148
149 my $user = $self->credential->authenticate($c, $self, $authinfo);
150 if (ref($user)) {
151 $c->set_authenticated($user, $self->name);
152 return $user;
153 } else {
154 return undef;
155 }
156}
157
8a7bd676 158sub user_is_restorable {
159 my ($self, $c) = @_;
160
161 return unless
162 $c->isa("Catalyst::Plugin::Session")
128321cc 163 and $self->config->{'use_session'}
8a7bd676 164 and $c->session_is_valid;
646ea5b1 165
8a7bd676 166 return $c->session->{__user};
167}
168
169sub restore_user {
170 my ($self, $c, $frozen_user) = @_;
646ea5b1 171
8a7bd676 172 $frozen_user ||= $self->user_is_restorable($c);
173 return unless defined($frozen_user);
174
175 $c->_user( my $user = $self->from_session( $c, $frozen_user ) );
176
177 # this sets the realm the user originated in.
178 $user->auth_realm($self->name);
179
180 return $user;
181}
182
183sub persist_user {
184 my ($self, $c, $user) = @_;
185
186 if (
187 $c->isa("Catalyst::Plugin::Session")
128321cc 188 and $self->config->{'use_session'}
8a7bd676 189 and $user->supports("session")
190 ) {
191 $c->session->{__user_realm} = $self->name;
192
193 # we want to ask the store for a user prepared for the session.
194 # but older modules split this functionality between the user and the
195 # store. We try the store first. If not, we use the old method.
196 if ($self->store->can('for_session')) {
197 $c->session->{__user} = $self->store->for_session($c, $user);
198 } else {
199 $c->session->{__user} = $user->for_session;
200 }
646ea5b1 201 }
8a7bd676 202 return $user;
203}
204
205sub remove_persisted_user {
206 my ($self, $c) = @_;
207
208 if (
209 $c->isa("Catalyst::Plugin::Session")
128321cc 210 and $self->config->{'use_session'}
8a7bd676 211 and $c->session_is_valid
212 ) {
213 delete @{ $c->session }{qw/__user __user_realm/};
214 }
215}
216
217## backwards compatibility - I don't think many people wrote realms since they
218## have only existed for a short time - but just in case.
219sub save_user_in_session {
220 my ( $self, $c, $user ) = @_;
221
222 return $self->persist_user($c, $user);
646ea5b1 223}
224
225sub from_session {
226 my ($self, $c, $frozen_user) = @_;
227
228 return $self->store->from_session($c, $frozen_user);
229}
230
231
232__PACKAGE__;
233
52a3537a 234__END__
1489b476 235
236=pod
237
238=head1 NAME
239
5c5af345 240Catalyst::Authentication::Realm - Base class for realm objects.
1489b476 241
242=head1 DESCRIPTION
243
5afc0dde 244=head1 CONFIGURATION
1489b476 245
246=over 4
247
5afc0dde 248=item class
249
8a7bd676 250By default this class is used by
251L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication> for all
252realms. The class parameter allows you to choose a different class to use for
253this realm. Creating a new Realm class can allow for authentication methods
254that fall outside the normal credential/store methodology.
85593aa9 255
5afc0dde 256=item auto_create_user
1489b476 257
85593aa9 258Set this to true if you wish this realm to auto-create user accounts when the
259user doesn't exist (most useful for remote authentication schemes).
260
5afc0dde 261=item auto_update_user
1489b476 262
85593aa9 263Set this to true if you wish this realm to auto-update user accounts after
264authentication (most useful for remote authentication schemes).
265
5afc0dde 266=back
267
268=head1 METHODS
1489b476 269
8a7bd676 270=head2 new( $realmname, $config, $app )
1489b476 271
85593aa9 272Instantiantes this realm, plus the specified store and credential classes.
273
274=head2 store( )
275
8a7bd676 276Returns an instance of the store object for this realm.
85593aa9 277
278=head2 credential( )
279
8a7bd676 280Returns an instance of the credential object for this realm.
85593aa9 281
8a7bd676 282=head2 find_user( $authinfo, $c )
5afc0dde 283
8a7bd676 284Retrieves the user given the authentication information provided. This
285is most often called from the credential. The default realm class simply
286delegates this call the store object. If enabled, auto-creation and
287auto-updating of users is also handled here.
85593aa9 288
8a7bd676 289=head2 authenticate( $c, $authinfo)
5afc0dde 290
8a7bd676 291Performs the authentication process for the current realm. The default
292realm class simply delegates this to the credential and sets
293the authenticated user on success. Returns the authenticated user object;
85593aa9 294
aa4a1855 295=head2 save_user_in_session($c, $user)
5afc0dde 296
8a7bd676 297Used to save the user in a session. Saves $user in the current session,
298marked as originating in the current realm. Calls $store->for_session() by
299default. If for_session is not available in the store class, will attempt
300to call $user->for_session().
85593aa9 301
96671824 302=head2 persist_user($c, $user)
303
304Takes the user data and persists it in the sessi.on
305
306=head2 remove_persisted_user($c)
307
308Removes any persisted user data in the session.
309
310=head2 restore_user($c, [$frozen_user])
311
312Restores the user from parameter, or the session by default.
313
314=head2 user_is_restorable( $c )
315
316Predicate to tell you if the current session has restorable user data.
317
8a7bd676 318=head2 from_session($c, $frozenuser )
1489b476 319
8a7bd676 320Triggers restoring of the user from data in the session. The default realm
321class simply delegates the call to $store->from_session($c, $frozenuser);
85593aa9 322
1489b476 323=cut
324