source: trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Objects.pm @ 1002

Last change on this file since 1002 was 1002, checked in by nanardon, 12 years ago
  • fix attributes object return in some, simplify code
  • Property svn:keywords set to Id Rev
File size: 14.0 KB
Line 
1package LATMOS::Accounts::Bases::Objects;
2
3use 5.010000;
4use strict;
5use warnings;
6use LATMOS::Accounts::Log;
7use LATMOS::Accounts::Bases::Attributes;
8use Crypt::Cracklib;
9
10our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
11
12=head1 NAME
13
14LATMOS::Accounts::Bases::Objects - Base class for account objects
15
16=head1 SYNOPSIS
17
18  use LATMOS::Accounts::Bases::Objects;
19  LATMOS::Accounts::Bases::Objects->new($base, $type, $id);
20
21=head1 DESCRIPTION
22
23=head1 FUNCTIONS
24
25=cut
26
27=head2 list($base)
28
29List object supported by this module existing in base $base
30
31Must be provide by object class
32
33    sub list {
34        my ($class, $base) = @_;
35    }
36
37=cut
38
39=head2 new($base, $id)
40
41Create a new object having $id as uid.
42
43=cut
44
45sub new {
46    my ($class, $base, $id, @args) = @_;
47    # So can be call as $class->SUPER::new()
48    bless {
49        _base => $base,
50        _type => lc(($class =~ m/::([^:]*)$/)[0]),
51        _id => $id,
52    }, $class;
53}
54
55# _new($base, $type, $id, ...)
56
57# Return a new object of type $type having unique identifier
58# $id, all remaining arguments are passed to the subclass.
59
60sub _new {
61    my ($class, $base, $otype, $id, @args) = @_;
62
63    # finding perl class:
64    my $pclass = $base->_load_obj_class($otype) or return;
65    my $newobj = "$pclass"->new($base, $id, @args) or return;
66    $newobj->{_base} = $base;
67    $newobj->{_type} = lc($otype);
68    $newobj->{_id} ||= $id;
69    return $newobj;
70}
71
72=head2 _create($class, $base, $id, %data)
73
74Must create a new object in database.
75
76Is called if underling base does not override create_object
77
78    sub _create(
79        my ($class, $base, $id, %data)
80    }
81
82=cut
83
84=head2 type
85
86Return the type of the object
87
88=cut
89
90sub type {
91    my ($self) = @_;
92    if (ref $self) {
93        return $self->{_type}
94    } else {
95        return lc(($self =~ /::([^:]+)$/)[0]);
96    }
97}
98
99=head2 base
100
101Return the base handle for this object.
102
103=cut
104
105sub base {
106    return $_[0]->{_base}
107}
108
109=head2 id
110
111Must return the unique identifier for this object
112
113=cut
114
115sub id {
116    my ($self) = @_;
117    $self->{_id}
118}
119
120=head2 list_canonical_fields($for)
121
122Object shortcut to get the list of field supported by the object.
123
124=cut
125
126sub list_canonical_fields {
127    my ($self, $for) = @_;
128    $for ||= 'rw';
129    $self->_canonical_fields($for);
130}
131
132sub attribute {
133    my ($self, $attribute) = @_;
134
135    my $attrinfo;
136    if (! ref $attribute) {
137        $attrinfo = $self->base->get_attr_schema(
138            $self->type, $attribute
139        ) or return;
140        $attrinfo->{name} = $attribute;
141    } else {
142        $attrinfo = $attribute;
143    }
144
145    return LATMOS::Accounts::Bases::Attributes->new(
146        $attrinfo,
147        $self,
148    );
149}   
150
151sub _canonical_fields {
152    my ($class, $base, $for) = @_;
153    $for ||= 'rw';
154    my $info = $base->_get_attr_schema($class->type);
155    my @attrs = map { $base->attribute($class->type, $_) } keys %{$info || {}};
156    @attrs = grep { ! $_->ro } @attrs if($for =~ /w/);
157    @attrs = grep { $_->readable } @attrs if($for =~ /r/);
158    map { $_->name } grep { !$_->hidden }  @attrs;
159}
160
161=head2 get_field($field)
162
163Return the value for $field, must be provide by data base.
164
165    sub get_field {
166        my ($self, $field)
167    }
168
169=cut
170
171=head2 get_c_field($cfield)
172
173Return the value for canonical field $cfield.
174
175Call driver specific get_field()
176
177=cut
178
179sub get_c_field {
180    my ($self, $cfield) = @_;
181    $self->base->check_acl($self, $cfield, 'r') or do {
182        $self->base->log(LA_ERR, "Permission denied to get %s/%s",
183            $self->id, $cfield
184        );
185        return;
186    };
187    return $self->_get_c_field($cfield);
188}
189
190=head2 get_attributes($attr)
191
192Like get_c_field but always return an array
193
194=cut
195
196sub get_attributes {
197    my ($self, $cfield) = @_;
198    my $res = $self->get_c_field($cfield);
199    return ref $res ? @{ $res } : ($res);
200}
201
202sub _get_attributes {
203    my ($self, $cfield) = @_;
204    my $res = $self->_get_c_field($cfield);
205    return ref $res ? @{ $res } : ($res);
206}
207
208sub get_state {
209    my ($self, $state) = @_;
210    # hum...
211    if (defined(my $res = $self->_get_state($state))) {
212        return $res;
213    }
214    for ($state) {
215    }
216    return;
217}
218
219sub _get_state {
220    my ($self, $state) = @_;
221    return;
222}
223
224sub _get_c_field {
225    my ($self, $cfield) = @_;
226    my $attribute = $self->attribute($cfield) or do {
227        $self->base->log(LA_WARN, "Unknow attribute $cfield");
228        return;
229    };
230    $attribute->readable or do {
231        $self->base->log(LA_WARN, "Attribute $cfield is not readable");
232        return;
233    };
234    return $attribute->get; 
235}
236
237sub queryformat {
238    my ($self, $fmt) = @_;
239    $fmt =~ s/\\n/\n/g;
240    $fmt =~ s!
241        (?:%{([^:}]*)(?::([^}]+))?})
242        !
243        my $val = $self->get_c_field($1);
244        sprintf('%' . ($2 || 's'), ref $val ? join(',', @$val) : ($val||''))
245        !egx;
246    $fmt;
247}
248
249=head2 set_fields(%data)
250
251Set values for this object. %data is a list or peer field => values.
252
253    sub set_fields {
254        my ($self, %data) = @_;
255    }
256
257=cut
258
259sub check_allowed_values {
260    my ($self, $attr, $values) = @_;
261    $self->base->check_allowed_values($self->type, $attr, $values);
262}
263
264sub attr_allow_values {
265    my ($self, $attr) = @_;
266    return $self->base->obj_attr_allowed_values(
267        $self->type,
268        $attr,
269    );
270}
271
272=head2 set_c_fields(%data)
273
274Set values for this object. %data is a list or peer
275canonical field => values. Fields names are translated.
276
277=cut
278
279sub set_c_fields {
280    my ($self, %cdata) = @_;
281    foreach my $cfield (keys %cdata) {
282        $self->base->check_acl($self, $cfield, 'w') or do { 
283            $self->base->log(LA_ERR, "Cannot modified %s/%s: %s",
284                $self->type, $self->id, "permission denied");
285            return;
286        };
287    }
288
289    foreach my $cfield (keys %cdata) {
290        $self->check_allowed_values($cfield, $cdata{$cfield}) or do {
291            $self->base->log(LA_ERR, "Cannot modified %s/%s: %s",
292                $self->type, $self->id, "non authorized value");
293            return;
294        };
295    }
296    $self->_set_c_fields(%cdata);
297}
298
299sub _set_c_fields {
300    my ($self, %cdata) = @_;
301    my %data;
302    my $res = 0;
303    foreach my $cfield (keys %cdata) {
304        my $attribute = $self->attribute($cfield) or do {
305            $self->base->log(LA_ERR,
306                "Cannot set unsupported attribute %s to %s (%s)",
307                $cfield, $self->id, $self->type
308            );
309            return;
310        };
311        $attribute->ro and do {
312            $self->base->log(LA_ERR,
313                "Cannot set read-only attribute %s to %s (%s)",
314                $cfield, $self->id, $self->type
315            );
316            return;
317        };
318        $attribute->mandatory &&
319            (!(defined($cdata{$cfield})) || $cdata{$cfield} eq '') and do {
320            $self->base->log(LA_ERR,
321                "%s attribute cannot be empty, ignoring for object %s/%s",
322                $cfield,
323                        $self->type,
324                        $self->id,
325            );
326            return 0;
327        };
328        $res += ($attribute->set($cdata{$cfield}) || 0);
329
330    }
331    $res
332}
333
334=head2 set_password($password)
335
336Set the password into the database, $password is the clear version
337of the password.
338
339This function store it into userPassword canonical field if supported
340using crypt unix and md5 algorythm (crypt md5), the salt is 8 random
341caracters.
342
343The base driver should override it if another encryption is need.
344
345=cut
346
347sub set_password {
348    my ($self, $clear_pass) = @_;
349    if ($self->base->check_acl($self, 'userPassword', 'w')) {
350        return $self->_set_password($clear_pass);
351    } else {
352        $self->base->log(LA_ERROR, "Permission denied for %s to change its password",
353            $self->id);
354        return;
355    }
356}
357
358sub _set_password {
359    my ($self, $clear_pass) = @_;
360    if (my $attribute = $self->base->attribute($self->type, 'userPassword')) {
361        my @salt_char = (('a' .. 'z'), ('A' .. 'Z'), (0 .. 9), '/', '.');
362        my $salt = join('', map { $salt_char[rand(scalar(@salt_char))] } (1 .. 8));
363        my $res = $self->set_fields($attribute->iname, crypt($clear_pass, '$1$' . $salt));
364        $self->base->log(LA_NOTICE, 'Mot de passe changé pour %s', $self->id)
365            if($res);
366        return $res;
367    } else {
368        $self->log(LA_WARN,
369            "Cannot set password: userPassword attributes is unsupported");
370    }
371}
372
373sub check_password {
374    my ( $self, $password ) = @_;
375    my $dictionary;
376
377    return fascist_check($password, $dictionary);
378}
379
380sub search {
381    my ($class, $base, @filter) = @_;
382    my @results;
383    my %parsed_filter;
384    while (my $item = shift(@filter)) {
385        # attr=foo => no extra white space !
386        # \W is false, it is possible to have two char
387        my ($attr, $mode, $val) = $item =~ /^(\w+)(?:(\W)(.+))?$/ or next;
388        if (!$mode) {
389            $mode = '~';
390            $val = shift(@filter);
391        }
392        push(
393            @{$parsed_filter{$attr}},
394            {
395                attr => $attr,
396                mode => $mode,
397                val  => $val,
398            }
399        );
400    }
401    foreach my $id ($base->list_objects($class->type)) {
402        my $obj = $base->get_object($class->type, $id);
403        my $match = 1;
404        foreach my $field (keys %parsed_filter) {
405            $base->attribute($class->type, $field) or
406                la_log LA_WARN "Unsupported attribute $field";
407            my $tmatch = 0;
408            foreach (@{$parsed_filter{$field}}) {
409                my $value = $_->{val};
410                my $fval = $obj->_get_c_field($field) || '';
411                if ($value eq '*') {
412                    if ($fval ne '') {
413                        $tmatch = 1;
414                        last;
415                    }
416                } elsif ($value eq '!') {
417                    if ($fval eq '') {
418                        $match = 1;
419                        last;
420                    }
421                } elsif ($_->{mode} eq '=') {
422                    if ($fval eq $value) {
423                        $tmatch = 1;
424                        last;
425                    }
426                } elsif($_->{mode} eq '~') {
427                    if ($fval =~ m/\Q$value\E/i) {
428                        $tmatch = 1;
429                        last;
430                    }
431                }
432            }
433            $match = 0 unless($tmatch);
434        }
435        push(@results, $id) if($match);
436    }
437    @results;
438}
439
440
441sub attributes_summary {
442    my ($class, $base, $attribute) = @_;
443    my %values;
444    foreach my $id ($base->list_objects($class->type)) {
445        my $obj = $base->get_object($class->type, $id);
446        my $value = $obj->_get_c_field($attribute);
447        if ($value) {
448            if (ref $value) {
449                foreach (@$value) {
450                    $values{$_} = 1;
451                }
452            } else {
453                $values{$value} = 1;
454            }
455        }
456    }
457    return sort(keys %values);
458}
459
460sub find_next_numeric_id {
461    my ($class, $base, $field, $min, $max) = @_;
462    $base->attribute($class->type, $field) or return;
463    $min ||= 
464        $field eq 'uidNumber' ? 500 :
465        $field eq 'gidNumber' ? 500 :
466        1;
467    $max ||= 65635;
468    $base->log(LA_DEBUG, "Trying to find %s in range %d - %d",
469        $field, $min, $max);
470    my %existsid;
471    foreach ($base->list_objects($class->type)) {
472        my $obj = $base->get_object($class->type, $_) or next;
473        my $id = $obj->_get_c_field($field) or next;
474        $existsid{$id + 0} = 1;
475    }
476    $min += 0;
477    $max += 0;
478    for(my $i = $min; $i <= $max; $i++) {
479        $existsid{$i + 0} or do {
480            $base->log(LA_DEBUG, "Next %s found: %d", $field, $i);
481            return $i;
482        };
483    }
484    return;
485}
486
487sub text_dump {
488    my ($self, $handle, $options, $base) = @_;
489    print $handle $self->dump($options, $base);
490    return 1;
491}
492
493sub dump {
494    my ($self, $options, $base) = @_;
495
496    my $otype = $self->type;
497    $base ||= $self->base;
498    my $dump;
499    if (ref $self) {
500        $dump .= sprintf "# base %s: object %s/%s\n",
501            $base->label, $self->type, $self->id;
502    }
503    $dump .= sprintf "# %s\n", scalar(localtime);
504
505    foreach my $attr (sort { $a cmp $b } $base->list_canonical_fields($otype,
506        $options->{only_rw} ? 'rw' : 'r')) {
507        my $oattr = $base->attribute($otype, $attr);
508        if (ref $self) {
509            my $val = $self->get_c_field($attr);
510            if ($val || $options->{empty_attr}) {
511                if (my @allowed = $base->obj_attr_allowed_values($otype, $attr)) {
512                    $dump .= sprintf("# %s must be%s: %s\n",
513                        $attr,
514                        ($oattr->mandatory ? '' : ' empty or either'),
515                        join(', ', @allowed)
516                    );
517                }
518                my @vals = ref $val ? @{ $val } : $val;
519                foreach (@vals) {
520                    $_ ||= '';
521                    s/\r?\n/\\n/g;
522                    $dump .= sprintf("%s%s:%s\n", 
523                        $oattr->ro ? '# (ro) ' : '',
524                        $attr, $_ ? " $_" : '');
525                }
526            }
527        } else {
528            if (my @allowed = $base->obj_attr_allowed_values($otype, $attr)) {
529                $dump .= sprintf("# %s must be empty or either: %s\n",
530                    $attr,
531                    join(', ', @allowed)
532                );
533            }
534            $dump .= sprintf("%s%s: %s\n", 
535                $oattr->ro ? '# (ro) ' : '',
536                $attr, '');
537        }
538    }
539    return $dump;
540}
541
5421;
543
544__END__
545
546=head1 CANICALS FIELDS
547
548=head2 User class
549
550=head2 Group class
551
552=head1 SEE ALSO
553
554Mention other useful documentation such as the documentation of
555related modules or operating system documentation (such as man pages
556in UNIX), or any relevant external documentation such as RFCs or
557standards.
558
559If you have a mailing list set up for your module, mention it here.
560
561If you have a web site set up for your module, mention it here.
562
563=head1 AUTHOR
564
565Thauvin Olivier, E<lt>olivier.thauvin.ipsl.fr@localdomainE<gt>
566
567=head1 COPYRIGHT AND LICENSE
568
569Copyright (C) 2009 by Thauvin Olivier
570
571This library is free software; you can redistribute it and/or modify
572it under the same terms as Perl itself, either Perl version 5.10.0 or,
573at your option, any later version of Perl 5 you may have available.
574
575=cut
Note: See TracBrowser for help on using the repository browser.