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 |
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 => { |
d02b2273 |
44 | 'local' => { 'type' => 'normal' }, |
45 | 'temp' => { 'type' => 'temporary' }, |
714e2bdc |
46 | } |
47 | }, |
48 | 'normal' => { |
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', |
57 | user_class => 'Schema::Person::Identity', |
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', |
70 | user_class => 'Schema::Person::Identity', |
71 | id_field => 'id', |
72 | } |
73 | }, |
74 | } |
75 | } |
76 | ); |
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 | |
93 | An array reference consisting of each realm to attempt authentication against, |
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 |
102 | realm, separated by some other token (in the L<EXAMPLE> authinfo_mungesection, |
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 | |
111 | =head2 authenticate |
112 | |
113 | This method iteratively calls each realm listed in the C<realms> configuration |
114 | key. It returns after the first successful authentication call is done. |
115 | |
116 | =cut |
117 | |
118 | sub authenticate { |
119 | my ( $self, $c, $authinfo ) = @_; |
120 | my $realms = $self->config->{realms}; |
121 | carp "No realms to authenticate against, check configuration" |
122 | unless $realms; |
123 | carp "Realms configuration must be an array reference" |
124 | unless ref $realms eq 'ARRAY'; |
125 | foreach my $realm_name ( @$realms ) { |
126 | my $realm = $c->get_auth_realm( $realm_name ); |
127 | carp "Unable to find realm: $realm_name, check configuration" |
128 | unless $realm; |
129 | my $auth = { %$authinfo }; |
130 | $auth->{realm} ||= $realm->name; |
131 | if ( my $info = $realm->config->{authinfo_munge}->{$realm->name} ) { |
132 | $auth = Catalyst::Utils::merge_hashes($auth, $info); |
133 | } |
134 | if ( my $obj = $realm->authenticate( $c, $auth ) ) { |
135 | $c->set_authenticated( $obj, $realm->name ); |
136 | return $obj; |
137 | } |
138 | } |
139 | return; |
140 | } |
141 | |
142 | =head1 AUTHORS |
143 | |
144 | J. Shirley C<< <jshirley@cpan.org> >> |
145 | |
146 | Jay Kuri C<< <jayk@cpan.org> >> |
147 | |
148 | =head1 COPYRIGHT & LICENSE |
149 | |
150 | Copyright (c) 2008 the aforementioned authors. All rights reserved. This program |
151 | is free software; you can redistribute it and/or modify it under the same terms |
152 | as Perl itself. |
153 | |
154 | =cut |
155 | |
156 | 1; |