Commit | Line | Data |
b94c4996 |
1 | package Catalyst::Authentication::Credential::Remote; |
2 | |
3 | use strict; |
4 | use warnings; |
5 | |
6 | use base 'Class::Accessor::Fast'; |
7 | |
8 | BEGIN { |
7289cea0 |
9 | __PACKAGE__->mk_accessors( |
10 | qw/allow_re deny_re cutname_re source realm username_field/); |
b94c4996 |
11 | } |
12 | |
13 | sub new { |
14 | my ( $class, $config, $app, $realm ) = @_; |
15 | |
16 | my $self = { }; |
17 | bless $self, $class; |
7289cea0 |
18 | |
b94c4996 |
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); |
0de2d093 |
38 | $self->username_field($config->{username_field} || 'username'); |
b94c4996 |
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: |
0e28e47d |
48 | if ($c->engine->can('env') && defined($c->engine->env)) { |
b94c4996 |
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 | } |
69be7d10 |
107 | |
108 | $authinfo->{ $self->username_field } = $usr; |
b94c4996 |
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 |
e17a338b |
179 | Active Directory environment) or even the SSL authentication when users |
b94c4996 |
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 |
e17a338b |
257 | Catalyst application as a substring from WEBUSER. This is useful for |
b94c4996 |
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 | |
7289cea0 |
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 | |
b94c4996 |
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 |