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

Last change on this file since 1950 was 1865, checked in by nanardon, 7 years ago

Merge branch

  • Property svn:keywords set to Id Rev
File size: 28.5 KB
Line 
1package LATMOS::Accounts::Bases;
2
3use 5.010000;
4use strict;
5use warnings;
6use LATMOS::Accounts::Bases::Objects;
7use LATMOS::Accounts::Bases::Attributes;
8use LATMOS::Accounts::Log;
9use LATMOS::Accounts::Utils qw(exec_command to_ascii);
10
11our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
12
13=head1 NAME
14
15LATMOS::Accounts::Bases - Base class for account data bases
16
17=head1 SYNOPSIS
18
19  use LATMOS::Accounts::Bases;
20  my $base = LATMOS::Accounts::Bases->new('type', %options);
21  ...
22
23=head1 DESCRIPTION
24
25This module provide basic functions for various account base
26
27=head1 FUNCTIONS
28
29=cut
30
31=head2 new($type, %options)
32
33Return, if success, a new data base account object, $type is
34account base type, %options to setup the base.
35
36=cut
37
38sub new {
39    my ($class, $type, $options) = @_;
40
41    my $pclass = ucfirst(lc($type));
42    eval "require LATMOS::Accounts::Bases::$pclass;";
43    if ($@) {
44        la_log(LA_DEBUG, "Failed to load base type `%s': %s", $type, $@);
45        return
46    }
47    my $base = "LATMOS::Accounts::Bases::$pclass"->new(%{$options->{params}})
48        or return;
49    $base->{_type} = lc($pclass);
50    $base->{_label} = $options->{label};
51    $base->{_options} = $options->{params};
52    $base->{wexported} = 0;
53    $base->{defattr} = $options->{defattr};
54    $base->{_acls} = $options->{acls};
55    $base->{_allowed_values} = $options->{allowed_values};
56    $base->{_la} = $options->{la};
57    la_log(LA_DEBUG, 'Instanciate base %s (%s)', ($base->label || 'N/A'), $pclass);
58    $base
59}
60
61=head2 load
62
63Make account base loading data into memory if need.
64Should always be called, if database fetch data on the fly
65(SQL, LDAP), the function just return True.
66
67=cut
68
69# override by the database driver if need
70sub load { 1 }
71
72=head2 label
73
74Return the database label
75
76=cut
77
78sub label {
79    $_[0]->{_label} || 'NoLabel';
80}
81
82=head2 type
83
84Return the type of the base
85
86=cut
87
88sub type {
89    $_[0]->{_type};
90}
91
92=head2 la
93
94return LATMOS::Accounts object parent to the base
95
96=cut
97
98sub la { $_[0]->{_la} };
99
100=head2 config ($opt)
101
102Return options from config
103
104=cut
105
106sub config {
107    my ($self, $opt) = @_;
108    return $self->{_options}{$opt};
109}
110
111=head2 wexported
112
113See L</unexported>
114
115=cut
116
117sub wexported { unexported(@_) }
118
119=head2 unexported ($wexported)
120
121Set base to report unexported object or not
122
123=cut
124
125sub unexported {
126    my ($self, $wexported) = @_;
127    my $old = $self->{wexported};
128    if (defined($wexported)) {
129        $self->{wexported} = $wexported;
130        $self->log(LA_DEBUG, "Switching exported mode: %s => %s", $old,
131            $wexported);
132    }
133    return($old || 0);
134}
135
136=head2 temp_switch_unexported($CODE, $value)
137
138Switch the base to unexported mode given by C<$value>, run C<$CODE>, restore
139back the previous state and return the result of code ref.
140
141=cut
142
143sub temp_switch_unexported (&;$) {
144    my ($self, $sub, $value) = @_;
145
146    my $old = $self->unexported($value || 0);
147    my $res = $sub->();
148    $self->unexported($old);
149    return $res;
150}
151
152=head2 log($level, $msg, $arg)
153
154Log a message prefixed by database information
155
156=cut
157
158sub log {
159    my ($self, $level, $msg, @args) = @_;
160    my $prefix = 'Base(' . $self->type . '/' . $self->label . ')';
161    LATMOS::Accounts::Log::la_log($level, "$prefix $msg", @args);
162}
163
164=head2 ReportChange($otype, $name, $ref, $changetype, $message, @args)
165
166Functions to report back
167
168=cut 
169
170sub ReportChange {}
171
172=head2 list_supported_objects(@otype)
173
174Return a list of supported object
175
176@type is an additionnal list of objects to check
177
178=cut
179
180sub list_supported_objects {
181    my ($self, @otype) = @_;
182    my %res;
183    foreach my $inc (@INC) {
184        my $sub = 'LATMOS::Accounts::Bases::' . ucfirst($self->type);
185        $sub =~ s/::/\//g;
186        foreach (glob("$inc/$sub/[A-Z]*.pm")) {
187            s/.*\///;
188            s/\.pm$//;
189            $res{lc($_)} = 1;
190        }
191    }
192    $res{$_} = 1 foreach(@otype);
193    my @sobj = grep { $self->is_supported_object($_) } keys %res;
194    la_log(LA_DEBUG, "Base %s supported objects: %s", $self->type, join(', ', @sobj));
195    return @sobj;
196}
197
198=head2 ordered_objects
199
200Return supported object type ordered in best order for synchronisation
201
202=cut
203
204sub ordered_objects {
205    my ($self) = @_;
206
207    my %deps;
208    my %maxdeps;
209    my @objs = sort { $b cmp $a } $self->list_supported_objects;
210    foreach my $obj (@objs) {
211        foreach my $at ($self->list_canonical_fields($obj)) {
212            my $attr = $self->attribute($obj, $at);
213            $attr->ro and next;
214            $attr->{delayed} and next;
215            if (my $res = $attr->reference) {
216                $deps{$obj}{$res} ||= 1;
217                if ($attr->mandatory) {
218                    $deps{$obj}{$res} = 2;
219                    $maxdeps{$res} = 1;
220                }
221            }
222        }
223    }
224
225    sort {
226        if (keys %{$deps{$a} || {}}) {
227            if (keys %{$deps{$b} || {}}) {
228                return (
229                    ($deps{$a}{$b} || 0) > ($deps{$b}{$a} || 0) ?  1 :
230                    ($deps{$b}{$a} || 0) > ($deps{$a}{$b} || 0) ? -1 :
231                    ($maxdeps{$b} || 0) - ($maxdeps{$a} || 0)
232                );
233            } else {
234                return 1;
235            }
236        } elsif (keys %{$deps{$b} || {}}) {
237            return -1;
238        } else {
239            return  ($maxdeps{$b} || 0) - ($maxdeps{$a} || 0)
240        }
241    } @objs;
242}
243
244sub _load_obj_class {
245    my ($self, $otype) = @_;
246
247    $otype or die "No type given: " . join(', ', caller) . "\n";
248    # finding perl class:
249    my $pclass = ref $self;
250    $pclass .= '::' . ucfirst(lc($otype));
251    eval "require $pclass;";
252    if ($@) {
253        $self->log(LA_DEBUG, 'Cannot load perl class %s', $pclass);
254        return
255    } # error message ?
256    return $pclass;
257}
258
259
260=head2 is_supported_object($otype)
261
262Return true is object type $otype is supported
263
264=cut
265
266sub is_supported_object {
267    my ($self, $otype) = @_;
268   
269    if (my $pclass = $self->_load_obj_class($otype)) {
270        if ($pclass->can('is_supported')) {
271            return $pclass->is_supported($self);
272        } else {
273            return 1;
274        }
275    } else {
276        return 0;
277    }
278}
279
280=head2 list_objects($otype, %options)
281
282Return the list of UID for object of $otype.
283
284=cut
285
286sub list_objects {
287    my ($self, $otype) = @_;
288    my $pclass = $self->_load_obj_class($otype) or return;
289    $pclass->list($self);
290}
291
292=head2 listRealObjects
293
294Return the list of UID for object of $otype, alias objects are not return
295
296Options depend of database support
297
298=cut
299
300sub listRealObjects {
301    my ($self, $otype) = @_;
302    $self->list_objects($otype);
303}
304
305=head2 get_object($type, $id)
306
307Return an object of $type (typically user or group) having identifier
308$id.
309
310=cut
311
312sub get_object {
313    my ($self, $otype, $id) = @_;
314
315    # finding perl class:
316    my $pclass = $self->_load_obj_class($otype) or return;
317    my $newobj = "$pclass"->new($self, $id);
318
319    defined($newobj) or do {
320        $self->log(LA_DEBUG, "$pclass->new() returned undef for $otype / %s", $id || '(none)');
321        return;
322    };
323
324    $newobj->{_base} = $self;
325    $newobj->{_type} = lc($otype);
326    $newobj->{_id} ||= $id;
327
328    return $newobj;
329}
330
331=head2 create_c_object($type, $id, %data)
332
333Create and return an object of type $type with unique id
334$id having %data using canonical fields
335
336=cut
337
338sub create_c_object {
339    my ($self, $otype, $id, %cdata) = @_;
340    $self->check_acl($otype, '@CREATE', 'w') or do {
341        $self->log(LA_WARN, 'permission denied to create object type %s',
342            $otype);
343        return;
344    };
345
346    $self->_create_c_object($otype, $id, %cdata);
347}
348
349sub _create_c_object {
350    my ($self, $otype, $id, %cdata) = @_;
351
352    $id ||= ''; # Avoid undef
353
354    if (my $chk = (
355        lc($otype) eq 'user' || lc($otype) eq 'group')
356        ? LATMOS::Accounts::Utils::check_ug_validity($id)
357        : LATMOS::Accounts::Utils::check_oid_validity($id)) {
358        $self->log(LA_ERR, "Cannot create $otype with ID $id `%s:'", $chk);
359        return;
360    }
361    foreach my $cfield (keys %cdata) {
362        $self->check_allowed_values($otype, $cfield, $cdata{$cfield}) or do {
363            my $last = LATMOS::Accounts::Log::lastmessage(LA_ERR);
364            $self->log(LA_ERR, "Cannot create $otype, wrong value%s", ($last ? ": $last" : ''));
365            return;
366        };
367    }
368
369    # populating default value
370    {
371        my %default = $self->compute_default($otype, $id, %cdata);
372        foreach my $k (keys %default) {
373            $cdata{$k} = $default{$k} unless(exists($cdata{$k}));
374        }
375    }
376
377    my %data;
378    foreach my $cfield (keys %cdata) {
379        my $attribute = $self->attribute($otype, $cfield) or next;
380        $attribute->ro and next;
381        $data{$attribute->iname} = $cdata{$cfield};
382    }
383    $self->create_object($otype, $id, %data) or return;
384    my $obj = $self->get_object($otype, $id) or return;
385    $obj->ReportChange('Create', 'Object created with %s', join(', ', sort keys %cdata));
386
387    foreach my $attrname (keys %data) {
388        my $attribute = $self->attribute($obj->type, $attrname) or next;
389        $attribute->monitored or next;
390
391        $obj->ReportChange('Attributes', '%s set to %s', $attrname,
392            (ref $data{$attrname}
393                ? join(', ', @{ $data{$attrname} })
394                : $data{$attrname}) || '(none)');
395    }
396
397    $obj
398}
399
400=head2 create_object($type, $id, %data)
401
402Create and return an object of type $type with unique id
403$id having %data.
404
405This method should be provided by the data base handler.
406
407=cut
408
409sub create_object {
410    my ($self, $otype, $id, %data) = @_;
411    "$id" or do {
412        $self->log(LA_ERR, l("Cannot create %s object with empty id"),
413            $otype);
414        return;
415    };
416    my $pclass = $self->_load_obj_class($otype) or do {
417        $self->log(LA_ERR, "Cannot create %s object type (cannot load class)",
418            $otype);
419        return;
420    };
421
422    if (!$pclass->checkValues($self, $id, %data)) {
423        my $last = LATMOS::Accounts::Log::lastmessage(LA_ERR);
424        la_log(LA_ERR,
425            'Cannot create %s (%s) in base %s (%s): wrong value%s',
426            $id, $otype, $self->label, $self->type,
427            ($last ? ": $last" : ''),
428        );
429        return;
430    }
431
432    if ($pclass->_create($self, $id, %data)) {
433        la_log(LA_INFO,
434            'Object %s (%s) created in base %s (%s)',
435            $id, $otype, $self->label, $self->type,
436        );
437    } else {
438        my $last = LATMOS::Accounts::Log::lastmessage(LA_ERR);
439        la_log(LA_ERR,
440            'Object creation %s (%s) in base %s (%s) failed%s',
441            $id, $otype, $self->label, $self->type,
442            ($last ? ": $last" : ''),
443        );
444        return;
445    };
446
447    $self->get_object($otype, $id);
448}
449
450=head2 defaultAttributeValue($otype, $attr)
451
452Return default static value for attribute C<$attr>
453
454=cut
455
456sub defaultAttributeValue {
457    my ($self, $otype, $attr) = @_;
458
459    my %def = %{ $self->{defattr} || {}};
460    return $def{"$otype.$attr"} || '';
461}
462
463=head2 compute_default($otype, $id, %cdata)
464
465Return a hash containing value to set for new object
466
467=cut
468
469sub compute_default {
470    my ($self, $otype, $id, %cdata) = @_;
471
472    my %default;
473    foreach my $def (keys %{ $self->{defattr} || {}}) {
474        if ($def =~ /^$otype\.(.*)$/) {
475            $default{$1} = $self->{defattr}{$def} if(!$cdata{$1});
476        }
477    }
478
479    # computed default value (not a simple set)
480    if (lc($otype) eq 'user') {
481        if (!$cdata{homeDirectory}) {
482            $default{homeDirectory} = $self->{defattr}{'user.homebase'}
483                ? $self->{defattr}{'user.homebase'} . "/$id" 
484                : '';
485        }
486
487        if (!$cdata{uidNumber}) {
488            $default{uidNumber} ||= $self->find_next_numeric_id('user', 'uidNumber',
489            $self->{defattr}{'user.min_uid'}, $self->{defattr}{'user.max_uid'});
490        }
491
492        my $mailid = $cdata{givenName} && $cdata{sn}
493            ? sprintf('%s.%s',
494                to_ascii(lc($cdata{givenName})),
495                to_ascii(lc($cdata{sn})),)
496            : undef;
497        $mailid =~ s/\s+/-/g if($mailid);
498
499        if ($mailid &&
500            $self->is_supported_object('aliases') &&
501            ! $self->get_object('aliases', $mailid)) {
502            if (my $attr = $self->attribute($otype, 'mail')) {
503                if ((!$attr->ro) && $self->{defattr}{'user.maildomain'}) {
504                    $default{mail} ||= sprintf('%s@%s',
505                    $mailid,
506                    $self->{defattr}{'user.maildomain'});
507                }
508            }
509            if (my $attr = $self->attribute($otype, 'aliases')) {
510                $default{aliases} ||= $mailid unless ($attr->ro);
511            }
512            if (my $attr = $self->attribute($otype, 'revaliases')) {
513                $default{revaliases} ||= $mailid unless ($attr->ro);
514            }
515        }
516    } elsif (lc($otype) eq 'group') {
517        if (!$cdata{gidNumber}) {
518            $default{gidNumber} ||= $self->find_next_numeric_id(
519                'group', 'gidNumber',
520                $self->{defattr}{'group.min_gid'},
521                $self->{defattr}{'group.max_gid'}
522            );
523        }
524    }
525
526    return %default;
527}
528
529sub _allowed_values {
530    $_[0]->{_allowed_values}
531}
532
533=head2 obj_attr_allowed_values ($otype, $attr)
534
535Return value allowed for this attribute
536
537=cut
538
539sub obj_attr_allowed_values {
540    my ($self, $otype, $attr) = @_;
541    if ($self->_allowed_values &&
542        $self->_allowed_values->SectionExists("$otype.$attr")) {
543        return grep { defined($_) } $self->_allowed_values->val("$otype.$attr", 'allowed');
544    }
545    return();
546}
547
548=head2 check_allowed_values ($otype, $attr, $attrvalues)
549
550Check attributes C<$attr> of object type C<$otype> allow values C<$attrvalues>
551
552=cut
553
554sub check_allowed_values {
555    my ($self, $otype, $attr, $attrvalues) = @_;
556    $self->_allowed_values or return 1;
557    my @values = ref $attrvalues ? @{ $attrvalues } : $attrvalues;
558    foreach my $value (@values) {
559        $value or next;
560        if (my @allowed = $self->obj_attr_allowed_values($otype, $attr)) {
561            grep { $value eq $_ } @allowed or do {
562                $self->log(LA_ERR,
563                    "value `%s' is not allow for %s.%s per configuration (allowed_values)",
564                    $value, $otype, $attr
565                );
566                return;
567            };
568        }
569    }
570    return 1;
571}
572
573=head2 list_canonical_fields($otype, $for)
574
575Return the list of supported fields by the database for object type $otype.
576
577Optionnal $for specify the goal for which the list is requested, only supported
578fields will be returns
579
580=cut
581
582sub list_canonical_fields {
583    my ($self, $otype, $for) = @_;
584    $for ||= 'rw';
585    my $pclass = $self->_load_obj_class($otype) or return;
586    sort $pclass->_canonical_fields($self, $for);
587}
588
589sub _get_attr_schema {
590    my ($self, $otype) = @_;
591    my $pclass = $self->_load_obj_class($otype) or return;
592    return $pclass->_get_attr_schema($self);
593}
594
595=head2 get_attr_schema
596
597Deprecated
598
599=cut
600
601# TODO: kill this
602
603sub get_attr_schema {
604    my ($self, $otype, $attribute) = @_;
605    my $info = $self->_get_attr_schema($otype);
606    if ($info->{$attribute}) {
607        return $info->{$attribute};
608    } else {
609        return;
610    }
611}
612
613=head2 attribute($otype, $attribute)
614
615Return attribute object.
616
617See L<LATMOS::Accounts::Bases::Attribute>
618
619=cut
620
621sub attribute {
622    my ($self, $otype, $attribute) = @_;
623
624    my $attrinfo;
625    if (!ref $attribute) {
626       $attrinfo = $self->get_attr_schema($otype, $attribute)
627            or return;
628        $attrinfo->{name} = $attribute;
629    } else {
630        $attrinfo = $attribute;
631    }
632
633    return LATMOS::Accounts::Bases::Attributes->new(
634        $attrinfo,
635        $self,
636        $otype,
637    );
638}
639
640=head2 delayed_fields
641
642DEPRECATED
643
644=cut 
645
646# TODO: kill this
647
648sub delayed_fields {
649    my ($self, $otype, $for) = @_;
650    $self->log(LA_WARN, "calling DEPRECATED delayed_fields " . join(',',
651            caller));
652    $for ||= 'rw';
653    my @attrs;
654    foreach ($self->list_canonical_fields($otype, $for)) {
655        my $attr = $self->attribute($otype, $_) or next;
656        $for =~ /w/ && $attr->ro and next;
657        $attr->delayed or next;
658        push(@attrs, $_);
659    }
660    @attrs
661}
662
663=head2 ochelper ($otype)
664
665Return L<LATMOS::Accounts::Bases::OChelper> object
666
667=cut
668
669sub ochelper {
670    my ($self, $otype) = @_;
671    my $pclass = ucfirst(lc($otype));
672    foreach my $class (
673        ref($self) . '::OCHelper::' . $pclass,
674        ref($self) . '::OCHelper',
675        "LATMOS::Accounts::Bases::OCHelper::$pclass",
676        'LATMOS::Accounts::Bases::OCHelper' ) {
677        eval "require $class;";
678        if ($@) { next } # error message ?
679        my $ochelper = "$class"->new($self, $otype);
680        return $ochelper;
681    }
682    return;
683}
684
685=head2 delete_object($otype, $id)
686
687Destroy from data base object type $otype having id $id.
688
689=cut
690
691sub delete_object {
692    my ($self, $otype, $id) = @_;
693    my $obj = $self->get_object($otype, $id) or do {
694        $self->log(LA_WARN, 'Cannot delete %s/%s: no such object',
695            $otype, $id);
696        return;
697    };
698    $self->check_acl($obj, '@DELETE', 'w') or do {
699        $self->log(LA_WARN, 'permission denied to delete %s/%s',
700            $otype, $id);
701        return;
702    };
703    my $ref = $obj->Iid;
704    if (my $res = $self->_delete_object($otype, $id)) {
705        $self->ReportChange($otype, $id, $ref, 'Delete', 'Object deleted');
706        return $res;
707    }
708    return;
709}
710
711sub _delete_object {
712    my ($self, $otype, $id) = @_;
713    my $pclass = $self->_load_obj_class($otype);
714    $pclass->_delete($self, $id);
715}
716
717=head2 rename_object($otype, $id, $newid)
718
719Rename an object.
720
721=cut
722
723sub rename_object {
724    my ($self, $otype, $id, $newid) = @_;
725
726    $self->log(LA_DEBUG, "Trying to rename $otype/$id to $newid");
727    my $obj = $self->get_object($otype, $id) or do {
728        $self->log(LA_WARN, 'Cannot rename %s/%s: no such object',
729            $otype, $id);
730        return;
731    };
732    if (my $chk = (lc($otype) eq 'user' || lc($otype) eq 'group')
733        ? LATMOS::Accounts::Utils::check_ug_validity($newid)
734        : LATMOS::Accounts::Utils::check_oid_validity($newid)) {
735        $self->log(LA_ERR, "Cannot rename $otype/$id to ID $newid `%s:'", $chk);
736        return;
737    }
738    $self->check_acl($obj, '@DELETE', 'w') &&
739    $self->check_acl($obj, '@CREATE', 'w') or do {
740        $self->log(LA_WARN, 'permission denied to rename %s/%s',
741            $otype, $id);
742        return;
743    };
744
745    my $oldref = $obj->Iid;
746
747    if (my $res = $self->_rename_object($otype, $id, $newid)) {
748        my $newobj = $self->get_object($otype, $newid) or do {
749            $self->log(LA_WARN, 'Cannot get object %s/%s: rename failed ?',
750                $otype, $id);
751            return;
752        };
753
754        $self->ReportChange($otype, $id, $oldref, 'Rename', 'Object rename to %s', $newid);
755        $newobj->ReportChange('Rename', 'Object renamed from %s', $id);
756        return $res;
757    }
758    return;
759}
760
761sub _rename_object {
762    my ($self, $otype, $id, $newid) = @_;
763    my $pclass = $self->_load_obj_class($otype);
764    $pclass->can('_rename') or do {
765        $self->log(LA_ERR, 'rename object type %s is unsupported', $otype);
766        return;
767    };
768    $pclass->_rename($self, $id, $newid);
769}
770
771=head2 is_transactionnal
772
773Return True is the database support commit and rollback
774
775=cut
776
777sub is_transactionnal {
778    my ($self) = @_;
779    return($self->can('_rollback') && $self->can('_commit'));
780}
781
782=head2 commit
783
784Save change into the database if change are not done immediately.
785This should always be called as you don't know when change are applied.
786
787Return always true if database does not support any transaction.
788
789The driver should provides a _commit functions to save data.
790
791=cut
792
793sub commit {
794    my ($self) = @_;
795    if ($self->can('_commit')) {
796        la_log(LA_DEBUG, 'Commiting data');
797        if (!(my $res = $self->_commit)) {
798            la_log(LA_ERR, "Commit error on %s", $_->label);
799            return $res;
800        }
801    }
802
803    $self->postcommit();
804
805    return 1;
806}
807
808=head2 postcommit
809
810Run postcommit command
811
812=cut
813
814sub postcommit {
815    my ($self) = @_;
816
817    if ($self->{_options}{postcommit}) {
818        exec_command($self->{_options}{postcommit},
819            {
820                BASE => $self->label,
821                BASETYPE => $self->type,
822                HOOK_TYPE => 'POST',
823                CONFIG => $self->{_options}{configdir}, 
824            }
825        );
826    } else {
827        return 1;
828    }
829}
830
831=head2 rollback
832
833If database support transaction, rollback changes. Return false
834if database does not support.
835
836If supported, driver should provides a _rollback functions
837
838=cut
839
840sub rollback {
841    my ($self) = @_;
842    if ($self->can('_rollback')) {
843       la_log(LA_DEBUG, 'Rolling back data');
844       return $self->_rollback;
845   } else {
846       return 0;
847   }
848}
849
850=head2 current_rev
851
852Return the current revision of the database
853
854Must be provide by base driver if incremental synchro is supported
855
856=cut
857
858sub current_rev { return }
859
860=head2 list_objects_from_rev($otype, $rev)
861
862Return the list of UID for object of $otype.
863
864=cut
865
866sub list_objects_from_rev {
867    my ($self, $otype, $rev) = @_;
868    my $pclass = $self->_load_obj_class($otype) or return;
869    if (defined($rev) && $pclass->can('list_from_rev')) {
870        return $pclass->list_from_rev($self, $rev);
871    } else {
872        # no support, return all objects...
873        return $self->list_objects($otype);
874    }
875}
876
877=head2 sync_object_from($srcbase, $otype, $id, %options)
878
879Sync object type C<$otype> C<$id> from base C<$srcbase> to current base.
880
881C<%options>:
882
883=over 4
884
885=item nodelete
886
887Don't delete object if the object synchronize don't exist in source base
888
889=back
890
891=cut
892
893sub sync_object_from {
894    my ($self, $srcbase, $otype, $id, %options) = @_;
895
896    # is the object type supported by both
897    foreach ($self, $srcbase) {
898        $_->is_supported_object($otype) or return '';
899    }
900   
901    if (my $srcobj = $srcbase->get_object($otype, $id)) {
902        return $self->sync_object($srcobj, %options);
903    } elsif (!$options{nodelete}) {
904        $self->_delete_object($otype, $id) and return 'DELETED';
905    }
906    return;
907}
908
909=head2 sync_object
910
911Synchronise an object into this base
912
913=cut
914
915sub sync_object {
916    my ($self, $srcobj, %options) = @_;
917    $self->is_supported_object($srcobj->type) or return '';
918    my @fields = $options{attrs}
919        ? @{ $options{attrs} }
920        : $self->list_canonical_fields($srcobj->type, 'w');
921    my %data;
922    foreach (@fields) {
923        # check attribute exists in source:
924        my $attr = $srcobj->attribute($_) or next;
925        $attr->readable or next;
926        if (! $options{onepass}) {
927            if ($options{firstpass}) {
928                $attr->delayed and next;
929            } else {
930                $attr->delayed or next;
931            }
932        }
933        $data{$_} = $srcobj->GetAttributeValue($_);
934    }
935    if (my $dstobj = $self->get_object($srcobj->type, $srcobj->id)) {
936        keys %data or return 'SYNCED';
937        foreach (keys %data) {
938            if (!$dstobj->attribute($_) ||
939                $dstobj->attribute($_)->ro) {
940                delete($data{$_});
941            }
942        }
943        my $res = $dstobj->_set_c_fields(%data);
944        if (defined $res) {
945            return $res ? 'SYNCED' : '';
946        } else {
947            return;
948        }
949    } elsif(!$options{nocreate}) {
950        if ((! $options{firstpass}) && (!$options{onepass})) {
951            $self->log(LA_ERR, 'This is not first pass, creation wanted but denied');
952            return;
953        }
954        if (my $res = $self->_create_c_object($srcobj->type, $srcobj->id, %data)) {
955            return 'CREATED'
956        } else {
957            return;
958        }
959    } else {
960        # No error, but creation is denied
961        return 'Creation skipped';
962    }
963
964    return;
965}
966
967=head2 search_objects($otype, @filter)
968
969Search object according @filter. @filter is a list
970of field/value which should match.
971
972A default function is provided but each db driver can provide
973an optimize version.
974
975=cut
976
977sub search_objects {
978    my ($self, $otype, @filter) = @_;
979    my $pclass = $self->_load_obj_class($otype) or return;
980    $pclass->search($self, @filter);
981}
982
983=head2 attributes_summary($otype, $attr)
984
985Return couple object id / value for attribute C<$attr> of object type C<$otype>
986
987This method is designed to be faster than fetching object one by one.
988
989=cut
990
991sub attributes_summary {
992    my ($self, $otype, $attr) = @_;
993    my $pclass = $self->_load_obj_class($otype) or return;
994    $pclass->attributes_summary($self, $attr);
995}
996
997=head2 attributes_summary_by_object($otype, $attr)
998
999Return couple object id / value for attribute C<$attr> of object type C<$otype>
1000
1001This method is designed to be faster than fetching object one by one.
1002
1003=cut
1004
1005sub attributes_summary_by_object {
1006    my ($self, $otype, $attr) = @_;
1007    my $pclass = $self->_load_obj_class($otype) or return;
1008    $pclass->attributes_summary_by_object($self, $attr);
1009}
1010
1011=head2 fetchObjectInfo($otype, $attributes, @filters)
1012
1013Return an hashref with attribute data for each object matching @filters.
1014
1015If C<$attributes> can be an array ref to a list of attributes.
1016
1017=cut
1018
1019sub fetchObjectInfo {
1020    my ($self, $otype, $attributes, @filters) = @_;
1021
1022    my %results = map { $_ => {} }  $self->search_objects($otype, @filters);
1023
1024    foreach my $attr ( ref $attributes ? @$attributes : $attributes) {
1025        my %res = $self->attributes_summary_by_object($otype, $attr);
1026        foreach my $obj (keys %results) {
1027            $results{$obj}{$attr} = $res{$obj};
1028        }
1029    }
1030
1031    \%results;
1032}
1033
1034=head2 find_next_numeric_id($otype, $field, $min, $max)
1035
1036Return, if possible, next numeric id available (typically unix user UID).
1037
1038=cut
1039
1040sub find_next_numeric_id {
1041    my ($self, $otype, $field, $min, $max) = @_;
1042    my $pclass = $self->_load_obj_class($otype) or return;
1043    $pclass->find_next_numeric_id($self, $field, $min, $max);
1044}
1045
1046=head2 authenticate_user($username, $passwd)
1047
1048Return true if authentication success.
1049
1050Must be override by driver if the base have a proper authentication method
1051
1052=cut
1053
1054sub authenticate_user {
1055    my ($self, $username, $passwd) = @_;
1056    $username or return;
1057    my $uobj = $self->get_object('user', $username) or do {
1058        la_log(LA_ERR, "Cannot authenticate non existing user $username");
1059        return;
1060    };
1061
1062    if ($uobj->_get_c_field('expired')) {
1063        la_log(LA_ERR, "Account $username has expired (%s)",
1064            $uobj->_get_c_field('expired'));
1065        return;
1066    }
1067
1068    if ($uobj->_get_c_field('locked')) {
1069        la_log(LA_ERR, "Account $username is currently locked");
1070        return;
1071    }
1072
1073    my $password = $uobj->get_field('userPassword') or do {
1074        la_log(LA_ERR, "Cannot authenticate user $username having no passwd");
1075        return;
1076    };
1077    if ($password eq crypt($passwd, $password)) { # crypt unix
1078        la_log(LA_NOTICE, "User $username authenticated");
1079        return 1;
1080    } else {
1081        la_log(LA_ERR, "Cannot authenticate user $username");
1082        return 0;
1083    }
1084}
1085
1086=head2 connect($username, $password)
1087
1088Authenticate the user and store the username as connected
1089
1090=cut
1091
1092sub connect {
1093    my ($self, $username, $password) = @_;
1094    my $auth = $self->authenticate_user($username, $password);
1095    if ($auth) {
1096        $self->{_user} = $username;
1097        la_log(LA_DEBUG, "Connected as $username");
1098    }
1099    return $auth;
1100}
1101
1102=head2 user
1103
1104Return the current connected username
1105
1106=cut
1107
1108sub user { $_[0]->{_user} }
1109
1110=head2 check_acl($obj, $attr, $perm)
1111
1112Return true if connected user have C<$perm> permission on attribute C<$attr> of
1113object C<$obj>.
1114
1115=cut
1116
1117sub check_acl {
1118    my ($self, $obj, $attr, $perm) = @_;
1119    if ($self->{_acls}) {
1120        my ($who, $groups) = ($self->user || '');
1121
1122        $self->{_acl_cache}{uid} ||= '';
1123
1124        if ($self->{_acl_cache}{uid} ne $who) {
1125            $self->{_acl_cache}{uid} = $who;
1126            my $uo = $self->get_object('user', $who);
1127            $self->{_acl_cache}{obj} = $uo;
1128            if ($uo) {
1129                $self->{_acl_cache}{groups} = [ $uo->_get_attributes('memberOf') ];
1130            }
1131        }
1132
1133        if ($who && (my $uo = $self->{_acl_cache}{obj})) {
1134            $groups = $self->{_acl_cache}{groups};
1135        } else {
1136            $who = '';
1137        }
1138        my $res = $self->{_acls}->check($obj, $attr, $perm, $who, $groups);
1139        $self->log(LA_INFO, 'permission denied for "%s" to get %s.%s for %s',
1140           $who, ref $obj ? $obj->id . '(' . $obj->type . ')' : $obj, $attr, $perm) if (!$res);
1141        return $res;
1142    } else {
1143        # No acls, woot
1144        return 1;
1145    }
1146}
1147
1148=head2 text_empty_dump($fh, $otype, $options)
1149
1150Empty object dump
1151
1152=cut
1153
1154sub text_empty_dump {
1155    my ($self, $fh, $otype, $options) = @_;
1156    my $pclass = $self->_load_obj_class($otype) or return;
1157    $pclass->text_dump($fh, $options, $self);
1158}
1159
11601;
1161
1162__END__
1163
1164=head1 SEE ALSO
1165
1166=head1 AUTHOR
1167
1168Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.fr<gt>
1169
1170=head1 COPYRIGHT AND LICENSE
1171
1172Copyright (C) 2009 by Thauvin Olivier
1173
1174This library is free software; you can redistribute it and/or modify
1175it under the same terms as Perl itself, either Perl version 5.10.0 or,
1176at your option, any later version of Perl 5 you may have available.
1177
1178=cut
Note: See TracBrowser for help on using the repository browser.