Change to actual Detach extension
[catagits/Catalyst-Plugin-Authentication.git] / lib / Catalyst / Authentication / Realm / Progressive.pm
CommitLineData
714e2bdc 1package Catalyst::Authentication::Realm::Progressive;
2
3use Carp;
4use warnings;
5use strict;
6
7use base 'Catalyst::Authentication::Realm';
8
3a311407 9use Try::Tiny;
10use Scalar::Util 'blessed';
11
714e2bdc 12=head1 NAME
13
14Catalyst::Authentication::Realm::Progressive - Authenticate against multiple realms
15
16=head1 SYNOPSIS
17
d02b2273 18This Realm allows an application to use a single authenticate() call during
64db792b 19which multiple realms are used and tried incrementally until one performs
20a successful authentication is accomplished.
714e2bdc 21
d02b2273 22A simple use case is a Temporary Password that looks and acts exactly as a
23regular password. Without changing the authentication code, you can
714e2bdc 24authenticate against multiple realms.
25
d02b2273 26Another use might be to support a legacy website authentication system, trying
27the current auth system first, and upon failure, attempting authentication against
28the legacy system.
29
714e2bdc 30=head2 EXAMPLE
31
32If your application has multiple realms to authenticate, such as a temporary
33password realm and a normal realm, you can configure the progressive realm as
34the default, and configure it to iteratively call the temporary realm and then
35the normal realm.
36
37 __PACKAGE__->config(
38 'Plugin::Authentication' => {
39 default_realm => 'progressive',
40 realms => {
41 progressive => {
42 class => 'Progressive',
43 realms => [ 'temp', 'normal' ],
44 # Modify the authinfo passed into authenticate by merging
45 # these hashes into the realm's authenticate call:
46 authinfo_munge => {
64db792b 47 normal => { 'type' => 'normal' },
48 temp => { 'type' => 'temporary' },
714e2bdc 49 }
50 },
a55abc15 51 normal => {
714e2bdc 52 credential => {
53 class => 'Password',
54 password_field => 'secret',
55 password_type => 'hashed',
56 password_hash_type => 'SHA-1',
57 },
58 store => {
59 class => 'DBIx::Class',
055630f7 60 user_model => 'Schema::Person::Identity',
714e2bdc 61 id_field => 'id',
62 }
63 },
64 temp => {
65 credential => {
66 class => 'Password',
67 password_field => 'secret',
68 password_type => 'hashed',
69 password_hash_type => 'SHA-1',
70 },
71 store => {
72 class => 'DBIx::Class',
055630f7 73 user_model => 'Schema::Person::Identity',
714e2bdc 74 id_field => 'id',
75 }
76 },
77 }
78 }
64db792b 79 );
714e2bdc 80
81Then, in your controller code, to attempt authentication against both realms
82you just have to do a simple authenticate call:
83
84 if ( $c->authenticate({ id => $username, password => $password }) ) {
d02b2273 85 if ( $c->user->type eq 'temporary' ) {
714e2bdc 86 # Force user to change password
87 }
88 }
89
90=head1 CONFIGURATION
91
92=over
93
94=item realms
95
64db792b 96An array reference consisting of each realm to attempt authentication against,
714e2bdc 97in the order listed. If the realm does not exist, calling authenticate will
98die.
99
100=item authinfo_munge
101
102A hash reference keyed by realm names, with values being hash references to
103merge into the authinfo call that is subsequently passed into the realm's
104authenticate method. This is useful if your store uses the same class for each
64db792b 105realm, separated by some other token (in the L<EXAMPLE> authinfo_mungesection,
714e2bdc 106the 'realm' is a column on C<Schema::Person::Identity> that will be either
107'temp' or 'local', to ensure the query to fetch the user finds the right
108Identity record for that realm.
109
110=back
111
112=head1 METHODS
113
3dae968a 114=head2 new ($realmname, $config, $app)
115
116Constructs an instance of this realm.
117
714e2bdc 118=head2 authenticate
119
120This method iteratively calls each realm listed in the C<realms> configuration
121key. It returns after the first successful authentication call is done.
122
123=cut
124
125sub authenticate {
126 my ( $self, $c, $authinfo ) = @_;
127 my $realms = $self->config->{realms};
128 carp "No realms to authenticate against, check configuration"
3a311407 129 unless $realms;
714e2bdc 130 carp "Realms configuration must be an array reference"
3a311407 131 unless ref $realms eq 'ARRAY';
132
133 my $should_detach;
134 foreach my $realm_name (@$realms) {
135 my $realm = $c->get_auth_realm($realm_name);
64db792b 136 carp "Unable to find realm: $realm_name, check configuration"
3a311407 137 unless $realm;
138 my $auth = {%$authinfo};
714e2bdc 139 $auth->{realm} ||= $realm->name;
3a311407 140 if ( my $info = $self->config->{authinfo_munge}->{ $realm->name } ) {
141 $auth = Catalyst::Utils::merge_hashes( $auth, $info );
714e2bdc 142 }
3a311407 143
144 my $auth_obj;
145 try {
146 $auth_obj = $realm->authenticate( $c, $auth );
147 }
148 catch {
82134370 149 if ( $_ eq $Catalyst::DETACH ) {
3a311407 150 $should_detach = 1;
151 }
152 else {
153 die $_;
154 }
155 };
156
157 if ($auth_obj) {
158 $c->set_authenticated( $auth_obj, $realm->name );
159 return $auth_obj;
714e2bdc 160 }
161 }
3a311407 162
163 # detach if detach is thrown like from Credential::HTTP
164 $c->detach if $should_detach;
165
714e2bdc 166 return;
167}
168
64db792b 169## we can not rely on inheriting new() because in this case we do not
170## load a credential or store, which is what new() sets up in the
6657dfec 171## standard realm. So we have to create our realm object, set our name
172## and return $self in order to avoid nasty warnings.
173
174sub new {
3a311407 175 my ( $class, $realmname, $config, $app ) = @_;
6657dfec 176
177 my $self = { config => $config };
178 bless $self, $class;
64db792b 179
6657dfec 180 $self->name($realmname);
181 return $self;
182}
183
714e2bdc 184=head1 AUTHORS
185
186J. Shirley C<< <jshirley@cpan.org> >>
187
188Jay Kuri C<< <jayk@cpan.org> >>
189
3a311407 190=head1 CONTRIBUTORS
191
192Gavin Henry C<< <ghenry@surevoip.co.uk> >>
193
194Tomas Doran C<< <bobtfish@bobtfish.net> >>
195
714e2bdc 196=head1 COPYRIGHT & LICENSE
197
3a311407 198Copyright (c) 2008-2011 the aforementioned authors. All rights reserved. This program
714e2bdc 199is free software; you can redistribute it and/or modify it under the same terms
200as Perl itself.
201
202=cut
203
2041;