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

Last change on this file since 1983 was 1983, checked in by nanardon, 7 years ago

Avoid undef warning

  • Property svn:keywords set to Id Rev
File size: 10.4 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$ =~ /^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        msSFU30NisDomain => { },
118        msSFU30Name => { },
119        labeledURI => {},
120        wWWHomePage => {},
121        userPassword => { readable => 0 },
122    };
123
124    $info->{$class->_key_attribute($base)}{ro} = 1;
125    $info->{$class->_dn_attribute($base)}{ro} = 1;
126
127    $info
128}
129
130sub _create {
131    my ($class, $base, $id, %data) = @_;
132
133    my $entry = Net::LDAP::Entry->new();
134
135    $entry->dn(join(',',
136        sprintf('%s=%s',
137            $class->_dn_attribute($base), 
138            escape_filter_value($id)),
139            $base->object_base_dn($class->type),
140    ));
141    $data{'sAMAccountName'} ||= $id;
142    $entry->replace(objectClass => [ $class->_my_ldap_classes ],);
143    # Must be 544 for creation...
144    $entry->replace(userAccountControl => 544); #66112);
145    # This attributes cannot be set via LDAP:
146    #$entry->replace(sAMAccountType => 0x30000000);
147    $entry->replace(accountExpires => '9223372036854775807'); # TODO hardcoded, burk
148    $entry->replace(userPrincipalName => "$id\@" . $base->ad_domain);
149
150    {
151        my $charmap = Unicode::Map8->new('latin1')  or  die;
152        my $clear_pass = LATMOS::Accounts::Utils::genpassword(lenght => 20, nonalpha => 1);
153        my $newUniPW = $charmap->tou('"'.$clear_pass.'"')->byteswap()->utf16();
154        $entry->replace(unicodePwd => $newUniPW);
155    }
156
157    my %delayed;
158    $data{$class->_key_attribute($base)} = $id;
159    foreach (keys %data) {
160        /^memberOf$/ and do {
161            $delayed{$_} = $data{$_};
162            next;
163        };
164        $class->_populate_entry($entry, $_, $data{$_}, $base);
165    }
166    my $msg = $base->ldap->add($entry);
167    if ($msg->code) {
168        $base->log(LA_ERR, "Cannot create user: %s", $msg->error);
169        return 0;
170    }
171    if (my $obj = $base->get_object('user', $id)) {
172        my $res = $obj->set_fields(%delayed);
173        return defined($res) ? 1 : 0;
174    } else {
175        $base->log(LA_ERR, "Cannot get just created object %s/%s", 'user', $id);
176        return 0;
177    }
178}
179
180sub get_field {
181    my ($self, $field) = @_;
182    require LATMOS::Accounts::Bases::Ad::Group;
183
184    $field eq 'memberOf' and do {
185        my @res;
186        $self->base->_unlimited_search(
187            base => $self->base->object_base_dn('group'),
188            filter => sprintf(
189                '(&(objectClass=group)(member=%s))',
190                escape_filter_value($self->{entry}->dn),
191            ),
192            callback => sub {
193                my ($mesg, $entry) = @_;
194                ref $entry eq 'Net::LDAP::Entry' or return;
195                push(@res, $entry->get_value(
196                        LATMOS::Accounts::Bases::Ad::Group->_key_attribute($self->base)
197                    )
198                );
199            },
200        );
201        return [ sort(@res) ];
202    };
203    $field eq 'directReports' and do {
204        my $res = $self->SUPER::get_field($field) or return;
205        return [
206            map { $self->base->_get_object_from_dn($_)->get_value(
207                LATMOS::Accounts::Bases::Ad::User->_key_attribute($self->base)
208            ) }
209            @{ ref $res ? $res : [ $res ] }
210        ];
211    };
212    $field eq 'manager' and do {
213        my $dn = $self->SUPER::get_field($field) or return;
214        return $self->base->_get_object_from_dn($dn)->get_value(
215            LATMOS::Accounts::Bases::Ad::User->_key_attribute($self->base)
216        );
217    };
218    $field eq 'homeDirectory' and do {
219        $field = 'unixHomeDirectory';
220    };
221    $field eq 'winhomeDirectory' and do {
222        $field = 'homeDirectory';
223    };
224    $self->SUPER::get_field($field);
225}
226
227sub _populate_entry {
228    my ($self, $entry, $field, $value, $base) = @_;
229    $base ||= $self->base;
230    # MS Ad sucks, does not respect RFC
231    # homeDirectory => unixHomeDirectory
232    # Win home => homeDirectory...
233    # The question is now: do we have to manage Ad homeDirectory field ?
234    for ($field) {
235        /^homeDirectory$/ and do {
236            $field = 'unixHomeDirectory';
237            next;
238        };
239        /^winhomeDirectory$/ and do {
240            $field = 'homeDirectory';
241            next;
242        };
243        /^manager$/ && $value and do {
244            my $user = $base->get_object('user', $value) or do {
245                $value = undef;
246                next;
247            };
248            $value = $user->get_field('dn');
249            next;
250        };
251        /^locked$/ and do {
252            my $uac = $entry->get_value('userAccountControl') || 0;
253            if ($value) {
254                $uac |= 0x00000002;
255            } else {
256                $uac &= (0xFFFFFFFF ^ 0x00000002);
257            }
258            $self->SUPER::_populate_entry($entry, 'userAccountControl', "$uac",
259                $base);
260            return; # nothing more to do
261        };
262        /^gecos$/ and do {
263            $value = to_ascii($value);
264        };
265    }
266    $self->SUPER::_populate_entry($entry, $field, $value, $base);
267}
268
269sub set_fields {
270    my ($self, %data) = @_;
271    my %ndata;
272    while (my ($f, $val) = each(%data)) {
273        $f eq 'memberOf' and do {
274            $val = [ $val ] unless(ref $val);
275            my %users;
276            $users{$_}{e} = 1 foreach (grep { $_ } @{ $self->get_field('memberOf') || []});
277            $users{$_}{n} = 1 foreach (grep { $_ } @{ $val || []});
278            foreach (keys %users) {
279                $users{$_}{e} && $users{$_}{n} and next;
280                my $group = $self->base->get_object('group', $_) or next;
281                # memberOf field is read only
282                # so we modify the peer entry into peer object
283                if ($users{$_}{e}) {
284                    $group->{entry}->delete(member => $self->get_field('dn'));
285                } elsif ($users{$_}{n}) {
286                    $group->{entry}->add(member => $self->get_field('dn'));
287                } # else {} # can't happen
288                my $mesg = $group->{entry}->update($self->base->ldap);
289                if ($mesg->code) {
290                    $self->base->log(LA_ERR, "Cannot set attributes: %s", $mesg->error);
291                    return;
292                }
293            }
294            next;
295        };
296        $ndata{$f} = $val;
297    }
298    $self->SUPER::set_fields(%ndata);
299}
300
301sub _set_password {
302    my ($self, $clear_pass) = @_;
303    my $charmap = Unicode::Map8->new('latin1')  or  die;
304    my $newUniPW = $charmap->tou('"'.$clear_pass.'"')->byteswap()->utf16();
305    my $mesg = $self->base->ldap->modify(
306        $self->_get_c_field('dn'),
307        changes => [
308        #replace => [ userPassword => $clear_pass ],
309        replace => [ unicodePwd => $newUniPW ],
310
311        ]
312    );
313    if ($mesg->code) {
314        $self->base->log(LA_ERR, "Cannot set password: %s", $mesg->error);
315        return;
316    }
317
318    my $userAccountControl = $self->get_field('userAccountControl');
319    # http://msdn.microsoft.com/en-us/library/ms680832(VS.85).aspx
320    $userAccountControl |= 0x00010000; # ADS_UF_DONT_EXPIRE_PASSWD
321                                       # The password for this account will never expire.
322    $userAccountControl |= 0x00000040; # ADS_UF_PASSWD_CANT_CHANGE
323                                       # The user cannot change the password.
324    $self->set_fields(userAccountControl => $userAccountControl);
325
326    $self->base->log(LA_NOTICE, 'Mot de passe changé pour %s', $self->id);
327
328    1;
329}
330
3311;
332
333__END__
334
335=head1 SEE ALSO
336
337=head1 AUTHOR
338
339Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
340
341=head1 COPYRIGHT AND LICENSE
342
343Copyright (C) 2008 CNRS SA/CETP/LATMOS
344
345This library is free software; you can redistribute it and/or modify
346it under the same terms as Perl itself, either Perl version 5.10.0 or,
347at your option, any later version of Perl 5 you may have available.
348
349
350=cut
Note: See TracBrowser for help on using the repository browser.