Commit | Line | Data |
b94c4996 |
1 | package Catalyst::Authentication::Credential::Remote; |
2 | |
3 | use strict; |
4 | use warnings; |
8f57bf96 |
5 | use Try::Tiny qw/ try catch /; |
b94c4996 |
6 | |
7 | use base 'Class::Accessor::Fast'; |
8 | |
9 | BEGIN { |
7289cea0 |
10 | __PACKAGE__->mk_accessors( |
11 | qw/allow_re deny_re cutname_re source realm username_field/); |
b94c4996 |
12 | } |
13 | |
14 | sub 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 | |
49 | sub 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 | |
120 | 1; |
121 | |
122 | __END__ |
123 | |
124 | =pod |
125 | |
126 | =head1 NAME |
127 | |
128 | Catalyst::Authentication::Credential::Remote - Let the webserver (e.g. Apache) |
129 | authenticate 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 | |
178 | This module allows you to authenticate the users of your Catalyst application |
179 | on underlaying webserver. The complete list of authentication method available |
180 | via this module depends just on what your webserver (e.g. Apache, IIS, Lighttpd) |
181 | is able to handle. |
182 | |
183 | Besides the common methods like HTTP Basic and Digest authentication you can |
184 | also use sophisticated ones like so called "integrated authentication" via |
185 | NTLM or Kerberos (popular in corporate intranet applications running in Windows |
e17a338b |
186 | Active Directory environment) or even the SSL authentication when users |
b94c4996 |
187 | authenticate themself using their client SSL certificates. |
188 | |
189 | The main idea of this module is based on a fact that webserver passes the name |
190 | of authenticated user into Catalyst application as REMOTE_USER variable (or in |
191 | case of SSL client authentication in other variables like SSL_CLIENT_S_DN on |
192 | Apache + mod_ssl) - from this point referenced as WEBUSER. |
193 | This module simply takes this value - perfoms some optional checks (see |
194 | below) - and if everything is OK the WEBUSER is declared as authenticated on |
195 | Catalyst level. In fact this module does not perform any check for password or |
196 | other credential; it simply believes the webserver that user was properly |
197 | authenticated. |
198 | |
199 | =head1 CONFIG |
200 | |
201 | =head2 class |
202 | |
203 | This config item is B<REQUIRED>. |
204 | |
205 | B<class> is part of the core L<Catalyst::Plugin::Authentication> module, it |
206 | contains the class name of the store to be used. |
207 | |
208 | The classname used for Credential. This is part of L<Catalyst::Plugin::Authentication> |
209 | and is the method by which Catalyst::Authentication::Credential::Remote is |
210 | loaded as the credential validator. For this module to be used, this must be set |
211 | to 'Remote'. |
212 | |
213 | =head2 source |
214 | |
215 | This config item is B<OPTIONAL> - default is REMOTE_USER. |
216 | |
217 | B<source> contains a name of a variable passed from webserver that contains the |
218 | user identification. |
219 | |
220 | Supported values: REMOTE_USER, SSL_CLIENT_*, CERT_*, AUTH_USER |
221 | |
222 | B<BEWARE:> Support for using different variables than REMOTE_USER does not work |
223 | properly with Catalyst 5.8004 and before (if you want details see source code). |
224 | |
225 | Note1: Apache + mod_ssl uses SSL_CLIENT_S_DN, SSL_CLIENT_S_DN_* etc. (has to be |
226 | enabled by 'SSLOption +StdEnvVars') or you can also let Apache make a copy of |
227 | this value into REMOTE_USER (Apache option 'SSLUserName SSL_CLIENT_S_DN'). |
228 | |
229 | Note2: Microsoft IIS uses CERT_SUBJECT, CERT_SERIALNUMBER etc. for storing info |
230 | about client authenticated via SSL certificate. AUTH_USER on IIS seems to have |
231 | the same value as REMOTE_USER (but there might be some differences I am not |
232 | aware of). |
233 | |
234 | =head2 deny_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 | (see details below) |
240 | |
241 | =head2 allow_regexp |
242 | |
243 | This config item is B<OPTIONAL> - no default value. |
244 | |
245 | B<deny_regexp> contains a regular expression used for check against WEBUSER. |
246 | |
247 | Allow/deny checking of WEBUSER values goes in this way: |
248 | |
249 | 1) If B<deny_regexp> is defined and WEBUSER matches deny_regexp then |
250 | authentication FAILS otherwise continues with next step. If deny_regexp is not |
251 | defined or is an empty string we skip this step. |
252 | |
253 | 2) If B<allow_regexp> is defined and WEBUSER matches allow_regexp then |
254 | authentication PASSES otherwise FAILS. If allow_regexp is not |
255 | defined or is an empty string we skip this step. |
256 | |
257 | The order deny-allow is fixed. |
258 | |
259 | =head2 cutname_regexp |
260 | |
261 | This config item is B<OPTIONAL> - no default value. |
262 | |
263 | If param B<cutname_regexp> is specified we try to cut the final usename passed to |
e17a338b |
264 | Catalyst application as a substring from WEBUSER. This is useful for |
b94c4996 |
265 | example 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 |
267 | pure usename by cutname_regexp set to 'CN=(.*), OU=Unit Name, O=Company, C=CZ'. |
268 | |
269 | Substring is always taken as '$1' regexp substring. If WEBUSER does not |
270 | match cutname_regexp at all or if '$1' regexp substring is empty we pass the |
271 | original WEBUSER value (without cutting) to Catalyst application. |
272 | |
7289cea0 |
273 | =head2 username_field |
274 | |
275 | This config item is B<OPTIONAL> - default is I<username> |
276 | |
277 | The key name in the authinfo hash that the user's username is mapped into. |
278 | This is useful for using a store which requires a specific unusual field name |
279 | for 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 | |
285 | Instantiate a new Catalyst::Authentication::Credential::Remote object using the |
286 | configuration hash provided in $config. In case of invalid value of any |
287 | configuration parameter (e.g. invalid regular expression) throws an exception. |
288 | |
289 | =cut |
290 | |
291 | =head2 authenticate ( $realm, $authinfo ) |
292 | |
293 | Takes the username form WEBUSER set by webserver, performs additional |
294 | checks using optional allow_regexp/deny_regexp configuration params, optionaly |
295 | takes substring from WEBUSER and the sets the resulting value as |
296 | a Catalyst username. |
297 | |
298 | =cut |
299 | |
300 | =head1 COMPATIBILITY |
301 | |
302 | It is B<strongly recommended> to use this module with Catalyst 5.80005 and above |
303 | as previous versions have some bugs related to $c->engine->env and do not |
304 | support $c->req->remote_user. |
305 | |
306 | This module tries some workarounds when it detects an older version and should |
307 | work as well. |
308 | |
309 | =cut |