source: LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ad.pm @ 71

Last change on this file since 71 was 71, checked in by nanardon, 15 years ago
  • make objects path configurable
  • Property svn:keywords set to Id Rev
File size: 6.9 KB
Line 
1package LATMOS::Accounts::Bases::Ad;
2
3use 5.010000;
4use strict;
5use warnings;
6
7use base qw(LATMOS::Accounts::Bases);
8use Net::LDAP;
9use Net::LDAP::Entry;
10use Net::LDAP::Control::Paged;
11use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); 
12use Net::LDAP::Util     qw( escape_filter_value );
13
14our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
15
16=head1 NAME
17
18LATMOS::Ad - Perl extension for blah blah blah
19
20=head1 SYNOPSIS
21
22  use LATMOS::Ad;
23  blah blah blah
24
25=head1 DESCRIPTION
26
27Stub documentation for LATMOS::Ad, created by h2xs. It looks like the
28author of the extension was negligent enough to leave the stub
29unedited.
30
31Blah blah blah.
32
33=head1 FUNCTIONS
34
35=cut
36
37=head2 new(%options)
38
39Create a new LATMOS::Ad object for windows AD $domain.
40
41options:
42
43=over 4
44
45=item domain
46
47The Active directory domain
48
49=item server (optional)
50
51If set, try to connect to this server, if not set, a dns query is performed
52to find AD server, first responding is used.
53
54=item ssl
55
56If set, try to connect using ssl
57
58=item OBJECT_container
59
60The sub path where to find object type of OBJECT and where they should
61be created
62
63=back
64
65=cut
66
67sub new {
68    my ($class, %options) = @_;
69   
70    $options{domain} or return;
71    my $self = {
72        _server => $options{server},
73        _ad_domain => $options{domain},
74        _top_dn => join(',', map { "dc=$_" } split('\.', $options{domain})),
75        _login => $options{login},
76        _password => $options{password},
77        _ssl => $options{ssl},
78        _param => { %options },
79    };
80
81    bless($self, $class);
82}
83
84sub param {
85    my ($self, $var) = @_;
86    return $self->{_param}{$var}
87}
88
89sub object_base_dn {
90    my ($self, $otype) = @_;
91    warn $otype;
92    return join(',',
93        ($self->param($otype . '_container') || 'cn=Users'),
94        $self->top_dn,
95    );
96}
97
98sub load {
99    my ($self) = @_;
100    # At this point, if still no $server, DNS search
101
102    my $ldap;
103    foreach my $tryserv (
104        $self->{server}
105        ? ($self->_ldap_url($self->{_server}))
106        : $self->_query_zone_ads) {
107        $ldap = Net::LDAP->new(
108            $tryserv,
109        ) and last;
110    }
111
112    $ldap or return; # connot connect to any ldap :\
113
114    my $login = $self->{_login};
115    $login =~ m/@/ or $login .= '@' . $self->ad_domain;
116
117    my $msg = $ldap->bind($login, password => $self->{_password}) or return;
118    $msg->code and do {
119        warn $msg->error;
120        return;
121    };
122
123    $self->{_ldap} = $ldap;
124    return 1;
125}
126
127sub _ldap_url {
128    my ($self, $host, $port) = @_;
129
130    sprintf(
131        '%s://%s%s/',
132        $self->{_ssl} ? 'ldaps' : 'ldap',
133        $host,
134        $port ? ":$port" : '',
135    )
136}
137
138sub _query_zone_ads {
139    my ($self) = @_;
140    require Net::DNS;
141    my @urllist;
142
143    my $resolver = Net::DNS::Resolver->new;
144    my $query = $resolver->query("_ldap._tcp.dc._msdcs." . $self->ad_domain, "SRV");
145    foreach my $rr (
146        sort { $a->priority <=> $b->priority || $a->weight <=> $b->weight }
147        grep { $_->type eq 'SRV' } $query->answer) {
148        push(@urllist, $self->_ldap_url($rr->target, $rr->port));
149    }
150
151    @urllist
152}
153
154sub ldap {
155    return $_[0]->{_ldap};
156}
157
158=head2 top_dn
159
160Return the TOP DN of the AD zone.
161
162=cut
163
164sub top_dn {
165    return $_[0]->{_top_dn}
166}
167
168=head2 ad_domain
169
170Return the active directory zone
171
172=cut
173
174sub ad_domain {
175    return $_[0]->{_ad_domain}
176}
177
178=head2 unlimited_search
179
180By default, ldap servers limit results to avoid deni of services.
181LDAP protocol provide a paging feature to fetch all results.
182
183This function works like Net::LDAP::search functions, but return nothing,
184use "callback" option to get entries.
185
186=cut
187
188sub _unlimited_search {
189    my $self = shift;
190    my @args = @_;
191
192    my ($page, $cookie) = (Net::LDAP::Control::Paged->new( size => 100 ));
193
194    while (1) {
195        my $search = $self->ldap->search(
196            @args,
197            control => [ $page ],
198        );
199        if ($search->code) {
200            return $search;
201        }
202
203        ### After foreach loops ends, client checks LDAP server reponse of how many search results total.
204        ### This is a control to end the infinite while loop if there are no search results to go through
205        ### If there are search results, this control will always return the total number of results. It is
206        ### never decremented
207        my ($resp) = $search->control( LDAP_CONTROL_PAGED ) or last;
208
209
210        ### Obtaining the cookie from the search result. When no more results, cookie will be NULL
211        ### and infinite while loop will terminate.
212        $cookie = $resp->cookie or last;
213
214        ### Sets cookie so server knows the next search result to send
215        $page->cookie($cookie);
216    }
217    ### This is a control to check for abnormal exit of the while loop.
218    ### If this occurs ### we need to tell the LDAP server that remaining
219    ### search results are no longer needed ### by sending a search request with a page size of 0
220    if ($cookie)    {
221        $page->cookie($cookie);
222        $page->size(0);
223        $self->search(@args, control => [ $page ]);
224    }
225    return 1;
226}
227
228sub _defaults_group_attrs {
229    my ($self, $entry, $attrs) = @_;
230    foreach my $attr (keys %{ $attrs || {} }) {
231        my $val = $attrs->{$attr};
232        $entry->replace($attr => $val);
233    }
234}
235
236sub get_group_users {
237    my ($self, $groupname, @searchargs) = @_;
238    my $gr = $self->get_group($groupname, attrs => [ qw(cn member) ]);
239
240    my @res;
241    foreach my $dnu (@{ $gr->get_value('member', asref => 1) || [] }) {
242        my $mesg = $self->search(
243            filter => '(objectClass=*)', # TODO can we get something else than user ?
244            @searchargs,
245            base => $dnu,
246        );
247
248        $mesg->code and return; # ensure error is propagate here
249        foreach my $entry ($mesg->entries) {
250           push(@res, $entry);
251       } 
252    }
253    @res
254}
255
256sub get_user_groups {
257    my ($self, $username, @searchargs) = @_;
258    my $user = $self->get_user($username);
259
260    my @res;
261    $self->unlimited_search(
262        base => $self->top_dn,
263        filter => sprintf(
264            '(&(objectClass=group)(member=%s))',
265            escape_filter_value($user->dn),
266        ),
267        @searchargs,
268        callback => sub {
269            my ($mesg, $entry) = @_;
270            ref $entry eq 'Net::LDAP::Entry' or return;
271            push(@res, $entry);
272        },
273    );
274
275    @res
276}
277
278sub add_user_group {
279    my ($self, $username, $groupname) = @_;
280
281    my $user = $self->get_user($username) or return;
282    my $group = $self->get_group($groupname) or return;
283
284    $group->add(member => $user->dn);
285
286    my $mesg = $group->update($self);
287    if ($mesg->code) {
288        warn $mesg->error;
289        return;
290    } else { return 1 };
291}
292
2931;
294
295__END__
296
297=head1 SEE ALSO
298
299=head1 AUTHOR
300
301Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
302
303=head1 COPYRIGHT AND LICENSE
304
305Copyright (C) 2008 CNRS SA/CETP/LATMOS
306
307This library is free software; you can redistribute it and/or modify
308it under the same terms as Perl itself, either Perl version 5.10.0 or,
309at your option, any later version of Perl 5 you may have available.
310
311
312=cut
Note: See TracBrowser for help on using the repository browser.