b4266c6470854f15ceb4433a59e8db9e80811ab7
[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->{id} = $authinfo->{ $self->username_field } = $usr;
109     $authinfo->{remote_user} = $remuser; # just to keep the original value
110     my $user_obj = $realm->find_user( $authinfo, $c );
111     return ref($user_obj) ? $user_obj : undef;
112 }
113
114 1;
115
116 __END__
117
118 =pod
119
120 =head1 NAME
121
122 Catalyst::Authentication::Credential::Remote - Let the webserver (e.g. Apache)
123 authenticate Catalyst application users
124
125 =head1 SYNOPSIS
126
127     # in your MyApp.pm
128     __PACKAGE__->config(
129
130         'Plugin::Authentication' => {
131             default_realm => 'remoterealm',
132             realms => {
133                 remoterealm => {
134                     credential => {
135                         class        => 'Remote',
136                         allow_regexp => '^(user.*|admin|guest)$',
137                         deny_regexp  => 'test',
138                     },
139                     store => {
140                         class => 'Null',
141                         # if you want to have some additional user attributes
142                         # like user roles, user full name etc. you can specify
143                         # here the store where you keep this data
144                     }
145                 },
146             },
147         },
148         
149     );
150     
151     # in your Controller/Root.pm you can implement "auto-login" in this way
152     sub begin : Private {
153         my ( $self, $c ) = @_;        
154         unless ($c->user_exists) {
155             # authenticate() for this module does not need any user info
156             # as the username is taken from $c->req->remote_user and
157             # password is not needed     
158             unless ($c->authenticate( {} )) {
159               # return 403 forbidden or kick out the user in other way
160             };
161         }   
162     }
163
164     # or you can implement in any controller an ordinary login action like this
165     sub login : Global {
166         my ( $self, $c ) = @_;
167         $c->authenticate( {} );
168     }
169
170 =head1 DESCRIPTION
171
172 This module allows you to authenticate the users of your Catalyst application
173 on underlaying webserver. The complete list of authentication method available 
174 via this module depends just on what your webserver (e.g. Apache, IIS, Lighttpd)
175 is able to handle.
176
177 Besides the common methods like HTTP Basic and Digest authentication you can
178 also use sophisticated ones like so called "integrated authentication" via
179 NTLM or Kerberos (popular in corporate intranet applications running in Windows
180 Active Directory enviroment) or even the SSL authentication when users 
181 authenticate themself using their client SSL certificates.   
182
183 The main idea of this module is based on a fact that webserver passes the name
184 of authenticated user into Catalyst application as REMOTE_USER variable (or in 
185 case of SSL client authentication in other variables like SSL_CLIENT_S_DN on
186 Apache + mod_ssl) - from this point referenced as WEBUSER. 
187 This module simply takes this value - perfoms some optional checks (see
188 below) - and if everything is OK the WEBUSER is declared as authenticated on 
189 Catalyst level. In fact this module does not perform any check for password or 
190 other credential; it simply believes the webserver that user was properly 
191 authenticated.
192
193 =head1 CONFIG
194
195 =head2 class
196
197 This config item is B<REQUIRED>. 
198
199 B<class> is part of the core L<Catalyst::Plugin::Authentication> module, it 
200 contains the class name of the store to be used.
201
202 The classname used for Credential. This is part of L<Catalyst::Plugin::Authentication>
203 and is the method by which Catalyst::Authentication::Credential::Remote is
204 loaded as the credential validator. For this module to be used, this must be set
205 to 'Remote'.
206
207 =head2 source
208
209 This config item is B<OPTIONAL> - default is REMOTE_USER.
210
211 B<source> contains a name of a variable passed from webserver that contains the 
212 user identification.
213
214 Supported values: REMOTE_USER, SSL_CLIENT_*, CERT_*, AUTH_USER
215
216 B<BEWARE:> Support for using different variables than REMOTE_USER does not work 
217 properly with Catalyst 5.8004 and before (if you want details see source code). 
218
219 Note1: Apache + mod_ssl uses SSL_CLIENT_S_DN, SSL_CLIENT_S_DN_* etc. (has to be 
220 enabled by 'SSLOption +StdEnvVars') or you can also let Apache make a copy of 
221 this value into REMOTE_USER (Apache option 'SSLUserName SSL_CLIENT_S_DN'). 
222
223 Note2: Microsoft IIS uses CERT_SUBJECT, CERT_SERIALNUMBER etc. for storing info
224 about client authenticated via SSL certificate. AUTH_USER on IIS seems to have
225 the same value as REMOTE_USER (but there might be some differences I am not
226 aware of).
227
228 =head2 deny_regexp
229
230 This config item is B<OPTIONAL> - no default value.
231
232 B<deny_regexp> contains a regular expression used for check against WEBUSER 
233 (see details below)
234
235 =head2 allow_regexp
236
237 This config item is B<OPTIONAL> - no default value.
238
239 B<deny_regexp> contains a regular expression used for check against WEBUSER.
240
241 Allow/deny checking of WEBUSER values goes in this way:
242
243 1) If B<deny_regexp> is defined and WEBUSER matches deny_regexp then 
244 authentication FAILS otherwise continues with next step. If deny_regexp is not 
245 defined or is an empty string we skip this step.  
246
247 2) If B<allow_regexp> is defined and WEBUSER matches allow_regexp then 
248 authentication PASSES otherwise FAILS. If allow_regexp is not 
249 defined or is an empty string we skip this step.
250
251 The order deny-allow is fixed.
252
253 =head2 cutname_regexp
254
255 This config item is B<OPTIONAL> - no default value.
256
257 If param B<cutname_regexp> is specified we try to cut the final usename passed to
258 Catalyst application as a substring from WEBUSER. This is usefull for 
259 example in case of SSL authentication when WEBUSER looks like this 
260 'CN=john, OU=Unit Name, O=Company, C=CZ' - from this format we can simply cut
261 pure usename by cutname_regexp set to 'CN=(.*), OU=Unit Name, O=Company, C=CZ'.
262
263 Substring is always taken as '$1' regexp substring. If WEBUSER does not
264 match cutname_regexp at all or if '$1' regexp substring is empty we pass the
265 original WEBUSER value (without cutting) to Catalyst application.
266
267 =head2 username_field
268
269 This config item is B<OPTIONAL> - default is I<username>
270
271 The key name in the authinfo hash that the user's username is mapped into.
272 This is useful for using a store which requires a specific unusual field name
273 for the username.  The username is additionally mapped onto the I<id> key.
274
275 =head1 METHODS
276
277 =head2 new ( $config, $app, $realm )
278
279 Instantiate a new Catalyst::Authentication::Credential::Remote object using the
280 configuration hash provided in $config. In case of invalid value of any 
281 configuration parameter (e.g. invalid regular expression) throws an exception.
282
283 =cut
284
285 =head2 authenticate ( $realm, $authinfo )
286
287 Takes the username form WEBUSER set by webserver, performs additional 
288 checks using optional allow_regexp/deny_regexp configuration params, optionaly 
289 takes substring from WEBUSER and the sets the resulting value as
290 a Catalyst username.
291
292 =cut
293
294 =head1 COMPATIBILITY
295
296 It is B<strongly recommended> to use this module with Catalyst 5.80005 and above
297 as previous versions have some bugs related to $c->engine->env and do not 
298 support $c->req->remote_user.
299
300 This module tries some workarounds when it detects an older version and should
301 work as well.
302
303 =cut