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

Last change on this file since 2255 was 2234, checked in by nanardon, 5 years ago

Allow to use functions in queryformat

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