source: trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ad/User.pm @ 2491

Last change on this file since 2491 was 2491, checked in by nanardon, 3 years ago

Typo

  • Property svn:keywords set to Id Rev
File size: 10.7 KB
Line 
1package LATMOS::Accounts::Bases::Ad::User;
2
3use 5.010000;
4use strict;
5use warnings;
6
7use base qw(LATMOS::Accounts::Bases::Ad::objects);
8use Net::LDAP;
9use Net::LDAPS;
10use Net::LDAP::Entry;
11use Net::LDAP::Control::Paged;
12use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); 
13use Net::LDAP::Util     qw( escape_filter_value );
14use LATMOS::Accounts::Log;
15use LATMOS::Accounts::Utils;
16
17our $VERSION = (q$Rev: 2047 $ =~ /^Rev: (\d+) /)[0];
18
19=head1 NAME
20
21LATMOS::Ad - Perl extension for blah blah blah
22
23=head1 SYNOPSIS
24
25  use LATMOS::Ad;
26  blah blah blah
27
28=head1 DESCRIPTION
29
30Stub documentation for LATMOS::Ad, created by h2xs. It looks like the
31author of the extension was negligent enough to leave the stub
32unedited.
33
34Blah blah blah.
35
36=head1 FUNCTIONS
37
38=cut
39
40sub _class_filter { '(&(ObjectClass=user) (!(ObjectClass=computer)))' }
41
42sub _key_attr { 'cn' } 
43
44sub _my_ldap_classes { qw(top person organizationalPerson user) }
45
46sub _get_attr_schema {
47    my ($class, $base) = @_;
48    my $info = {
49        sn => { },
50        name => { },
51        givenName => { },
52        sAMAccountName => { },
53        uid => { uniq => 1, },
54        gecos => { },
55        homeDirectory => { },
56        loginShell => { },
57        uidNumber => { uniq => 1, },
58        gidNumber => {
59            mandatory => 1,
60            can_values => sub {
61                map { $base->get_object('group',
62                        $_)->get_attributes('gidNumber') }
63                $base->list_objects('group')
64            },
65            display => sub {
66                my ($self, $val) = @_;
67                my ($gr) = $self->base->search_objects('group', "gidNumber=$val")
68                    or return;
69                return $gr;
70            },
71            reference => 'group',
72        },
73        shadowLastChange => { },
74        shadowMin => { },
75        shadowMax => { },
76        shadowWarning => { },
77        shadowInactive => { },
78        shadowExpire => { },
79        shadowFlag => { },
80        description => { },
81        mail => { },
82        telephoneNumber => { },
83        ipPhone => { },
84        otherTelephone => { },
85        department => { },
86        title => { },
87        mobile => { },
88        homePhone => { },
89        accountExpires => { },
90        streetAddress => { },
91        postalCode => { },
92        postOfficeBox => { },
93        l => { },
94        physicalDeliveryOfficeName => { },
95        company => { },
96        st => { },
97        displayName => { },
98        initials => { },
99        manager => {
100            delayed => 1,
101            can_values => sub { $base->list_objects('user') },
102        },
103        userAccountControl => { },
104        locked => { },
105        memberOf => {
106            multiple => 1,
107            delayed => 1,
108        },
109        winhomeDirectory => { },
110        facsimileTelephoneNumber => { },
111        cn => { ro => 1, },
112        dn => { ro => 1, },
113        uSNCreated => { ro => 1, },
114        uSNChanged => { ro => 1, },
115        directReports => { ro => 1, },
116        objectClass => { ro => 1, },
117        objectCategory => { ro => 1, },
118        msSFU30NisDomain => { },
119        msSFU30Name => { },
120        labeledURI => {},
121        wWWHomePage => {},
122        userPassword => { readable => 0 },
123    };
124
125    $info->{$class->_key_attribute($base)}{ro} = 1;
126    $info->{$class->_dn_attribute($base)}{ro} = 1;
127
128    $info
129}
130
131sub _create {
132    my ($class, $base, $id, %data) = @_;
133
134    my $entry = Net::LDAP::Entry->new();
135
136    $entry->dn(join(',',
137        sprintf('%s=%s',
138            $class->_dn_attribute($base), 
139            escape_filter_value($id)),
140            $base->object_base_dn($class->type),
141    ));
142    $data{'sAMAccountName'} ||= substr($id, 0, 19);
143    $entry->replace(objectClass => [ $class->_my_ldap_classes ],);
144    # Must be 544 for creation...
145    $entry->replace(userAccountControl => 544); #66112);
146    # This attributes cannot be set via LDAP:
147    #$entry->replace(sAMAccountType => 0x30000000);
148    $entry->replace(accountExpires => '9223372036854775807'); # TODO hardcoded, burk
149    $entry->replace(userPrincipalName => "$id\@" . $base->ad_domain);
150
151    if ($base->config('ssl')) {
152        my $charmap = Unicode::Map8->new('latin1')  or  die;
153        my $clear_pass = LATMOS::Accounts::Utils::genpassword(lenght => 20, nonalpha => 1);
154        my $newUniPW = $charmap->tou('"'.$clear_pass.'"')->byteswap()->utf16();
155        $entry->replace(unicodePwd => $newUniPW);
156    }
157
158    my %delayed;
159    $data{$class->_key_attribute($base)} = $id;
160    foreach (keys %data) {
161        /^memberOf$/ and do {
162            $delayed{$_} = $data{$_};
163            next;
164        };
165        $class->_populate_entry($entry, $_, $data{$_}, $base);
166    }
167    my $msg = $base->ldap->add($entry);
168    if ($msg->code) {
169        $base->log(LA_ERR, "Cannot create user: %s", $msg->error);
170        return 0;
171    }
172    if (my $obj = $base->get_object('user', $id)) {
173        my $res = $obj->set_fields(%delayed);
174        return defined($res) ? 1 : 0;
175    } else {
176        $base->log(LA_ERR, "Cannot get just created object %s/%s", 'user', $id);
177        return 0;
178    }
179}
180
181sub get_field {
182    my ($self, $field) = @_;
183    require LATMOS::Accounts::Bases::Ad::Group;
184
185    $field eq 'memberOf' and do {
186        my @res;
187        $self->base->_unlimited_search(
188            base => $self->base->object_base_dn('group'),
189            filter => sprintf(
190                '(&(objectClass=group)(member=%s))',
191                escape_filter_value($self->{entry}->dn),
192            ),
193            callback => sub {
194                my ($mesg, $entry) = @_;
195                ref $entry eq 'Net::LDAP::Entry' or return;
196                push(@res, $entry->get_value(
197                        LATMOS::Accounts::Bases::Ad::Group->_key_attribute($self->base)
198                    )
199                );
200            },
201        );
202        return [ sort(@res) ];
203    };
204    $field eq 'directReports' and do {
205        my $res = $self->SUPER::get_field($field) or return;
206        return [
207            map { $self->base->_get_object_from_dn($_)->get_value(
208                LATMOS::Accounts::Bases::Ad::User->_key_attribute($self->base)
209            ) }
210            @{ ref $res ? $res : [ $res ] }
211        ];
212    };
213    $field eq 'manager' and do {
214        my $dn = $self->SUPER::get_field($field) or return;
215        return $self->base->_get_object_from_dn($dn)->get_value(
216            LATMOS::Accounts::Bases::Ad::User->_key_attribute($self->base)
217        );
218    };
219    $field eq 'homeDirectory' and do {
220        $field = 'unixHomeDirectory';
221    };
222    $field eq 'winhomeDirectory' and do {
223        $field = 'homeDirectory';
224    };
225    $self->SUPER::get_field($field);
226}
227
228sub _populate_entry {
229    my ($self, $entry, $field, $value, $base) = @_;
230    $base ||= $self->base;
231    # MS Ad sucks, does not respect RFC
232    # homeDirectory => unixHomeDirectory
233    # Win home => homeDirectory...
234    # The question is now: do we have to manage Ad homeDirectory field ?
235    for ($field) {
236        /^homeDirectory$/ and do {
237            $field = 'unixHomeDirectory';
238            next;
239        };
240        /^winhomeDirectory$/ and do {
241            $field = 'homeDirectory';
242            next;
243        };
244        /^manager$/ && $value and do {
245            my $user = $base->get_object('user', $value) or do {
246                $value = undef;
247                next;
248            };
249            $value = $user->get_field('dn');
250            next;
251        };
252        /^locked$/ and do {
253            my $uac = $entry->get_value('userAccountControl') || 0;
254            if ($value) {
255                $uac |= 0x00000002;
256            } else {
257                $uac &= (0xFFFFFFFF ^ 0x00000002);
258            }
259            $self->SUPER::_populate_entry($entry, 'userAccountControl', "$uac",
260                $base);
261            return; # nothing more to do
262        };
263        /^gecos$/ and do {
264            $value = to_ascii($value);
265        };
266    }
267    $self->SUPER::_populate_entry($entry, $field, $value, $base);
268}
269
270sub set_fields {
271    my ($self, %data) = @_;
272    my %ndata;
273    while (my ($f, $val) = each(%data)) {
274        $f eq 'memberOf' and do {
275            $val = [ $val ] unless(ref $val);
276            my %users;
277            $users{$_}{e} = 1 foreach (grep { $_ } @{ $self->get_field('memberOf') || []});
278            $users{$_}{n} = 1 foreach (grep { $_ } @{ $val || []});
279            foreach (keys %users) {
280                $users{$_}{e} && $users{$_}{n} and next;
281                my $group = $self->base->get_object('group', $_) or next;
282                # memberOf field is read only
283                # so we modify the peer entry into peer object
284                if ($users{$_}{e}) {
285                    $group->{entry}->delete(member => $self->get_field('dn'));
286                } elsif ($users{$_}{n}) {
287                    $group->{entry}->add(member => $self->get_field('dn'));
288                } # else {} # can't happen
289                my $mesg = $group->{entry}->update($self->base->ldap);
290                if ($mesg->code) {
291                    $self->base->log(LA_ERR, "Cannot set attributes: %s", $mesg->error);
292                    return;
293                }
294            }
295            next;
296        };
297        $ndata{$f} = $val;
298    }
299    $self->SUPER::set_fields(%ndata);
300}
301
302sub _set_password {
303    my ($self, $clear_pass) = @_;
304    my $charmap = Unicode::Map8->new('latin1')  or  die;
305    my $newUniPW = $charmap->tou('"'.$clear_pass.'"')->byteswap()->utf16();
306    my $mesg = $self->base->ldap->modify(
307        $self->_get_c_field('dn'),
308        changes => [
309        #replace => [ userPassword => $clear_pass ],
310        replace => [ unicodePwd => $newUniPW ],
311
312        ]
313    );
314    if ($mesg->code) {
315        $self->base->log(LA_ERR, "Cannot set password: %s", $mesg->error);
316        return;
317    }
318
319    my $userAccountControl = $self->get_field('userAccountControl');
320    # http://msdn.microsoft.com/en-us/library/ms680832(VS.85).aspx
321    $userAccountControl |= 0x00010000; # ADS_UF_DONT_EXPIRE_PASSWD
322                                       # The password for this account will never expire.
323    $userAccountControl |= 0x00000040; # ADS_UF_PASSWD_CANT_CHANGE
324                                       # The user cannot change the password.
325    $self->set_fields(userAccountControl => $userAccountControl);
326
327    my $mesg2 = $self->{entry}->update($self->base->ldap);
328
329    if ($mesg2->code && $mesg2->code != 82) {
330        $self->base->log(LA_ERR, "Cannot set attributes: %s", $mesg->error);
331        return;
332    } else {
333        $self->base->log(LA_NOTICE, 'Mot de passe changé pour %s', $self->id);
334        return 1;
335    }
336
337    1;
338}
339
3401;
341
342__END__
343
344=head1 SEE ALSO
345
346=head1 AUTHOR
347
348Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
349
350=head1 COPYRIGHT AND LICENSE
351
352Copyright (C) 2008 CNRS SA/CETP/LATMOS
353
354This library is free software; you can redistribute it and/or modify
355it under the same terms as Perl itself, either Perl version 5.10.0 or,
356at your option, any later version of Perl 5 you may have available.
357
358
359=cut
Note: See TracBrowser for help on using the repository browser.