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