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