source: LATMOS-Accounts/lib/LATMOS/Accounts/Bases.pm @ 783

Last change on this file since 783 was 783, checked in by nanardon, 14 years ago
  • kill debug warning
  • Property svn:keywords set to Id Rev
File size: 17.7 KB
Line 
1package LATMOS::Accounts::Bases;
2
3use 5.010000;
4use strict;
5use warnings;
6use LATMOS::Accounts::Bases::Objects;
7use LATMOS::Accounts::Log;
8use LATMOS::Accounts::Utils qw(exec_command to_ascii);
9
10our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
11
12=head1 NAME
13
14LATMOS::Accounts::Bases - Base class for account data bases
15
16=head1 SYNOPSIS
17
18  use LATMOS::Accounts::Bases;
19  my $base = LATMOS::Accounts::Bases->new('type', %options);
20  ...
21
22=head1 DESCRIPTION
23
24This module provide basic functions for various account base
25
26=head1 FUNTIONS
27
28=cut
29
30=head2 new($type, %options)
31
32Return, if success, a new data base account object, $type is
33account base type, %options to setup the base.
34
35=cut
36
37sub new {
38    my ($class, $type, %options) = @_;
39
40    my $pclass = ucfirst(lc($type));
41    eval "require LATMOS::Accounts::Bases::$pclass;";
42    if ($@) { return } # error message ?
43    my $base = "LATMOS::Accounts::Bases::$pclass"->new(%options);
44    $base->{_type} = lc($pclass);
45    $base->{_label} = $options{label};
46    $base->{_options} = { %options };
47    $base->{wexported} = 0;
48    $base->{defattr} = $options{defattr};
49    $base->{_acls} = $options{acls};
50    $base->{_allowed_values} = $options{allowed_values};
51    la_log(LA_DEBUG, 'Instanciate base %s (%s)', ($options{label} || 'N/A'), $pclass);
52    $base
53}
54
55sub wexported {
56    my ($self, $wexported) = @_;
57    my $old = $self->{wexported};
58    if (defined($wexported)) {
59        $self->{wexported} = $wexported;
60        $self->log(LA_DEBUG, "Switching exported mode: %s => %s", $old,
61            $wexported);
62    }
63    return($old || 0);
64}
65
66sub log {
67    my ($self, $level, $msg, @args) = @_;
68    my $prefix = 'Base(' . $self->type . '/' . $self->label . ')';
69    LATMOS::Accounts::Log::la_log($level, "$prefix $msg", @args);
70}
71
72sub label {
73    $_[0]->{_label} || 'NoLabel';
74}
75
76sub type {
77    $_[0]->{_type};
78}
79
80sub allowed_values {
81    $_[0]->{_allowed_values}
82}
83
84sub obj_attr_allowed_values {
85    my ($self, $otype, $attr) = @_;
86    if ($self->allowed_values) {
87        return $self->allowed_values->val("$otype.$attr", 'allowed');
88    }
89    return();
90}
91
92sub check_allowed_values {
93    my ($self, $otype, $attr, $attrvalues) = @_;
94    $self->allowed_values or return 1;
95    my @values = ref $attrvalues ? @{ $attrvalues } : $attrvalues;
96    foreach my $value (@values) {
97        $value or next;
98        if (my @allowed = $self->allowed_values->val("$otype.$attr", 'allowed')) {
99            grep { $value eq $_ } @allowed or do {
100                $self->log(LA_ERR,
101                    "value `%s' is not allow for %s.%s per configuration (allowed_values)",
102                    $value, $otype, $attr
103                );
104                return;
105            };
106        }
107    }
108    return 1;
109}
110
111sub _load_obj_class {
112    my ($self, $otype) = @_;
113
114    # finding perl class:
115    my $pclass = ref $self;
116    $pclass .= '::' . ucfirst(lc($otype));
117    eval "require $pclass;";
118    if ($@) {
119        $self->log(LA_DEBUG, 'Cannot load perl class %s', $pclass);
120        return
121    } # error message ?
122    return $pclass;
123}
124
125=head2 list_canonical_fields($otype, $for)
126
127Return the list of supported fields by the database for object type $otype.
128
129Optionnal $for specify the goal for which the list is requested, only supported
130fields will be returns
131
132=cut
133
134sub list_canonical_fields {
135    my ($self, $otype, $for) = @_;
136    $for ||= 'rw';
137    my $pclass = $self->_load_obj_class($otype) or return;
138    sort $pclass->_canonical_fields($self, $for);
139}
140
141sub delayed_fields {
142    my ($self, $otype, $for) = @_;
143    $for ||= 'rw';
144    my $pclass = $self->_load_obj_class($otype) or return;
145    $pclass->_delayed_fields($self, $for);
146}
147
148=head2 get_field_name($otype, $c_fields, $for)
149
150Return the internal fields name for $otype object for
151canonical fields $c_fields
152
153=cut
154
155sub get_field_name {
156    my ($self, $otype, $c_fields, $for) = @_;
157    $for ||= 'rw';
158    my $pclass = $self->_load_obj_class($otype) or return;
159    $pclass->_get_field_name($c_fields, $self, $for);
160}
161
162=head2 list_supported_objects(@otype)
163
164Return a list of supported object
165
166@type is an additionnal list of objects to check
167
168=cut
169
170sub list_supported_objects {
171    my ($self, @otype) = @_;
172    my %res;
173    foreach my $inc (@INC) {
174        my $sub = 'LATMOS::Accounts::Bases::' . ucfirst($self->type);
175        $sub =~ s/::/\//g;
176        foreach (glob("$inc/$sub/[A-Z]*.pm")) {
177            s/.*\///;
178            s/\.pm$//;
179            $res{lc($_)} = 1;
180        }
181    }
182    $res{$_} = 1 foreach(@otype);
183    my @sobj = grep { $self->is_supported_object($_) } keys %res;
184    la_log(LA_DEBUG, "Base %s supported objects: %s", $self->type, join(', ', @sobj));
185    return @sobj;
186}
187
188=head2 is_supported_object($otype)
189
190Return true is object type $otype is supported
191
192=cut
193
194sub is_supported_object {
195    my ($self, $otype) = @_;
196    return $self->_load_obj_class($otype) ? 1 : 0;
197}
198
199=head2 list_objects($otype)
200
201Return the list of UID for object of $otype.
202
203=cut
204
205sub list_objects {
206    my ($self, $otype) = @_;
207    my $pclass = $self->_load_obj_class($otype) or return;
208    $pclass->list($self);
209}
210
211=head2 get_object($type, $id)
212
213Return an object of $type (typically user or group) having identifier
214$id.
215
216=cut
217
218sub get_object {
219    my ($self, $otype, $id) = @_;
220
221    return LATMOS::Accounts::Bases::Objects->_new($self, $otype, $id);
222}
223
224=head2 create_object($type, $id, %data)
225
226Create and return an object of type $type with unique id
227$id having %data.
228
229This method should be provided by the data base handler.
230
231=cut
232
233sub create_object {
234    my ($self, $otype, $id, %data) = @_;
235    my $pclass = $self->_load_obj_class($otype);
236    if ($pclass->_create($self, $id, %data)) {
237        la_log(LA_INFO,
238            'Object %s (%s) created in base %s (%s)',
239            $id, $otype, $self->label, $self->type
240        );
241    } else {
242        la_log(LA_ERR,
243            'Object creation %s (%s) in base %s (%s) failed',
244            $id, $otype, $self->label, $self->type
245        );
246        return;
247    };
248    $self->get_object($otype, $id);
249}
250
251=head2 create_c_object($type, $id, %data)
252
253Create and return an object of type $type with unique id
254$id having %data using canonical fields
255
256=cut
257
258sub create_c_object {
259    my ($self, $otype, $id, %cdata) = @_;
260    $self->check_acl($otype, '@CREATE', 'w') or do {
261        $self->log(LA_WARN, 'permission denied to create object type %s',
262            $otype);
263        return;
264    };
265    if (my $chk = (lc($otype) eq 'user' || lc($otype) eq 'group')
266        ? LATMOS::Accounts::Utils::check_ug_validity($id)
267        : LATMOS::Accounts::Utils::check_oid_validity($id)) {
268        $self->log(LA_ERR, "Cannot create $otype with ID $id `%s:'", $chk);
269        return;
270    }
271    foreach my $cfield (keys %cdata) {
272        $self->check_allowed_values($otype, $cfield, $cdata{$cfield}) or do {
273            $self->log(LA_ERR, "Cannot create $otype, wrong value");
274            return;
275        };
276    }
277
278    $self->_create_c_object($otype, $id, %cdata);
279}
280
281sub _create_c_object {
282    my ($self, $otype, $id, %cdata) = @_;
283
284    # populating default value
285    foreach my $def (keys %{ $self->{defattr} || {}}) {
286        if ($def =~ /^$otype\.(.*)$/) {
287            $cdata{$1} = $self->{defattr}{$def} if(!$cdata{$1});
288        }
289    }
290    if (lc($otype) eq 'user') {
291        $cdata{homeDirectory} ||= $self->{defattr}{'user.homebase'} ?
292            $self->{defattr}{'user.homebase'} . "/$id" : '';
293        $cdata{uidNumber} ||= $self->find_next_numeric_id('user', 'uidNumber',
294            $self->{defattr}{'user.min_uid'}, $self->{defattr}{'user.max_uid'});
295        my $mailid = $cdata{givenName} && $cdata{sn}
296            ? sprintf('%s.%s',
297                to_ascii(lc($cdata{givenName})),
298                to_ascii(lc($cdata{sn})),)
299            : undef;
300
301        if ($mailid &&
302            $self->is_supported_object('aliases') &&
303            ! $self->get_object('aliases', $mailid)) {
304            if ($self->get_field_name($otype, 'mail', 'write')) {
305                if ($self->{defattr}{'user.maildomain'}) {
306                    $cdata{mail} ||= sprintf('%s@%s',
307                    $mailid,
308                    $self->{defattr}{'user.maildomain'});
309                }
310            }
311            if ($self->get_field_name($otype, 'aliases', 'write')) {
312                $cdata{aliases} ||= $mailid;
313            }
314            if ($self->get_field_name($otype, 'revaliases', 'write')) {
315                $cdata{revaliases} ||= $mailid;
316            }
317        }
318    } elsif (lc($otype) eq 'group') {
319        $cdata{gidNumber} ||= $self->find_next_numeric_id('group', 'gidNumber',
320            $self->{defattr}{'group.min_gid'}, $self->{defattr}{'group.max_gid'});
321    }
322    my %data;
323    foreach my $cfield (keys %cdata) {
324        my $field = $self->get_field_name($otype, $cfield, 'write') or next;
325        $data{$field} = $cdata{$cfield};
326    }
327    keys %data or return 0; # TODO: return an error ?
328    $self->create_object($otype, $id, %data);
329}
330
331=head2 delete_object($otype, $id)
332
333Destroy from data base object type $otype having id $id.
334
335=cut
336
337sub delete_object {
338    my ($self, $otype, $id) = @_;
339    my $obj = $self->get_object($otype, $id) or do {
340        $self->log(LA_WARN, 'Cannot delete %s/%s: no such object',
341            $otype, $id);
342        return;
343    };
344    $self->check_acl($obj, '@DELETE', 'w') or do {
345        $self->log(LA_WARN, 'permission denied to delete %s/%s',
346            $otype, $id);
347        return;
348    };
349    $self->_delete_object($otype, $id);
350}
351
352sub _delete_object {
353    my ($self, $otype, $id) = @_;
354    my $pclass = $self->_load_obj_class($otype);
355    $pclass->_delete($self, $id);
356}
357
358=head2 rename_object($otype, $id, $newid)
359
360Rename an object.
361
362=cut
363
364sub rename_object {
365    my ($self, $otype, $id, $newid) = @_;
366
367    my $obj = $self->get_object($otype, $id) or do {
368        $self->log(LA_WARN, 'Cannot rename %s/%s: no such object',
369            $otype, $id);
370        return;
371    };
372    if (my $chk = (lc($otype) eq 'user' || lc($otype) eq 'group')
373        ? LATMOS::Accounts::Utils::check_ug_validity($id)
374        : LATMOS::Accounts::Utils::check_oid_validity($id)) {
375        $self->log(LA_ERR, "Cannot rename $otype/$id to ID $newid `%s:'", $chk);
376        return;
377    }
378    $self->check_acl($obj, '@DELETE', 'w') &&
379    $self->check_acl($obj, '@CREATE', 'w') or do {
380        $self->log(LA_WARN, 'permission denied to rename %s/%s',
381            $otype, $id);
382        return;
383    };
384
385    $self->_rename_object($otype, $id, $newid);
386}
387
388sub _rename_object {
389    my ($self, $otype, $id, $newid) = @_;
390    my $pclass = $self->_load_obj_class($otype);
391    $pclass->can('_rename') or do {
392        $self->log(LA_ERR, 'rename object type %s is unsupported', $otype);
393        return;
394    };
395    $pclass->_rename($self, $id, $newid);
396}
397
398=head2 load
399
400Make account base loading data into memory if need.
401Should always be called, if database fetch data on the fly
402(SQL, LDAP), the function just return True.
403
404=cut
405
406sub load { 1 }
407
408=head2 is_transactionnal
409
410Return True is the database support commit and rollback
411
412=cut
413
414sub is_transactionnal {
415    my ($self) = @_;
416    return($self->can('_rollback') && $self->can('_commit'));
417}
418
419=head2 commit
420
421Save change into the database if change are not done immediately.
422This should always be called as you don't know when change are applied.
423
424Return always true if database does not support any transaction.
425
426The driver should provides a _commit functions to save data.
427
428=cut
429
430sub commit {
431    my ($self) = @_;
432    if ($self->can('_commit')) {
433        la_log(LA_DEBUG, 'Commiting data');
434        if (!(my $res = $self->_commit)) {
435            la_log(LA_ERR, "Commit error on %s", $_->label);
436            return $res;
437        }
438    }
439    return 1;
440}
441
442=head2 rollback
443
444If database support transaction, rollback changes. Return false
445if database does not support.
446
447If supported, driver should provides a _rollback functions
448
449=cut
450
451sub rollback {
452    my ($self) = @_;
453    if ($self->can('_rollback')) {
454       la_log(LA_DEBUG, 'Rolling back data');
455       return $self->_rollback;
456   } else {
457       return 0;
458   }
459}
460
461=head2 current_rev
462
463Return the current revision of the database
464
465Must be provide by base driver if incremental synchro is supported
466
467=cut
468
469sub current_rev { return }
470
471=head2 list_objects_from_rev($otype, $rev)
472
473Return the list of UID for object of $otype.
474
475=cut
476
477sub list_objects_from_rev {
478    my ($self, $otype, $rev) = @_;
479    my $pclass = $self->_load_obj_class($otype) or return;
480    if (defined($rev) && $pclass->can('list_from_rev')) {
481        return $pclass->list_from_rev($self, $rev);
482    } else {
483        # no support, return all objects...
484        return $self->list_objects($otype);
485    }
486}
487
488sub sync_object_from {
489    my ($self, $srcbase, $otype, $id, %options) = @_;
490
491    # is the object type supported by both
492    foreach ($self, $srcbase) {
493        $_->is_supported_object($otype) or return '';
494    }
495   
496    if (my $srcobj = $srcbase->get_object($otype, $id)) {
497        return $self->sync_object($srcobj, %options);
498    } elsif (!$options{nodelete}) {
499        $self->_delete_object($otype, $id) and return 'DELETED';
500    }
501    return;
502}
503
504=head2 sync_object
505
506Synchronise an object into this base
507
508=cut
509
510sub sync_object {
511    my ($self, $srcobj, %options) = @_;
512    $self->is_supported_object($srcobj->type) or return '';
513    my @fields = $options{attrs}
514        ? @{ $options{attrs} }
515        : $self->list_canonical_fields($srcobj->type, 'w');
516    my %data;
517    my %delayed = map { $_ => 1 } $self->delayed_fields($srcobj->type);
518    foreach (@fields) {
519        $srcobj->get_field_name($_, 'r') or next;
520        if (! $options{onepass}) {
521            if ($options{firstpass}) {
522                $delayed{$_} and next;
523            } else {
524                $delayed{$_} or next;
525            }
526        }
527        $data{$_} = $srcobj->_get_c_field($_);
528    }
529    keys %data or return '';
530    if (my $dstobj = $self->get_object($srcobj->type, $srcobj->id)) {
531        my $res = $dstobj->_set_c_fields(%data);
532        if (defined $res) {
533            return $res ? 'SYNCED' : '';
534        } else {
535            return;
536        }
537    } elsif(!$options{nocreate}) {
538        if ((! $options{firstpass}) && (!$options{onepass})) {
539            $self->log(LA_ERR, 'This is not first pass, creation wanted but denied');
540            return;
541        }
542        if ($self->_create_c_object($srcobj->type, $srcobj->id, %data)) {
543            return 'CREATED'
544        } else {
545            return;
546        }
547    } else {
548        # No error, but creation is denied
549        return 'Creation skipped';
550    }
551
552    return;
553}
554
555=head2 search_objects($otype, %filter)
556
557Search object according %filter. %filter is a list
558of field/value which should match.
559
560A default function is provided but each db driver can provide
561an optimize version.
562
563=cut
564
565sub search_objects {
566    my ($self, $otype, @filter) = @_;
567    my $pclass = $self->_load_obj_class($otype) or return;
568    $pclass->search($self, @filter);
569}
570
571sub attributes_summary {
572    my ($self, $otype, $attr) = @_;
573    my $pclass = $self->_load_obj_class($otype) or return;
574    $pclass->attributes_summary($self, $attr);
575}
576
577sub find_next_numeric_id {
578    my ($self, $otype, $field, $min, $max) = @_;
579    my $pclass = $self->_load_obj_class($otype) or return;
580    $pclass->find_next_numeric_id($self, $field, $min, $max);
581}
582
583sub authenticate_user {
584    my ($self, $username, $passwd) = @_;
585    $username or return;
586    my $uobj = $self->get_object('user', $username) or do {
587        la_log(LA_ERR, "Cannot authenticate non existing user $username");
588        return;
589    };
590
591    if ($self->get_field_name('user', 'exported', 'r')) {
592        if (!$uobj->_get_c_field('exported')) {
593            la_log(LA_ERR, "User $username found but currently unexported");
594            return;
595        }
596    }
597
598    if (my $expire = $uobj->_get_c_field('shadowExpire')) {
599        if ($expire > 0 && $expire < int(time / ( 3600 * 24 ))) {
600            la_log(LA_ERR, "Account $username has expired (%d / %d)",
601                $expire, int(time / ( 3600 * 24 )));
602            return;
603        }
604    }
605
606    if ($uobj->_get_c_field('locked')) {
607        la_log(LA_ERR, "Account $username is currently locked");
608        return;
609    }
610
611    my $password = $uobj->_get_c_field('userPassword') or do {
612        la_log(LA_ERR, "Cannot authenticate user $username having no passwd");
613        return;
614    };
615    if ($password eq crypt($passwd, $password)) { # crypt unix
616        la_log(LA_NOTICE, "User $username authenticated");
617        return 1;
618    } else {
619        la_log(LA_ERR, "Cannot authenticate user $username");
620        return 0;
621    }
622}
623
624sub connect {
625    my ($self, $username, $password) = @_;
626    my $auth = $self->authenticate_user($username, $password);
627    if ($auth) {
628        $self->{_user} = $username;
629        la_log(LA_DEBUG, "Connected as $username");
630    }
631    return $auth;
632}
633
634sub check_acl {
635    my ($self, $obj, $attr, $perm) = @_;
636    if ($self->{_acls}) {
637        my ($who, $groups) = ($self->{_user} || '');
638        if ($who && (my $uo = $self->get_object('user', $who))) {
639            $groups = [ $uo->_get_attributes('memberOf') ];
640        } else {
641            $who = '';
642        }
643        my $res = $self->{_acls}->check($obj, $attr, $perm, $who, $groups);
644        $self->log(LA_INFO, 'permission denied for "%s" to get %s.%s for %s',
645           $who, ref $obj ? $obj->id . '(' . $obj->type . ')' : $obj, $attr, $perm) if (!$res);
646        return $res;
647    } else {
648        # No acls, woot
649        return 1;
650    }
651}
652
653sub text_empty_dump {
654    my ($self, $fh, $otype, $options) = @_;
655    my $pclass = $self->_load_obj_class($otype) or return;
656    $pclass->text_dump($fh, $options, $self);
657}
658
6591;
660
661__END__
662
663=head1 SEE ALSO
664
665=head1 AUTHOR
666
667Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.fr<gt>
668
669=head1 COPYRIGHT AND LICENSE
670
671Copyright (C) 2009 by Thauvin Olivier
672
673This library is free software; you can redistribute it and/or modify
674it under the same terms as Perl itself, either Perl version 5.10.0 or,
675at your option, any later version of Perl 5 you may have available.
676
677=cut
Note: See TracBrowser for help on using the repository browser.