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

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