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); |
7289cea0 |
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: |
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 | |
7289cea0 |
108 | $authinfo->{id} = $authinfo->{ $self->username_field } = $usr; |
b94c4996 |
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 | |
7289cea0 |
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 | |
b94c4996 |
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 |