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