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

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