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

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

Ensure users cannot retrieve password, even encrypted

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