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

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