File Coverage

blib/lib/Catalyst/Authentication/Realm/Progressive.pm
Criterion Covered Total %
statement 30 32 93.7
branch 6 10 60.0
condition 1 3 33.3
subroutine 6 6 100.0
pod 2 2 100.0
total 45 53 84.9


line stmt bran cond sub pod time code
1             package Catalyst::Authentication::Realm::Progressive;
2              
3 1     1   862 use Carp;
  1         3  
  1         128  
4 1     1   6 use warnings;
  1         2  
  1         65  
5 1     1   5 use strict;
  1         2  
  1         23  
6              
7 1     1   4 use base 'Catalyst::Authentication::Realm';
  1         1  
  1         475  
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             normal => { '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_model => '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_model => '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 2     2 1 7 my ( $self, $c, $authinfo ) = @_;
124 2         15 my $realms = $self->config->{realms};
125 2 50       517 carp "No realms to authenticate against, check configuration"
126             unless $realms;
127 2 50       24 carp "Realms configuration must be an array reference"
128             unless ref $realms eq 'ARRAY';
129 2         9 foreach my $realm_name ( @$realms ) {
130 3         16 my $realm = $c->get_auth_realm( $realm_name );
131 3 50       103 carp "Unable to find realm: $realm_name, check configuration"
132             unless $realm;
133 3         23 my $auth = { %$authinfo };
134 3   33     28 $auth->{realm} ||= $realm->name;
135 3 50       583 if ( my $info = $self->config->{authinfo_munge}->{$realm->name} ) {
136 0         0 $auth = Catalyst::Utils::merge_hashes($auth, $info);
137             }
138 3 100       962 if ( my $obj = $realm->authenticate( $c, $auth ) ) {
139 2         10 $c->set_authenticated( $obj, $realm->name );
140 2         45 return $obj;
141             }
142             }
143 0         0 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 1     1 1 4 my ($class, $realmname, $config, $app) = @_;
153              
154 1         3 my $self = { config => $config };
155 1         3 bless $self, $class;
156              
157 1         9 $self->name($realmname);
158 1         5623 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;