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, 9 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
RevLine 
[288]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;
[1117]15use LATMOS::Accounts::Bases::Ldap::Group;
[1122]16use LATMOS::Accounts::Utils;
[1603]17use Crypt::SmbHash;
[288]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
[292]46sub _my_ldap_classes { qw(
47    top
48    inetOrgPerson
49    organizationalPerson
50    posixAccount
51    shadowAccount
[1603]52    sambaSamAccount
[292]53) }
54
[1603]55sub _computeSSID {
56    my ($value) = @_;
57    $value * 2 + 1000
58}
59
[861]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 => { },
[1033]73        uid => { mandatory => 1 },
[861]74        manager => {
75            delayed => 1,
76            can_values => sub { $base->list_objects('user') },
77        },
78        facsimileTelephoneNumber => { },
[1603]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        },
[861]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            },
[288]106
[861]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 => { },
[1495]130        memberOf => {
131            delayed => 1,
132            multiple => 1,
133            reference => 'group',
134        },
[861]135        cn => { ro => 1, },
136        dn => { ro => 1, },
137        objectClass => { ro => 1, },
138        pwdAccountLockedTime => {},
139        pwdPolicySubentry => {},
140        pwdChangedTime => { ro => 1 },
141        labeledURI => {},
[1603]142        userPassword    => { readable => 0, },
143        sambaLMPassword => { readable => 0, },
144        sambaNTPassword => { readable => 0, },
145        sambaSid => { ro => 1 },
[861]146    }
147    );
[288]148}
149
150sub _create {
151    my ($class, $base, $id, %data) = @_;
152
153    my $entry = Net::LDAP::Entry->new();
154
155    $entry->dn(join(',',
[1115]156        sprintf('%s=%s',
[1117]157            $class->_dn_attribute($base),
[1115]158            escape_filter_value($id)),
159            $base->object_base_dn($class->type),
160        )
161    );
[288]162    $entry->replace(objectClass =>
[292]163        [ $class->_my_ldap_classes ],);
[1117]164    $data{cn} ||= $data{sn} || $id; # cn is mandatory
[383]165    $data{sn} ||= $id; # sn is mandatory
[1117]166    $data{uid} ||= $id; # uid is mandatory
[1603]167    $data{sambaSID} = $base->sambaSID(_computeSSID($data{uidNumber}));
[514]168    $data{homeDirectory} ||= '/dev/null'; # homeDirectory is mandatory
[1117]169    $data{$class->_key_attribute($base)} = $id;
[288]170    foreach (keys %data) {
171        $class->_populate_entry($entry,
[380]172            $_, $data{$_}, $base);
[288]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    }
[445]180    return 1;
[288]181}
182
[715]183sub _rename {
184    my ($class, $base, $uid, $newuid) = @_;
185
186    $class->SUPER::_rename($base, $uid, $newuid) or return;
187}
188
[288]189sub get_field {
190    my ($self, $field) = @_;
191
[294]192    $field eq 'streetAddress' and $field = 'street';
[288]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(
[618]198                '(&(objectClass=posixGroup)(memberUID=%s))',
199                escape_filter_value($self->id),
[288]200            ),
201            callback => sub {
202                my ($mesg, $entry) = @_;
203                ref $entry eq 'Net::LDAP::Entry' or return;
[1115]204                push(@res, $entry->get_value(
[1117]205                    LATMOS::Accounts::Bases::Ldap::Group->_key_attribute($self->base),
[1115]206                ));
[618]207                1;
[288]208            },
209        );
210        return [ sort(@res) ];
211    };
[295]212    $field eq 'manager' and do {
213        my $dn = $self->SUPER::get_field($field) or return;
[861]214        my $manager = $self->base->_get_object_from_dn($dn) or return;
[1174]215        return $manager->get_value(__PACKAGE__->_key_attribute($self->base));
[295]216    };
[288]217    $self->SUPER::get_field($field);
218}
219
[380]220sub _populate_entry {
221    my ($self, $entry, $f, $val, $base) = @_;
222    $base ||= $self->base;
223    for ($f) {
[1121]224        /^sn$/ and $val ||= $entry->get_value($self->_key_attribute);
[380]225        /^memberOf$/ and do {
[288]226            my %users;
[380]227            $users{$_}{e} = 1 foreach (ref $self
[621]228                ? $self->get_attributes('memberOf')
[380]229                : ());
[288]230            $users{$_}{n} = 1 foreach (@{ $val || []});
231            foreach (keys %users) {
232                $users{$_}{e} && $users{$_}{n} and next;
[767]233                my $group = $base->get_object('group', $_) or next;
[288]234                if ($users{$_}{e}) {
[1115]235                    $group->{entry}->delete(memberUID =>
[1117]236                        $entry->get_value(
237                            LATMOS::Accounts::Bases::Ldap::User->_key_attribute($base),
238                        )
239                    );
[767]240                } elsif ($users{$_}{n}) {
[1115]241                    $group->{entry}->add(memberUID =>
[1117]242                        $entry->get_value(
243                            LATMOS::Accounts::Bases::Ldap::User->_key_attribute($base),
244                        )
245                    );
[288]246                } # else {} # can't happen
[380]247                my $mesg = $group->{entry}->update($base->ldap);
248                if ($mesg->code) {
[381]249                    $base->log(LA_ERR, "Cannot set attributes: %s", $mesg->error);
[380]250                    return;
251                }
[288]252            }
[622]253            return 1;
[288]254        };
[380]255        /^manager$/ && $val and do {
[861]256            my $user = $base->get_object('user', $val) or do {
257                $val = undef;
258                next;
259            };
[380]260            $val = $user->get_field('dn');
[295]261            next;
262        };
[1122]263        /^gecos$/ and do {
264            $val = to_ascii($val);
265        };
[514]266        /^homeDirectory$/ and $val ||= '/dev/null';
[288]267    }
[380]268    $self->SUPER::_populate_entry($entry, $f, $val, $base);
[288]269}
270
[1603]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
[288]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.