Change default username_field back to 'username', as this fits in better with the...
[catagits/Catalyst-Plugin-Authentication.git] / lib / Catalyst / Authentication / Credential / Remote.pm
1 package Catalyst::Authentication::Credential::Remote;
2
3 use strict;
4 use warnings;
5
6 use base 'Class::Accessor::Fast';
7
8 BEGIN {
9     __PACKAGE__->mk_accessors(
10         qw/allow_re deny_re cutname_re source realm username_field/);
11 }
12
13 sub new {
14     my ( $class, $config, $app, $realm ) = @_;
15
16     my $self = { };
17     bless $self, $class;
18
19     # we are gonna compile regular expresions defined in config parameters
20     # and explicitly throw an exception saying what parameter was invalid 
21     if (defined($config->{allow_regexp}) && ($config->{allow_regexp} ne "")) { 
22         eval { $self->allow_re( qr/$config->{allow_regexp}/ ) };
23         Catalyst::Exception->throw( "Invalid regular expression in ".
24         "'allow_regexp' configuration parameter") if $@;
25     }
26     if (defined($config->{deny_regexp}) && ($config->{deny_regexp} ne "")) { 
27         eval { $self->deny_re( qr/$config->{deny_regexp}/ ) };     
28         Catalyst::Exception->throw( "Invalid regular expression in ".
29              "'deny_regexp' configuration parameter") if $@;
30     }
31     if (defined($config->{cutname_regexp}) && ($config->{cutname_regexp} ne "")) { 
32         eval { $self->cutname_re( qr/$config->{cutname_regexp}/ ) };
33         Catalyst::Exception->throw( "Invalid regular expression in ".
34              "'cutname_regexp' configuration parameter") if $@;
35     }
36     $self->source($config->{source} || 'REMOTE_USER');
37     $self->realm($realm);
38     $self->username_field($config->{username_field} || 'username');
39     return $self;
40 }
41
42 sub authenticate {
43     my ( $self, $c, $realm, $authinfo ) = @_;
44
45     my $remuser;
46     if ($self->source eq "REMOTE_USER") {    
47         # compatibility hack:
48         if (defined($c->engine->env)) {
49             # BEWARE: $c->engine->env was broken prior 5.80005
50             $remuser = $c->engine->env->{REMOTE_USER};
51         }
52         elsif ($c->req->can('remote_user')) {
53             # $c->req->remote_users was introduced in 5.80005; if not evailable we are
54             # gonna use $c->req->user that is deprecated but more or less works as well 
55             $remuser = $c->req->remote_user;
56         }
57         elsif ($c->req->can('user')) {
58             # maybe show warning that we are gonna use DEPRECATED $req->user            
59             if (ref($c->req->user)) {
60                 # I do not know exactly when this happens but it happens
61                 Catalyst::Exception->throw( "Cannot get remote user from ".
62                 "\$c->req->user as it seems to be a reference not a string" );
63             }
64             else {
65                 $remuser = $c->req->user;
66             }
67         }
68     }    
69     elsif ($self->source =~ /^(SSL_CLIENT_.*|CERT_*|AUTH_USER)$/) {
70         # if you are using 'exotic' webserver or if the user is 
71         # authenticated e.g via SSL certificate his name could be avaliable
72         # in different variables
73         # BEWARE: $c->engine->env was broken prior 5.80005
74         my $nam=$self->source;
75         if ($c->engine->can('env')) {
76             $remuser = $c->engine->env->{$nam};
77         }
78         else {
79             # this happens on Catalyst 5.80004 and before (when using FastCGI)
80             Catalyst::Exception->throw( "Cannot handle parameter 'source=$nam'".
81                 " as runnig Catalyst engine has broken \$c->engine->env" );
82         }
83     }
84     else {
85         Catalyst::Exception->throw( "Invalid value of 'source' parameter");
86     }
87     return unless defined($remuser);
88     return if ($remuser eq "");
89
90     # $authinfo hash can contain item username (it is optional) - if it is so
91     # this username has to be equal to remote_user 
92     my $authuser = $authinfo->{username};             
93     return if (defined($authuser) && ($authuser ne $remuser));
94
95     # handle deny / allow checks 
96     return if (defined($self->deny_re)  && ($remuser =~ $self->deny_re));
97     return if (defined($self->allow_re) && ($remuser !~ $self->allow_re));
98
99     # if param cutname_regexp is specified we try to cut the final usename as a
100     # substring from remote_user 
101     my $usr = $remuser;
102     if (defined($self->cutname_re)) {
103         if (($remuser =~ $self->cutname_re) && ($1 ne "")) {
104             $usr = $1;
105         }
106     }
107
108     $authinfo->{ $self->username_field } = $usr;
109     my $user_obj = $realm->find_user( $authinfo, $c );
110     return ref($user_obj) ? $user_obj : undef;
111 }
112
113 1;
114
115 __END__
116
117 =pod
118
119 =head1 NAME
120
121 Catalyst::Authentication::Credential::Remote - Let the webserver (e.g. Apache)
122 authenticate Catalyst application users
123
124 =head1 SYNOPSIS
125
126     # in your MyApp.pm
127     __PACKAGE__->config(
128
129         'Plugin::Authentication' => {
130             default_realm => 'remoterealm',
131             realms => {
132                 remoterealm => {
133                     credential => {
134                         class        => 'Remote',
135                         allow_regexp => '^(user.*|admin|guest)$',
136                         deny_regexp  => 'test',
137                     },
138                     store => {
139                         class => 'Null',
140                         # if you want to have some additional user attributes
141                         # like user roles, user full name etc. you can specify
142                         # here the store where you keep this data
143                     }
144                 },
145             },
146         },
147         
148     );
149     
150     # in your Controller/Root.pm you can implement "auto-login" in this way
151     sub begin : Private {
152         my ( $self, $c ) = @_;        
153         unless ($c->user_exists) {
154             # authenticate() for this module does not need any user info
155             # as the username is taken from $c->req->remote_user and
156             # password is not needed     
157             unless ($c->authenticate( {} )) {
158               # return 403 forbidden or kick out the user in other way
159             };
160         }   
161     }
162
163     # or you can implement in any controller an ordinary login action like this
164     sub login : Global {
165         my ( $self, $c ) = @_;
166         $c->authenticate( {} );
167     }
168
169 =head1 DESCRIPTION
170
171 This module allows you to authenticate the users of your Catalyst application
172 on underlaying webserver. The complete list of authentication method available 
173 via this module depends just on what your webserver (e.g. Apache, IIS, Lighttpd)
174 is able to handle.
175
176 Besides the common methods like HTTP Basic and Digest authentication you can
177 also use sophisticated ones like so called "integrated authentication" via
178 NTLM or Kerberos (popular in corporate intranet applications running in Windows
179 Active Directory enviroment) or even the SSL authentication when users 
180 authenticate themself using their client SSL certificates.   
181
182 The main idea of this module is based on a fact that webserver passes the name
183 of authenticated user into Catalyst application as REMOTE_USER variable (or in 
184 case of SSL client authentication in other variables like SSL_CLIENT_S_DN on
185 Apache + mod_ssl) - from this point referenced as WEBUSER. 
186 This module simply takes this value - perfoms some optional checks (see
187 below) - and if everything is OK the WEBUSER is declared as authenticated on 
188 Catalyst level. In fact this module does not perform any check for password or 
189 other credential; it simply believes the webserver that user was properly 
190 authenticated.
191
192 =head1 CONFIG
193
194 =head2 class
195
196 This config item is B<REQUIRED>. 
197
198 B<class> is part of the core L<Catalyst::Plugin::Authentication> module, it 
199 contains the class name of the store to be used.
200
201 The classname used for Credential. This is part of L<Catalyst::Plugin::Authentication>
202 and is the method by which Catalyst::Authentication::Credential::Remote is
203 loaded as the credential validator. For this module to be used, this must be set
204 to 'Remote'.
205
206 =head2 source
207
208 This config item is B<OPTIONAL> - default is REMOTE_USER.
209
210 B<source> contains a name of a variable passed from webserver that contains the 
211 user identification.
212
213 Supported values: REMOTE_USER, SSL_CLIENT_*, CERT_*, AUTH_USER
214
215 B<BEWARE:> Support for using different variables than REMOTE_USER does not work 
216 properly with Catalyst 5.8004 and before (if you want details see source code). 
217
218 Note1: Apache + mod_ssl uses SSL_CLIENT_S_DN, SSL_CLIENT_S_DN_* etc. (has to be 
219 enabled by 'SSLOption +StdEnvVars') or you can also let Apache make a copy of 
220 this value into REMOTE_USER (Apache option 'SSLUserName SSL_CLIENT_S_DN'). 
221
222 Note2: Microsoft IIS uses CERT_SUBJECT, CERT_SERIALNUMBER etc. for storing info
223 about client authenticated via SSL certificate. AUTH_USER on IIS seems to have
224 the same value as REMOTE_USER (but there might be some differences I am not
225 aware of).
226
227 =head2 deny_regexp
228
229 This config item is B<OPTIONAL> - no default value.
230
231 B<deny_regexp> contains a regular expression used for check against WEBUSER 
232 (see details below)
233
234 =head2 allow_regexp
235
236 This config item is B<OPTIONAL> - no default value.
237
238 B<deny_regexp> contains a regular expression used for check against WEBUSER.
239
240 Allow/deny checking of WEBUSER values goes in this way:
241
242 1) If B<deny_regexp> is defined and WEBUSER matches deny_regexp then 
243 authentication FAILS otherwise continues with next step. If deny_regexp is not 
244 defined or is an empty string we skip this step.  
245
246 2) If B<allow_regexp> is defined and WEBUSER matches allow_regexp then 
247 authentication PASSES otherwise FAILS. If allow_regexp is not 
248 defined or is an empty string we skip this step.
249
250 The order deny-allow is fixed.
251
252 =head2 cutname_regexp
253
254 This config item is B<OPTIONAL> - no default value.
255
256 If param B<cutname_regexp> is specified we try to cut the final usename passed to
257 Catalyst application as a substring from WEBUSER. This is usefull for 
258 example in case of SSL authentication when WEBUSER looks like this 
259 'CN=john, OU=Unit Name, O=Company, C=CZ' - from this format we can simply cut
260 pure usename by cutname_regexp set to 'CN=(.*), OU=Unit Name, O=Company, C=CZ'.
261
262 Substring is always taken as '$1' regexp substring. If WEBUSER does not
263 match cutname_regexp at all or if '$1' regexp substring is empty we pass the
264 original WEBUSER value (without cutting) to Catalyst application.
265
266 =head2 username_field
267
268 This config item is B<OPTIONAL> - default is I<username>
269
270 The key name in the authinfo hash that the user's username is mapped into.
271 This is useful for using a store which requires a specific unusual field name
272 for the username.  The username is additionally mapped onto the I<id> key.
273
274 =head1 METHODS
275
276 =head2 new ( $config, $app, $realm )
277
278 Instantiate a new Catalyst::Authentication::Credential::Remote object using the
279 configuration hash provided in $config. In case of invalid value of any 
280 configuration parameter (e.g. invalid regular expression) throws an exception.
281
282 =cut
283
284 =head2 authenticate ( $realm, $authinfo )
285
286 Takes the username form WEBUSER set by webserver, performs additional 
287 checks using optional allow_regexp/deny_regexp configuration params, optionaly 
288 takes substring from WEBUSER and the sets the resulting value as
289 a Catalyst username.
290
291 =cut
292
293 =head1 COMPATIBILITY
294
295 It is B<strongly recommended> to use this module with Catalyst 5.80005 and above
296 as previous versions have some bugs related to $c->engine->env and do not 
297 support $c->req->remote_user.
298
299 This module tries some workarounds when it detects an older version and should
300 work as well.
301
302 =cut