Make POD Coverage happy
[catagits/Catalyst-Plugin-Authentication.git] / lib / Catalyst / Authentication / Realm / Progressive.pm
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
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. 
18
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
21 authenticate against multiple realms.
22
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
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 => {
44                     'local'     => { 'type' => 'normal' },
45                     'temp'      => { 'type' => 'temporary' },
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 }) ) {
82      if ( $c->user->type eq 'temporary' ) {
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 new ($realmname, $config, $app)
112
113 Constructs an instance of this realm.
114
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 );
131         carp "Unable to find realm: $realm_name, check configuration" 
132             unless $realm;
133         my $auth = { %$authinfo };
134         $auth->{realm} ||= $realm->name;
135         if ( my $info = $realm->config->{authinfo_munge}->{$realm->name} ) {
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
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 
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;
156     
157     $self->name($realmname);
158     return $self;
159 }
160
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;