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