source: trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/User.pm @ 1603

Last change on this file since 1603 was 1603, checked in by nanardon, 8 years ago

Upgrade entry user to sambaAccount to store NT encrypted password

Having password stored in NT windows forms will allow to use them for mschap(v2)
authentication

File size: 9.1 KB
Line 
1package LATMOS::Accounts::Bases::Ldap::User;
2
3use 5.010000;
4use strict;
5use warnings;
6
7use base qw(LATMOS::Accounts::Bases::Ldap::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::Bases::Ldap::Group;
16use LATMOS::Accounts::Utils;
17use Crypt::SmbHash;
18
19our $VERSION = (q$Rev: 649 $ =~ /^Rev: (\d+) /)[0];
20
21=head1 NAME
22
23LATMOS::Ldap - Perl extension for blah blah blah
24
25=head1 SYNOPSIS
26
27  use LATMOS::Ldap;
28  blah blah blah
29
30=head1 DESCRIPTION
31
32Stub documentation for LATMOS::Ldap, created by h2xs. It looks like the
33author of the extension was negligent enough to leave the stub
34unedited.
35
36Blah blah blah.
37
38=head1 FUNCTIONS
39
40=cut
41
42sub _class_filter { '(&(ObjectClass=posixAccount))' }
43
44sub _key_attr { 'cn' } 
45
46sub _my_ldap_classes { qw(
47    top
48    inetOrgPerson
49    organizationalPerson
50    posixAccount
51    shadowAccount
52    sambaSamAccount
53) }
54
55sub _computeSSID {
56    my ($value) = @_;
57    $value * 2 + 1000
58}
59
60sub _get_attr_schema {
61    my ($class, $base) = @_;
62    $class->SUPER::_get_attr_schema($base,
63    {
64        displayName => { },
65        givenName => { },
66        homePhone => { },
67        homePostalAddress => { },
68        initials => { },
69        mail => { },
70        sn => { },
71        mobile => { },
72        o => { },
73        uid => { mandatory => 1 },
74        manager => {
75            delayed => 1,
76            can_values => sub { $base->list_objects('user') },
77        },
78        facsimileTelephoneNumber => { },
79        uidNumber => {
80            uniq => 1,
81            post => sub {
82                my ($self, $value) = @_;
83                if (!grep { $_ eq 'sambaSamAccount' }  $self->object->_get_attributes('objectClass')) {
84                    $self->_update_class(
85                        sambaSID => $self->base->sambaSID(_computeSSID($value))
86                    );
87                } else {
88                    $self->object->set_fields(sambaSID => $self->object->base->sambaSID(_computeSSID($value)));
89                }
90            },
91        },
92        gidNumber => {
93            reference => 'group',
94            mandatory => 1,
95            can_values => sub {
96                map { $base->get_object('group',
97                        $_)->get_attributes('gidNumber') }
98                $base->list_objects('group')
99            },
100            display => sub {
101                my ($self, $val) = @_;
102                my ($gr) = $self->base->search_objects('group', "gidNumber=$val")
103                    or return;
104                return $gr;
105            },
106
107        },
108        homeDirectory => { },
109        userPassword => { },
110        loginShell => { },
111        gecos => { },
112        description => { },
113        shadowLastChange => { },
114        shadowMin => { },
115        shadowMax => { },
116        shadowWarning => { },
117        shadowInactive => { },
118        shadowExpire => { },
119        shadowFlag => { },
120        street => { },
121        postOfficeBox => { },
122        postalCode => { },
123        postalAddress => { },
124        streetAddress => { },
125        physicalDeliveryOfficeName => { },
126        ou => { },
127        st => { },
128        l => { },
129        telephoneNumber => { },
130        memberOf => {
131            delayed => 1,
132            multiple => 1,
133            reference => 'group',
134        },
135        cn => { ro => 1, },
136        dn => { ro => 1, },
137        objectClass => { ro => 1, },
138        pwdAccountLockedTime => {},
139        pwdPolicySubentry => {},
140        pwdChangedTime => { ro => 1 },
141        labeledURI => {},
142        userPassword    => { readable => 0, },
143        sambaLMPassword => { readable => 0, },
144        sambaNTPassword => { readable => 0, },
145        sambaSid => { ro => 1 },
146    }
147    );
148}
149
150sub _create {
151    my ($class, $base, $id, %data) = @_;
152
153    my $entry = Net::LDAP::Entry->new();
154
155    $entry->dn(join(',',
156        sprintf('%s=%s',
157            $class->_dn_attribute($base),
158            escape_filter_value($id)),
159            $base->object_base_dn($class->type),
160        )
161    );
162    $entry->replace(objectClass =>
163        [ $class->_my_ldap_classes ],);
164    $data{cn} ||= $data{sn} || $id; # cn is mandatory
165    $data{sn} ||= $id; # sn is mandatory
166    $data{uid} ||= $id; # uid is mandatory
167    $data{sambaSID} = $base->sambaSID(_computeSSID($data{uidNumber}));
168    $data{homeDirectory} ||= '/dev/null'; # homeDirectory is mandatory
169    $data{$class->_key_attribute($base)} = $id;
170    foreach (keys %data) {
171        $class->_populate_entry($entry,
172            $_, $data{$_}, $base);
173    }
174
175    my $msg = $base->ldap->add($entry);
176    if ($msg->code) {
177        $base->log(LA_ERR, "Cannot create user: %s", $msg->error);
178        return 0;
179    }
180    return 1;
181}
182
183sub _rename {
184    my ($class, $base, $uid, $newuid) = @_;
185
186    $class->SUPER::_rename($base, $uid, $newuid) or return;
187}
188
189sub get_field {
190    my ($self, $field) = @_;
191
192    $field eq 'streetAddress' and $field = 'street';
193    $field eq 'memberOf' and do {
194        my @res;
195        $self->base->_unlimited_search(
196            base => $self->base->object_base_dn('group'),
197            filter => sprintf(
198                '(&(objectClass=posixGroup)(memberUID=%s))',
199                escape_filter_value($self->id),
200            ),
201            callback => sub {
202                my ($mesg, $entry) = @_;
203                ref $entry eq 'Net::LDAP::Entry' or return;
204                push(@res, $entry->get_value(
205                    LATMOS::Accounts::Bases::Ldap::Group->_key_attribute($self->base),
206                ));
207                1;
208            },
209        );
210        return [ sort(@res) ];
211    };
212    $field eq 'manager' and do {
213        my $dn = $self->SUPER::get_field($field) or return;
214        my $manager = $self->base->_get_object_from_dn($dn) or return;
215        return $manager->get_value(__PACKAGE__->_key_attribute($self->base));
216    };
217    $self->SUPER::get_field($field);
218}
219
220sub _populate_entry {
221    my ($self, $entry, $f, $val, $base) = @_;
222    $base ||= $self->base;
223    for ($f) {
224        /^sn$/ and $val ||= $entry->get_value($self->_key_attribute);
225        /^memberOf$/ and do {
226            my %users;
227            $users{$_}{e} = 1 foreach (ref $self
228                ? $self->get_attributes('memberOf')
229                : ());
230            $users{$_}{n} = 1 foreach (@{ $val || []});
231            foreach (keys %users) {
232                $users{$_}{e} && $users{$_}{n} and next;
233                my $group = $base->get_object('group', $_) or next;
234                if ($users{$_}{e}) {
235                    $group->{entry}->delete(memberUID =>
236                        $entry->get_value(
237                            LATMOS::Accounts::Bases::Ldap::User->_key_attribute($base),
238                        )
239                    );
240                } elsif ($users{$_}{n}) {
241                    $group->{entry}->add(memberUID =>
242                        $entry->get_value(
243                            LATMOS::Accounts::Bases::Ldap::User->_key_attribute($base),
244                        )
245                    );
246                } # else {} # can't happen
247                my $mesg = $group->{entry}->update($base->ldap);
248                if ($mesg->code) {
249                    $base->log(LA_ERR, "Cannot set attributes: %s", $mesg->error);
250                    return;
251                }
252            }
253            return 1;
254        };
255        /^manager$/ && $val and do {
256            my $user = $base->get_object('user', $val) or do {
257                $val = undef;
258                next;
259            };
260            $val = $user->get_field('dn');
261            next;
262        };
263        /^gecos$/ and do {
264            $val = to_ascii($val);
265        };
266        /^homeDirectory$/ and $val ||= '/dev/null';
267    }
268    $self->SUPER::_populate_entry($entry, $f, $val, $base);
269}
270
271sub _set_password {
272    my ($self, $clear_pass) = @_;
273
274    my @salt_char = (('a' .. 'z'), ('A' .. 'Z'), (0 .. 9), '/', '.');
275    my $salt = join('', map { $salt_char[rand(scalar(@salt_char))] } (1 .. 8));
276    # openldap use prefix to identify encryption passwd
277    # {CRYPT} is system dependant, eg use crypt from system
278    # As we run openldap on UNIX, this should not be a problem
279    # as we use perl crypt() which does the same
280    # This code will have to be changed if we use openldap on other UNIX
281    my $md5 = '{CRYPT}' . crypt($clear_pass, '$1$' . $salt);
282
283    my ($lm, $nt) = ntlmgen $clear_pass;
284
285    if (!grep { $_ eq 'sambaSamAccount' }  $self->get_attributes('objectClass')) {
286        $self->_update_class(
287            sambaSID => $self->base->sambaSID($self->_get_attributes('uidNumber') * 2 + 1000)
288        );
289    }
290
291    my $res = $self->set_fields(
292        userPassword    => $md5,
293        sambaLMPassword => $lm,
294        sambaNTPassword => $nt,
295    );
296    $self->base->log(LA_NOTICE, 'Mot de passe changé pour %s', $self->id)
297    if($res);
298    return $res;
299}
300
3011;
302
303__END__
304
305=head1 SEE ALSO
306
307=head1 AUTHOR
308
309Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
310
311=head1 COPYRIGHT AND LICENSE
312
313Copyright (C) 2008 CNRS SA/CETP/LATMOS
314
315This library is free software; you can redistribute it and/or modify
316it under the same terms as Perl itself, either Perl version 5.10.0 or,
317at your option, any later version of Perl 5 you may have available.
318
319
320=cut
Note: See TracBrowser for help on using the repository browser.