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