line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
|
use Carp qw/croak/; |
3
|
1
|
|
|
1
|
|
945254
|
use Dancer2::Core::Types qw/HashRef Str/; |
|
1
|
|
|
|
|
12
|
|
|
1
|
|
|
|
|
59
|
|
4
|
1
|
|
|
1
|
|
455
|
use Net::LDAP; |
|
1
|
|
|
|
|
200561
|
|
|
1
|
|
|
|
|
10
|
|
5
|
1
|
|
|
1
|
|
3995
|
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
12
|
|
6
|
|
|
|
|
|
|
use Moo; |
7
|
1
|
|
|
1
|
|
619
|
with "Dancer2::Plugin::Auth::Extensible::Role::Provider"; |
|
1
|
|
|
|
|
6917
|
|
|
1
|
|
|
|
|
6
|
|
8
|
|
|
|
|
|
|
use namespace::clean; |
9
|
1
|
|
|
1
|
|
1534
|
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
11
|
|
10
|
|
|
|
|
|
|
our $VERSION = '0.706'; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 NAME |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
Dancer2::Plugin::Auth::Extensible::Provider::LDAP - LDAP authentication provider for Dancer2::Plugin::Auth::Extensible |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
=head1 DESCRIPTION |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
This class is a generic LDAP authentication provider. |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
See L<Dancer2::Plugin::Auth::Extensible> for details on how to use the |
21
|
|
|
|
|
|
|
authentication framework. |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head2 host |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
The LDAP host name or IP address passed to L<Net::LDAP/CONSTRUCTOR>. |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
Required. |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
=cut |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
has host => ( |
34
|
|
|
|
|
|
|
is => 'ro', |
35
|
|
|
|
|
|
|
isa => Str, |
36
|
|
|
|
|
|
|
required => 1, |
37
|
|
|
|
|
|
|
); |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
=head2 options |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
Extra options to be passed to L<Net::LDAP/CONSTRUCTOR> as a hash reference. |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
=cut |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
has options => ( |
46
|
|
|
|
|
|
|
is => 'ro', |
47
|
|
|
|
|
|
|
isa => HashRef, |
48
|
|
|
|
|
|
|
default => sub { +{} }, |
49
|
|
|
|
|
|
|
); |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=head2 basedn |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
The base dn for all searches (e.g. 'dc=example,dc=com'). |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
Required. |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
=cut |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
has basedn => ( |
60
|
|
|
|
|
|
|
is => 'ro', |
61
|
|
|
|
|
|
|
isa => Str, |
62
|
|
|
|
|
|
|
required => 1, |
63
|
|
|
|
|
|
|
); |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
=head2 binddn |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
This must be the distinguished name of a user capable of binding to |
68
|
|
|
|
|
|
|
and reading the directory (e.g. 'cn=admin,dc=example,dc=com'). |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Not required, as some LDAP setups allow for anonymous binding. |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=cut |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
has binddn => ( |
75
|
|
|
|
|
|
|
is => 'ro', |
76
|
|
|
|
|
|
|
isa => Str, |
77
|
|
|
|
|
|
|
required => 0, |
78
|
|
|
|
|
|
|
); |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=head2 bindpw |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
The password for L</binddn>. |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
Not required, as some LDAP setups allow for anonymous binding. |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=cut |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
has bindpw => ( |
89
|
|
|
|
|
|
|
is => 'ro', |
90
|
|
|
|
|
|
|
isa => Str, |
91
|
|
|
|
|
|
|
required => 0, |
92
|
|
|
|
|
|
|
); |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
=head2 ldap |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
Returns a connected L<Net::LDAP> object. |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
=cut |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
has ldap => ( |
101
|
|
|
|
|
|
|
is => 'lazy', |
102
|
|
|
|
|
|
|
clearer => '_clear_ldap', |
103
|
|
|
|
|
|
|
predicate => '_has_ldap', |
104
|
|
|
|
|
|
|
); |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
my $self = shift; |
107
|
|
|
|
|
|
|
my $ldap = Net::LDAP->new( $self->host, %{ $self->options } ) |
108
|
0
|
|
|
0
|
|
0
|
or croak "LDAP connect failed for: " . $self->host; |
109
|
0
|
0
|
|
|
|
0
|
return $ldap; |
|
0
|
|
|
|
|
0
|
|
110
|
|
|
|
|
|
|
} |
111
|
0
|
|
|
|
|
0
|
|
112
|
|
|
|
|
|
|
=head2 username_attribute |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
The attribute to match when searching for a username. |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
Defaults to 'cn'. |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=cut |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
has username_attribute => ( |
121
|
|
|
|
|
|
|
is => 'ro', |
122
|
|
|
|
|
|
|
isa => Str, |
123
|
|
|
|
|
|
|
default => 'cn', |
124
|
|
|
|
|
|
|
); |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=head2 name_attribute |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
The attribute which contains the full name of the user. See also: |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
L<Dancer2::Plugin::Auth::Extensible::Role::User/name>. |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
Defaults to 'displayName'. |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=cut |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
has name_attribute => ( |
137
|
|
|
|
|
|
|
is => 'ro', |
138
|
|
|
|
|
|
|
isa => Str, |
139
|
|
|
|
|
|
|
default => 'displayName', |
140
|
|
|
|
|
|
|
); |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=head2 user_filter |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
Filter used when searching for users. |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
Defaults to '(objectClass=person)'. |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=cut |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
has user_filter => ( |
151
|
|
|
|
|
|
|
is => 'ro', |
152
|
|
|
|
|
|
|
isa => Str, |
153
|
|
|
|
|
|
|
default => '(objectClass=person)', |
154
|
|
|
|
|
|
|
); |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
=head2 role_attribute |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
The attribute used when searching for role names. |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
Defaults to 'cn'. |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=cut |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
has role_attribute => ( |
165
|
|
|
|
|
|
|
is => 'ro', |
166
|
|
|
|
|
|
|
isa => Str, |
167
|
|
|
|
|
|
|
default => 'cn', |
168
|
|
|
|
|
|
|
); |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=head2 role_filter |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
Filter used when searching for roles. |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
Defaults to '(objectClass=groupOfNames)' |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=cut |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
has role_filter => ( |
179
|
|
|
|
|
|
|
is => 'ro', |
180
|
|
|
|
|
|
|
isa => Str, |
181
|
|
|
|
|
|
|
default => '(objectClass=groupOfNames)', |
182
|
|
|
|
|
|
|
); |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=head2 role_member_attribute_name |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
The attribute of a user object who's value should be the value used to identify |
187
|
|
|
|
|
|
|
which roles a specific user is a member of. |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
Defaults to 'dn' |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=cut |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
has role_member_attribute_name => ( |
194
|
|
|
|
|
|
|
is => 'ro', |
195
|
|
|
|
|
|
|
isa => Str, |
196
|
|
|
|
|
|
|
default => 'dn', |
197
|
|
|
|
|
|
|
); |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=head2 role_member_attribute |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
The attribute of a role object who's value should be the value of a user's |
202
|
|
|
|
|
|
|
L</role_member_attribute_name> attribute to look up which roles a user is a |
203
|
|
|
|
|
|
|
member of. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
Defaults to 'member'. |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
=cut |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
has role_member_attribute => ( |
210
|
|
|
|
|
|
|
is => 'ro', |
211
|
|
|
|
|
|
|
isa => Str, |
212
|
|
|
|
|
|
|
default => 'member', |
213
|
|
|
|
|
|
|
); |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
my ($self) = @_; |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
return |
218
|
105
|
|
|
105
|
|
268
|
unless $self->_has_ldap; |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
my $ldap = $self->ldap; |
221
|
105
|
50
|
|
|
|
444
|
|
222
|
|
|
|
|
|
|
$ldap->unbind; |
223
|
0
|
|
|
|
|
0
|
$ldap->disconnect; |
224
|
|
|
|
|
|
|
$self->_clear_ldap; |
225
|
0
|
|
|
|
|
0
|
} |
226
|
0
|
|
|
|
|
0
|
|
227
|
0
|
|
|
|
|
0
|
my ( $self, $username, $dummy, $password ) = @_; |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
my $ldap = $self->ldap or return; |
230
|
|
|
|
|
|
|
|
231
|
111
|
|
|
111
|
|
376
|
# If either username or password is defined, ensure we have both, |
232
|
|
|
|
|
|
|
# otherwise we cannot bind to LDAP. Otherwise, assume we are going |
233
|
111
|
50
|
|
|
|
343
|
# to anonymously bind. |
234
|
|
|
|
|
|
|
my $mesg; |
235
|
|
|
|
|
|
|
if( !defined $username && !defined $password ) { |
236
|
|
|
|
|
|
|
$self->plugin->app->log( debug => "Binding to LDAP anonymously" ); |
237
|
|
|
|
|
|
|
$mesg = $ldap->bind; |
238
|
111
|
|
|
|
|
570
|
} |
239
|
111
|
50
|
33
|
|
|
412
|
else { |
240
|
0
|
|
|
|
|
0
|
croak "username and password must be defined" |
241
|
0
|
|
|
|
|
0
|
unless defined $username && defined $password; |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
$self->plugin->app->log( debug => "Binding to LDAP with credentials" ); |
244
|
111
|
50
|
33
|
|
|
609
|
$mesg = $ldap->bind( $username, password => $password ); |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
|
247
|
111
|
|
|
|
|
696
|
return $mesg; |
248
|
111
|
|
|
|
|
60768
|
} |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
=head1 METHODS |
251
|
111
|
|
|
|
|
42814
|
|
252
|
|
|
|
|
|
|
=head2 authenticate_user $username, $password |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
=cut |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
my ( $self, $username, $password ) = @_; |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
croak "username and password must be defined" |
259
|
|
|
|
|
|
|
unless defined $username && defined $password; |
260
|
|
|
|
|
|
|
|
261
|
52
|
|
|
52
|
1
|
1526879
|
my $user = $self->get_user_details($username) or return; |
262
|
|
|
|
|
|
|
|
263
|
52
|
100
|
100
|
|
|
729
|
my $ldap = $self->ldap or return; |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
my $mesg = $self->_bind_ldap( $user->{dn}, password => $password ); |
266
|
49
|
100
|
|
|
|
204
|
|
267
|
|
|
|
|
|
|
$self->_unbind_ldap; |
268
|
14
|
50
|
|
|
|
60
|
|
269
|
|
|
|
|
|
|
return not $mesg->is_error; |
270
|
14
|
|
|
|
|
120
|
} |
271
|
|
|
|
|
|
|
|
272
|
14
|
|
|
|
|
60
|
=head2 get_user_details $username |
273
|
|
|
|
|
|
|
|
274
|
14
|
|
|
|
|
53
|
=cut |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
my ( $self, $username ) = @_; |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
croak "username must be defined" |
279
|
|
|
|
|
|
|
unless defined $username; |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
my $ldap = $self->ldap or return; |
282
|
98
|
|
|
98
|
1
|
401338
|
|
283
|
|
|
|
|
|
|
my $mesg = $self->_bind_ldap( $self->binddn, password => $self->bindpw ); |
284
|
98
|
100
|
|
|
|
528
|
|
285
|
|
|
|
|
|
|
if ( $mesg->is_error ) { |
286
|
|
|
|
|
|
|
croak "LDAP bind error: " . $mesg->error; |
287
|
97
|
50
|
|
|
|
433
|
} |
288
|
|
|
|
|
|
|
|
289
|
97
|
|
|
|
|
1024
|
$mesg = $ldap->search( |
290
|
|
|
|
|
|
|
base => $self->basedn, |
291
|
97
|
50
|
|
|
|
509
|
sizelimit => 1, |
292
|
0
|
|
|
|
|
0
|
filter => '(&' |
293
|
|
|
|
|
|
|
. $self->user_filter |
294
|
|
|
|
|
|
|
. '(' . $self->username_attribute . '=' . $username . '))', |
295
|
97
|
|
|
|
|
2053
|
); |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
if ( $mesg->is_error ) { |
298
|
|
|
|
|
|
|
croak "LDAP search error: " . $mesg->error; |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
my $user; |
302
|
|
|
|
|
|
|
if ( $mesg->count > 0 ) { |
303
|
97
|
100
|
|
|
|
241248
|
my $entry = $mesg->entry(0); |
304
|
6
|
|
|
|
|
73
|
$self->plugin->app->log( |
305
|
|
|
|
|
|
|
debug => "User $username found with DN: ", |
306
|
|
|
|
|
|
|
$entry->dn |
307
|
91
|
|
|
|
|
1102
|
); |
308
|
91
|
100
|
|
|
|
424
|
|
309
|
52
|
|
|
|
|
828
|
# now get the roles |
310
|
52
|
|
|
|
|
3691
|
|
311
|
|
|
|
|
|
|
my $role_member_attribute_value; |
312
|
|
|
|
|
|
|
if ( $self->role_member_attribute_name eq 'dn' ) { |
313
|
|
|
|
|
|
|
$role_member_attribute_value = $entry->dn; |
314
|
|
|
|
|
|
|
} else { |
315
|
|
|
|
|
|
|
$role_member_attribute_value = $entry->get_value( $self->role_member_attribute_name ); |
316
|
|
|
|
|
|
|
} |
317
|
52
|
|
|
|
|
29100
|
$mesg = $ldap->search( |
318
|
52
|
50
|
|
|
|
314
|
base => $self->basedn, |
319
|
52
|
|
|
|
|
216
|
filter => '(&' |
320
|
|
|
|
|
|
|
. $self->role_filter . '(' |
321
|
0
|
|
|
|
|
0
|
. $self->role_member_attribute . '=' |
322
|
|
|
|
|
|
|
. $role_member_attribute_value . '))', |
323
|
52
|
|
|
|
|
798
|
); |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
if ( $mesg->is_error ) { |
326
|
|
|
|
|
|
|
$self->plugin->app->log( |
327
|
|
|
|
|
|
|
warning => "LDAP search error: " . $mesg->error ); |
328
|
|
|
|
|
|
|
} |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
my @roles = |
331
|
52
|
50
|
|
|
|
137932
|
map { $_->get_value( $self->role_attribute ) } $mesg->entries; |
332
|
0
|
|
|
|
|
0
|
|
333
|
|
|
|
|
|
|
$user = { |
334
|
|
|
|
|
|
|
username => $username, |
335
|
|
|
|
|
|
|
name => $entry->get_value( $self->name_attribute ), |
336
|
|
|
|
|
|
|
dn => $entry->dn, |
337
|
52
|
|
|
|
|
748
|
roles => \@roles, |
|
86
|
|
|
|
|
1218
|
|
338
|
|
|
|
|
|
|
map { $_ => scalar $entry->get_value($_) } $entry->attributes, |
339
|
|
|
|
|
|
|
}; |
340
|
|
|
|
|
|
|
} |
341
|
|
|
|
|
|
|
else { |
342
|
|
|
|
|
|
|
$self->plugin->app->log( |
343
|
|
|
|
|
|
|
debug => "User not found via LDAP: $username" ); |
344
|
52
|
|
|
|
|
959
|
} |
|
216
|
|
|
|
|
4112
|
|
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
$self->_unbind_ldap; |
347
|
|
|
|
|
|
|
|
348
|
39
|
|
|
|
|
770
|
return $user; |
349
|
|
|
|
|
|
|
} |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
=head2 get_user_roles |
352
|
91
|
|
|
|
|
23173
|
|
353
|
|
|
|
|
|
|
=cut |
354
|
91
|
|
|
|
|
390
|
|
355
|
|
|
|
|
|
|
my ( $self, $username ) = @_; |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
croak "username must be defined" |
358
|
|
|
|
|
|
|
unless defined $username; |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
my $user = $self->get_user_details($username) or return; |
361
|
|
|
|
|
|
|
|
362
|
17
|
|
|
17
|
1
|
35056
|
return $user->{roles}; |
363
|
|
|
|
|
|
|
} |
364
|
17
|
100
|
|
|
|
195
|
|
365
|
|
|
|
|
|
|
1; |
366
|
|
|
|
|
|
|
|