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

Last change on this file since 474 was 474, checked in by nanardon, 15 years ago
  • fix acls parsing
  • Property svn:keywords set to Id Rev
File size: 12.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
229    # populating default value
230    foreach my $def (keys %{ $self->{defattr} || {}}) {
231        if ($def =~ /^$otype\.(.*)$/) {
232            $cdata{$1} = $self->{defattr}{$def} if(!$cdata{$1});
233        }
234    }
235    if ($otype eq 'user') {
236        $cdata{homeDirectory} ||= $self->{defattr}{'user.homebase'} ?
237            $self->{defattr}{'user.homebase'} . "/$id" : '';
238        $cdata{uidNumber} ||= $self->find_next_numeric_id('user', 'uidNumber',
239            $self->{defattr}{'user.min_uid'}, $self->{defattr}{'user.max_uid'});
240    } elsif ($otype eq 'group') {
241        $cdata{gidNumber} ||= $self->find_next_numeric_id('group', 'gidNumber',
242            $self->{defattr}{'group.min_gid'}, $self->{defattr}{'group.max_gid'});
243    }
244    my %data;
245    foreach my $cfield (keys %cdata) {
246        my $field = $self->get_field_name($otype, $cfield, 'write') or next;
247        $data{$field} = $cdata{$cfield};
248    }
249    keys %data or return 0; # TODO: return an error ?
250    $self->create_object($otype, $id, %data);
251}
252
253=head2 delete_object($otype, $id)
254
255Destroy from data base object type $otype having id $id.
256
257=cut
258
259sub delete_object {
260    my ($self, $otype, $id) = @_;
261    my $pclass = $self->_load_obj_class($otype);
262    $pclass->_delete($self, $id);
263}
264
265=head2 load
266
267Make account base loading data into memory if need.
268Should always be called, if database fetch data on the fly
269(SQL, LDAP), the function just return True.
270
271=cut
272
273sub load { 1 }
274
275=head2 is_transactionnal
276
277Return True is the database support commit and rollback
278
279=cut
280
281sub is_transactionnal {
282    my ($self) = @_;
283    return($self->can('_rollback') && $self->can('_commit'));
284}
285
286=head2 commit
287
288Save change into the database if change are not done immediately.
289This should always be called as you don't know when change are applied.
290
291Return always true if database does not support any transaction.
292
293The driver should provides a _commit functions to save data.
294
295=cut
296
297sub commit {
298    my ($self) = @_;
299    if ($self->can('_commit')) {
300        la_log(LA_DEBUG, 'Commiting data');
301        if (!(my $res = $self->_commit)) {
302            la_log(LA_ERR, "Commit error on %s", $_->label);
303            return $res;
304        }
305    }
306    if ($self->{_options}{postcommit}) {
307        la_log(LA_DEBUG, "Running post commit `%s'", $self->{_options}{postcommit});
308        return exec_command($self->{_options}{postcommit}, $self->{_options});
309    } else {
310        la_log(LA_DEBUG, "No postcommit setting, ignoring this");
311    }
312    return 1;
313}
314
315=head2 rollback
316
317If database support transaction, rollback changes. Return false
318if database does not support.
319
320If supported, driver should provides a _rollback functions
321
322=cut
323
324sub rollback {
325    my ($self) = @_;
326    if ($self->can('_rollback')) {
327       la_log(LA_DEBUG, 'Rolling back data');
328       return $self->_rollback;
329   } else {
330       return 0;
331   }
332}
333
334=head2 current_rev
335
336Return the current revision of the database
337
338Must be provide by base driver if incremental synchro is supported
339
340=cut
341
342sub current_rev { return }
343
344=head2 list_objects_from_rev($otype, $rev)
345
346Return the list of UID for object of $otype.
347
348=cut
349
350sub list_objects_from_rev {
351    my ($self, $otype, $rev) = @_;
352    my $pclass = $self->_load_obj_class($otype) or return;
353    if (defined($rev) && $pclass->can('list_from_rev')) {
354        return $pclass->list_from_rev($self, $rev);
355    } else {
356        # no support, return all objects...
357        return $self->list_objects($otype);
358    }
359}
360
361=head2 sync_object
362
363Synchronise an object into this base
364
365=cut
366
367sub sync_object {
368    my ($self, $srcobj, %options) = @_;
369    $self->is_supported_object($srcobj->type) or return '';
370    my @fields = $options{attrs}
371        ? @{ $options{attrs} }
372        : $self->list_canonical_fields($srcobj->type, 'w');
373    my %data;
374    foreach (@fields) {
375        $srcobj->get_field_name($_, 'r') or next;
376        $data{$_} = $srcobj->_get_c_field($_);
377    }
378    if (my $dstobj = $self->get_object($srcobj->type, $srcobj->id)) {
379        my $res = $dstobj->_set_c_fields(%data);
380        if (defined $res) {
381            return $res ? 'SYNCHED' : '';
382        } else {
383            return;
384        }
385    } elsif(!$options{nocreate}) {
386        if ($self->create_c_object($srcobj->type, $srcobj->id, %data)) {
387            return 'CREATE'
388        } else {
389            return;
390        }
391    } else {
392        # No error, but creation is denied
393        return 'Creation skipped';
394    }
395
396    return;
397}
398
399=head2 search_objects($otype, %filter)
400
401Search object according %filter. %filter is a list
402of field/value which should match.
403
404A default function is provided but each db driver can provide
405an optimize version.
406
407=cut
408
409sub search_objects {
410    my ($self, $otype, @filter) = @_;
411    my $pclass = $self->_load_obj_class($otype) or return;
412    $pclass->search($self, @filter);
413}
414
415sub attributes_summary {
416    my ($self, $otype, $attr) = @_;
417    my $pclass = $self->_load_obj_class($otype) or return;
418    $pclass->attributes_summary($self, $attr);
419}
420
421sub find_next_numeric_id {
422    my ($self, $otype, $field, $min, $max) = @_;
423    my $pclass = $self->_load_obj_class($otype) or return;
424    $pclass->find_next_numeric_id($self, $field, $min, $max);
425}
426
427sub authenticate_user {
428    my ($self, $username, $passwd) = @_;
429    $username or return;
430    my $uobj = $self->get_object('user', $username) or do {
431        la_log(LA_ERR, "Cannot authenticate non existing user $username");
432        return;
433    };
434    my $password = $uobj->_get_c_field('userPassword') or do {
435        la_log(LA_ERR, "Cannot authenticate user $username having no passwd");
436        return;
437    };
438    if ($password eq crypt($passwd, $password)) { # crypt unix
439        return 1;
440    } else {
441        la_log(LA_ERR, "Cannot authenticate user $username");
442        return 0;
443    }
444}
445
446sub connect {
447    my ($self, $username, $password) = @_;
448    my $auth = $self->authenticate_user($username, $password);
449    if ($auth) {
450        $self->{_user} = $username;
451        la_log(LA_DEBUG, "Connect as $username");
452    }
453    return $auth;
454}
455
456sub check_acl {
457    my ($self, $obj, $attr, $perm) = @_;
458    if ($self->{_acls}) {
459        my ($who, $groups) = ($self->{_user} || '');
460        if ($who && (my $uo = $self->get_object('user', $who))) {
461            $groups = [ $uo->_get_attributes('memberOf') ];
462        } else {
463            $who = '';
464        }
465        my $res = $self->{_acls}->check($obj, $attr, $perm, $who, $groups);
466        $self->log(LA_NOTICE, 'permission denied for "%s" to get %s.%s for %s',
467           $who, ref $obj ? $obj->id . '(' . $obj->type . ')' : $obj, $attr, $perm) if (!$res);
468        return $res;
469    } else {
470        # No acls, woot
471        return 1;
472    }
473}
474
475sub text_empty_dump {
476    my ($self, $fh, $otype, $options) = @_;
477    my $pclass = $self->_load_obj_class($otype) or return;
478    $pclass->text_dump($fh, $options, $self);
479}
480
4811;
482
483__END__
484
485=head1 SEE ALSO
486
487=head1 AUTHOR
488
489Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.fr<gt>
490
491=head1 COPYRIGHT AND LICENSE
492
493Copyright (C) 2009 by Thauvin Olivier
494
495This library is free software; you can redistribute it and/or modify
496it under the same terms as Perl itself, either Perl version 5.10.0 or,
497at your option, any later version of Perl 5 you may have available.
498
499=cut
Note: See TracBrowser for help on using the repository browser.