line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Catalyst::Authentication::Store::LDAP; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
1426705
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
38
|
|
4
|
1
|
|
|
1
|
|
4
|
use warnings; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
44
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
our $VERSION = '1.015'; |
7
|
|
|
|
|
|
|
|
8
|
1
|
|
|
1
|
|
416
|
use Catalyst::Authentication::Store::LDAP::Backend; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
11
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
sub new { |
11
|
0
|
|
|
0
|
1
|
|
my ( $class, $config, $app ) = @_; |
12
|
0
|
|
|
|
|
|
return Catalyst::Authentication::Store::LDAP::Backend->new( |
13
|
|
|
|
|
|
|
$config); |
14
|
|
|
|
|
|
|
} |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
1; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
__END__ |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=pod |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=head1 NAME |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
Catalyst::Authentication::Store::LDAP |
25
|
|
|
|
|
|
|
- Authentication from an LDAP Directory. |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head1 SYNOPSIS |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
use Catalyst qw( |
30
|
|
|
|
|
|
|
Authentication |
31
|
|
|
|
|
|
|
); |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
__PACKAGE__->config( |
34
|
|
|
|
|
|
|
'authentication' => { |
35
|
|
|
|
|
|
|
default_realm => "ldap", |
36
|
|
|
|
|
|
|
realms => { |
37
|
|
|
|
|
|
|
ldap => { |
38
|
|
|
|
|
|
|
credential => { |
39
|
|
|
|
|
|
|
class => "Password", |
40
|
|
|
|
|
|
|
password_field => "password", |
41
|
|
|
|
|
|
|
password_type => "self_check", |
42
|
|
|
|
|
|
|
}, |
43
|
|
|
|
|
|
|
store => { |
44
|
|
|
|
|
|
|
binddn => "anonymous", |
45
|
|
|
|
|
|
|
bindpw => "dontcarehow", |
46
|
|
|
|
|
|
|
class => "LDAP", |
47
|
|
|
|
|
|
|
ldap_server => "ldap.yourcompany.com", |
48
|
|
|
|
|
|
|
ldap_server_options => { timeout => 30 }, |
49
|
|
|
|
|
|
|
role_basedn => "ou=groups,ou=OxObjects,dc=yourcompany,dc=com", |
50
|
|
|
|
|
|
|
role_field => "uid", |
51
|
|
|
|
|
|
|
role_filter => "(&(objectClass=posixGroup)(memberUid=%s))", |
52
|
|
|
|
|
|
|
role_scope => "one", |
53
|
|
|
|
|
|
|
role_search_options => { deref => "always" }, |
54
|
|
|
|
|
|
|
role_value => "dn", |
55
|
|
|
|
|
|
|
role_search_as_user => 0, |
56
|
|
|
|
|
|
|
start_tls => 1, |
57
|
|
|
|
|
|
|
start_tls_options => { verify => "none" }, |
58
|
|
|
|
|
|
|
entry_class => "MyApp::LDAP::Entry", |
59
|
|
|
|
|
|
|
use_roles => 1, |
60
|
|
|
|
|
|
|
user_basedn => "ou=people,dc=yourcompany,dc=com", |
61
|
|
|
|
|
|
|
user_field => "uid", |
62
|
|
|
|
|
|
|
user_filter => "(&(objectClass=posixAccount)(uid=%s))", |
63
|
|
|
|
|
|
|
user_scope => "one", # or "sub" for Active Directory |
64
|
|
|
|
|
|
|
user_search_options => { deref => "always" }, |
65
|
|
|
|
|
|
|
user_results_filter => sub { return shift->pop_entry }, |
66
|
|
|
|
|
|
|
}, |
67
|
|
|
|
|
|
|
}, |
68
|
|
|
|
|
|
|
}, |
69
|
|
|
|
|
|
|
}, |
70
|
|
|
|
|
|
|
); |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
sub login : Global { |
73
|
|
|
|
|
|
|
my ( $self, $c ) = @_; |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
$c->authenticate({ |
76
|
|
|
|
|
|
|
id => $c->req->param("login"), |
77
|
|
|
|
|
|
|
password => $c->req->param("password") |
78
|
|
|
|
|
|
|
}); |
79
|
|
|
|
|
|
|
$c->res->body("Welcome " . $c->user->username . "!"); |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=head1 DESCRIPTION |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
This plugin implements the L<Catalyst::Authentication> v.10 API. Read that documentation first if |
85
|
|
|
|
|
|
|
you are upgrading from a previous version of this plugin. |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
This plugin uses C<Net::LDAP> to let your application authenticate against |
88
|
|
|
|
|
|
|
an LDAP directory. It has a pretty high degree of flexibility, given the |
89
|
|
|
|
|
|
|
wide variation of LDAP directories and schemas from one system to another. |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
It authenticates users in two steps: |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
1) A search of the directory is performed, looking for a user object that |
94
|
|
|
|
|
|
|
matches the username you pass. This is done with the bind credentials |
95
|
|
|
|
|
|
|
supplied in the "binddn" and "bindpw" configuration options. |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
2) If that object is found, we then re-bind to the directory as that object. |
98
|
|
|
|
|
|
|
Assuming this is successful, the user is Authenticated. |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
=head1 CONFIGURATION OPTIONS |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
=head2 Configuring with YAML |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
Set Configuration to be loaded via Config.yml in YourApp.pm |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
use YAML qw(LoadFile); |
107
|
|
|
|
|
|
|
use Path::Class 'file'; |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
__PACKAGE__->config( |
110
|
|
|
|
|
|
|
LoadFile( |
111
|
|
|
|
|
|
|
file(__PACKAGE__->config->{home}, 'Config.yml') |
112
|
|
|
|
|
|
|
) |
113
|
|
|
|
|
|
|
); |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
Settings in Config.yml (adapt these to whatever configuration format you use): |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
# Config for Store::LDAP |
118
|
|
|
|
|
|
|
authentication: |
119
|
|
|
|
|
|
|
default_realm: ldap |
120
|
|
|
|
|
|
|
realms: |
121
|
|
|
|
|
|
|
ldap: |
122
|
|
|
|
|
|
|
credential: |
123
|
|
|
|
|
|
|
class: Password |
124
|
|
|
|
|
|
|
password_field: password |
125
|
|
|
|
|
|
|
password_type: self_check |
126
|
|
|
|
|
|
|
store: |
127
|
|
|
|
|
|
|
class: LDAP |
128
|
|
|
|
|
|
|
ldap_server: ldap.yourcompany.com |
129
|
|
|
|
|
|
|
ldap_server_options: |
130
|
|
|
|
|
|
|
timeout: 30 |
131
|
|
|
|
|
|
|
binddn: anonymous |
132
|
|
|
|
|
|
|
bindpw: dontcarehow |
133
|
|
|
|
|
|
|
start_tls: 1 |
134
|
|
|
|
|
|
|
start_tls_options: |
135
|
|
|
|
|
|
|
verify: none |
136
|
|
|
|
|
|
|
user_basedn: ou=people,dc=yourcompany,dc=com |
137
|
|
|
|
|
|
|
user_filter: (&(objectClass=posixAccount)(uid=%s)) |
138
|
|
|
|
|
|
|
user_scope: one |
139
|
|
|
|
|
|
|
user_field: uid |
140
|
|
|
|
|
|
|
user_search_options: |
141
|
|
|
|
|
|
|
deref: always |
142
|
|
|
|
|
|
|
use_roles: 1 |
143
|
|
|
|
|
|
|
role_basedn: ou=groups,ou=OxObjects,dc=yourcompany,dc=com |
144
|
|
|
|
|
|
|
role_filter: (&(objectClass=posixGroup)(memberUid=%s)) |
145
|
|
|
|
|
|
|
role_scope: one |
146
|
|
|
|
|
|
|
role_field: uid |
147
|
|
|
|
|
|
|
role_value: dn |
148
|
|
|
|
|
|
|
role_search_options: |
149
|
|
|
|
|
|
|
deref: always |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
B<NOTE:> The settings above reflect the default values for OpenLDAP. If you |
153
|
|
|
|
|
|
|
are using Active Directory instead, Matija Grabnar suggests that the following |
154
|
|
|
|
|
|
|
tweeks to the example configuration will work: |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
user_basedn: ou=Domain Users,ou=Accounts,dc=mycompany,dc=com |
157
|
|
|
|
|
|
|
user_field: samaccountname |
158
|
|
|
|
|
|
|
user_filter: (sAMAccountName=%s) |
159
|
|
|
|
|
|
|
user_scope: sub |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
He also notes: "I found the case in the value of user_field to be significant: |
162
|
|
|
|
|
|
|
it didn't seem to work when I had the mixed case value there." |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
=head2 ldap_server |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
This should be the hostname of your LDAP server. |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=head2 ldap_server_options |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
This should be a hashref containing options to pass to L<Net::LDAP>->new(). |
171
|
|
|
|
|
|
|
See L<Net::LDAP> for the full list. |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
=head2 binddn |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
This should be the DN of the object you wish to bind to the directory as |
176
|
|
|
|
|
|
|
during the first phase of authentication. (The user lookup phase) |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
If you supply the value "anonymous" to this option, we will bind anonymously |
179
|
|
|
|
|
|
|
to the directory. This is the default. |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
=head2 bindpw |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
This is the password for the initial bind. |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
=head2 start_tls |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
If this is set to 1, we will convert the LDAP connection to use SSL. |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=head2 start_tls_options |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
This is a hashref, which contains the arguments to the L<Net::LDAP> start_tls |
192
|
|
|
|
|
|
|
method. See L<Net::LDAP> for the complete list of options. |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=head2 user_basedn |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
This is the basedn for the initial user lookup. Usually points to the |
197
|
|
|
|
|
|
|
top of your "users" branch; ie "ou=people,dc=yourcompany,dc=com". |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=head2 user_filter |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
This is the LDAP Search filter used during user lookup. The special string |
202
|
|
|
|
|
|
|
'%s' will be replaced with the username you pass to $c->login. By default |
203
|
|
|
|
|
|
|
it is set to '(uid=%s)'. Other possibly useful filters: |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
(&(objectClass=posixAccount)(uid=%s)) |
206
|
|
|
|
|
|
|
(&(objectClass=User)(cn=%s)) |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=head2 user_scope |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
This specifies the scope of the search for the initial user lookup. Valid |
211
|
|
|
|
|
|
|
values are "base", "one", and "sub". Defaults to "sub". |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
=head2 user_field |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
This is the attribute of the returned LDAP object we will use for their |
216
|
|
|
|
|
|
|
"username". This defaults to "uid". If you had user_filter set to: |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
(&(objectClass=User)(cn=%s)) |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
You would probably set this to "cn". You can also set it to an array, |
221
|
|
|
|
|
|
|
to allow more than one login field. The first field will be returned |
222
|
|
|
|
|
|
|
as identifier for the user. |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
=head2 user_search_options |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
This takes a hashref. It will append it's values to the call to |
227
|
|
|
|
|
|
|
L<Net::LDAP>'s "search" method during the initial user lookup. See |
228
|
|
|
|
|
|
|
L<Net::LDAP> for valid options. |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
Be careful not to specify: |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
filter |
233
|
|
|
|
|
|
|
scope |
234
|
|
|
|
|
|
|
base |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
As they are already taken care of by other configuration options. |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=head2 user_results_filter |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
This is a Perl CODE ref that can be used to filter out multiple results |
241
|
|
|
|
|
|
|
from your LDAP query. In theory, your LDAP query should only return one result |
242
|
|
|
|
|
|
|
and find_user() will throw an exception if it encounters more than one result. |
243
|
|
|
|
|
|
|
However, if you have, for whatever reason, a legitimate reason for returning |
244
|
|
|
|
|
|
|
multiple search results from your LDAP query, use C<user_results_filter> to filter |
245
|
|
|
|
|
|
|
out the LDAP entries you do not want considered. Your CODE ref should expect |
246
|
|
|
|
|
|
|
a single argument, a Net::LDAP::Search object, and it should return exactly one |
247
|
|
|
|
|
|
|
value, a Net::LDAP::Entry object. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
Example: |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
user_results_filter => sub { |
252
|
|
|
|
|
|
|
my $search_obj = shift; |
253
|
|
|
|
|
|
|
foreach my $entry ($search_obj->entries) { |
254
|
|
|
|
|
|
|
return $entry if my_match_logic( $entry ); |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
return undef; # i.e., no match |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
=head2 use_roles |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
Whether or not to enable role lookups. It defaults to true; set it to 0 if |
262
|
|
|
|
|
|
|
you want to always avoid role lookups. |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
=head2 role_basedn |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
This should be the basedn where the LDAP Objects representing your roles are. |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
=head2 role_filter |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
This should be the LDAP Search filter to use during the role lookup. It |
271
|
|
|
|
|
|
|
defaults to '(memberUid=%s)'. The %s in this filter is replaced with the value |
272
|
|
|
|
|
|
|
of the "role_value" configuration option. |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
So, if you had a role_value of "cn", then this would be populated with the cn |
275
|
|
|
|
|
|
|
of the User's LDAP object. The special case is a role_value of "dn", which |
276
|
|
|
|
|
|
|
will be replaced with the User's DN. |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
=head2 role_scope |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
This specifies the scope of the search for the user's role lookup. Valid |
281
|
|
|
|
|
|
|
values are "base", "one", and "sub". Defaults to "sub". |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
=head2 role_field |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
Should be set to the Attribute of the Role Object's returned during Role lookup you want to use as the "name" of the role. Defaults to "CN". |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
=head2 role_value |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
This is the attribute of the User object we want to use in our role_filter. |
290
|
|
|
|
|
|
|
If this is set to "dn", we will use the User Objects DN. |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
=head2 role_search_options |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
This takes a hashref. It will append it's values to the call to |
295
|
|
|
|
|
|
|
L<Net::LDAP>'s "search" method during the user's role lookup. See |
296
|
|
|
|
|
|
|
L<Net::LDAP> for valid options. |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
Be careful not to specify: |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
filter |
301
|
|
|
|
|
|
|
scope |
302
|
|
|
|
|
|
|
base |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
As they are already taken care of by other configuration options. |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
=head2 role_search_as_user |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
By default this setting is false, and the role search will be performed |
309
|
|
|
|
|
|
|
by binding to the directory with the details in the I<binddn> and I<bindpw> |
310
|
|
|
|
|
|
|
fields. If this is set to false, then the role search will instead be |
311
|
|
|
|
|
|
|
performed when bound as the user you authenticated as. |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
=head2 entry_class |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
The name of the class of LDAP entries returned. This class should |
316
|
|
|
|
|
|
|
exist and is expected to be a subclass of Net::LDAP::Entry |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=head2 user_class |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
The name of the class of user object returned. By default, this is |
321
|
|
|
|
|
|
|
L<Catalyst::Authentication::Store::LDAP::User>. |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
=head1 METHODS |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
=head2 new |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
This method will populate |
328
|
|
|
|
|
|
|
L<Catalyst::Plugin::Authentication/default_auth_store> with this object. |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
=head1 AUTHORS |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
Adam Jacob <holoway@cpan.org> |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
Some parts stolen shamelessly and entirely from |
335
|
|
|
|
|
|
|
L<Catalyst::Plugin::Authentication::Store::Htpasswd>. |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
Currently maintained by Peter Karman <karman@cpan.org>. |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
=head1 THANKS |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
To nothingmuch, ghenry, castaway and the rest of #catalyst for the help. :) |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
=head1 SEE ALSO |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
L<Catalyst::Authentication::Store::LDAP>, |
346
|
|
|
|
|
|
|
L<Catalyst::Authentication::Store::LDAP::User>, |
347
|
|
|
|
|
|
|
L<Catalyst::Authentication::Store::LDAP::Backend>, |
348
|
|
|
|
|
|
|
L<Catalyst::Plugin::Authentication>, |
349
|
|
|
|
|
|
|
L<Net::LDAP> |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
Copyright (c) 2005 the aforementioned authors. All rights |
354
|
|
|
|
|
|
|
reserved. This program is free software; you can redistribute |
355
|
|
|
|
|
|
|
it and/or modify it under the same terms as Perl itself. |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=cut |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
|