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