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

Last change on this file since 1278 was 1278, checked in by nanardon, 9 years ago

interdire les mot de passe non ascii

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