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

Last change on this file since 262 was 262, checked in by nanardon, 15 years ago
  • allow to run command after commit success (postcommit=...)
  • Property svn:keywords set to Id Rev
File size: 10.3 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->{defattr} = $options{defattr};
48    la_log(LA_DEBUG, 'Instanciate base %s (%s)', ($options{label} || 'N/A'), $pclass);
49    $base
50}
51
52sub label {
53    $_[0]->{_label};
54}
55
56sub type {
57    $_[0]->{_type};
58}
59
60sub _load_obj_class {
61    my ($self, $otype) = @_;
62
63    # finding perl class:
64    my $pclass = ref $self;
65    $pclass .= '::' . ucfirst(lc($otype));
66    eval "require $pclass;";
67    if ($@) {
68        la_log(LA_DEBUG, 'Cannot load perl class %s', $pclass);
69        return
70    } # error message ?
71    return $pclass;
72}
73
74=head2 list_canonical_fields($otype, $for)
75
76Return the list of supported fields by the database for object type $otype.
77
78Optionnal $for specify the goal for which the list is requested, only supported
79fields will be returns
80
81=cut
82
83sub list_canonical_fields {
84    my ($self, $otype, $for) = @_;
85    $for ||= 'rw';
86    my $pclass = $self->_load_obj_class($otype) or return;
87    sort $pclass->_canonical_fields($self, $for);
88}
89
90sub delayed_fields {
91    my ($self, $otype, $for) = @_;
92    $for ||= 'rw';
93    my $pclass = $self->_load_obj_class($otype) or return;
94    $pclass->_delayed_fields($self, $for);
95}
96
97=head2 get_field_name($otype, $c_fields, $for)
98
99Return the internal fields name for $otype object for
100canonical fields $c_fields
101
102=cut
103
104sub get_field_name {
105    my ($self, $otype, $c_fields, $for) = @_;
106    $for ||= 'rw';
107    my $pclass = $self->_load_obj_class($otype) or return;
108    $pclass->_get_field_name($c_fields, $self, $for);
109}
110
111=head2 list_supported_objects(@otype)
112
113Return a list of supported object
114
115@type is an additionnal list of objects to check
116
117=cut
118
119sub list_supported_objects {
120    my ($self, @otype) = @_;
121    my %res;
122    foreach my $inc (@INC) {
123        my $sub = 'LATMOS::Accounts::Bases::' . ucfirst($self->type);
124        $sub =~ s/::/\//g;
125        foreach (glob("$inc/$sub/[A-Z]*.pm")) {
126            s/.*\///;
127            s/\.pm$//;
128            $res{lc($_)} = 1;
129        }
130    }
131    $res{$_} = 1 foreach(@otype);
132    my @sobj = grep { $self->is_supported_object($_) } keys %res;
133    la_log(LA_DEBUG, "Base %s supported objects: %s", $self->type, join(', ', @sobj));
134    return @sobj;
135}
136
137=head2 is_supported_object($otype)
138
139Return true is object type $otype is supported
140
141=cut
142
143sub is_supported_object {
144    my ($self, $otype) = @_;
145    return $self->_load_obj_class($otype) ? 1 : 0;
146}
147
148=head2 list_objects($otype)
149
150Return the list of UID for object of $otype.
151
152=cut
153
154sub list_objects {
155    my ($self, $otype) = @_;
156    my $pclass = $self->_load_obj_class($otype) or return;
157    $pclass->list($self);
158}
159
160=head2 get_object($type, $id)
161
162Return an object of $type (typically user or group) having identifier
163$id.
164
165=cut
166
167sub get_object {
168    my ($self, $otype, $id) = @_;
169
170    return LATMOS::Accounts::Bases::Objects->_new($self, $otype, $id);
171}
172
173=head2 create_object($type, $id, %data)
174
175Create and return an object of type $type with unique id
176$id having %data.
177
178This method should be provided by the data base handler.
179
180=cut
181
182sub create_object {
183    my ($self, $otype, $id, %data) = @_;
184    my $pclass = $self->_load_obj_class($otype);
185    if ($pclass->_create($self, $id, %data)) {
186        la_log(LA_INFO,
187            'Object %s (%s) created in base %s (%s)',
188            $id, $otype, $self->label, $self->type
189        );
190    } else {
191        la_log(LA_ERR,
192            'Object creation %s (%s) in base %s (%s) failed',
193            $id, $otype, $self->label, $self->type
194        );
195        return;
196    };
197    $self->get_object($otype, $id);
198}
199
200=head2 create_c_object($type, $id, %data)
201
202Create and return an object of type $type with unique id
203$id having %data using canonical fields
204
205=cut
206
207sub create_c_object {
208    my ($self, $otype, $id, %cdata) = @_;
209
210    # populating default value
211    foreach my $def (%{ $self->{defattr} || {}}) {
212        if ($def =~ /^$otype\.(.*)$/) {
213            $cdata{$1} = $self->{defattr}{$def} if(!$cdata{$1});
214        }
215    }
216    if ($otype eq 'user') {
217        $cdata{homeDirectory} ||= $self->{defattr}{'user.homebase'} ?
218            $self->{defattr}{'user.homebase'} . "/$id" : '';
219        $cdata{uidNumber} ||= $self->find_next_numeric_id('user', 'uidNumber',
220            $self->{defattr}{'user.min_uid'}, $self->{defattr}{'user.max_uid'});
221    } elsif ($otype eq 'group') {
222        $cdata{gidNumber} ||= $self->find_next_numeric_id('group', 'gidNumber',
223            $self->{defattr}{'group.min_gid'}, $self->{defattr}{'group.max_gid'});
224    }
225    my %data;
226    foreach my $cfield (keys %cdata) {
227        my $field = $self->get_field_name($otype, $cfield, 'write') or next;
228        $data{$field} = $cdata{$cfield};
229    }
230    keys %data or return 0; # TODO: return an error ?
231    $self->create_object($otype, $id, %data);
232}
233
234=head2 delete_object($otype, $id)
235
236Destroy from data base object type $otype having id $id.
237
238=cut
239
240sub delete_object {
241    my ($self, $otype, $id) = @_;
242    my $pclass = $self->_load_obj_class($otype);
243    $pclass->_delete($self, $id) or return;
244}
245
246=head2 load
247
248Make account base loading data into memory if need.
249Should always be called, if database fetch data on the fly
250(SQL, LDAP), the function just return True.
251
252=cut
253
254sub load { 1 }
255
256=head2 is_transactionnal
257
258Return True is the database support commit and rollback
259
260=cut
261
262sub is_transactionnal {
263    my ($self) = @_;
264    return($self->can('_rollback') && $self->can('_commit'));
265}
266
267=head2 commit
268
269Save change into the database if change are not done immediately.
270This should always be called as you don't know when change are applied.
271
272Return always true if database does not support any transaction.
273
274The driver should provides a _commit functions to save data.
275
276=cut
277
278sub commit {
279    my ($self) = @_;
280    if ($self->can('_commit')) {
281        la_log(LA_DEBUG, 'Commiting data');
282        if (!(my $res = $self->_commit)) {
283            return $res;
284        }
285        if ($self->{options}{postcommit}) {
286            return exec_command($self->{options}{postcommit}, $self->{options});
287        }
288        return 1;
289    } else {
290        return 1;
291    }
292}
293
294=head2 rollback
295
296If database support transaction, rollback changes. Return false
297if database does not support.
298
299If supported, driver should provides a _rollback functions
300
301=cut
302
303sub rollback {
304    my ($self) = @_;
305    if ($self->can('_rollback')) {
306       la_log(LA_DEBUG, 'Rolling back data');
307       return $self->_rollback;
308   } else {
309       return 0;
310   }
311}
312
313=head2 current_rev
314
315Return the current revision of the database
316
317Must be provide by base driver if incremental synchro is supported
318
319=cut
320
321sub current_rev { return }
322
323=head2 list_objects_from_rev($otype, $rev)
324
325Return the list of UID for object of $otype.
326
327=cut
328
329sub list_objects_from_rev {
330    my ($self, $otype, $rev) = @_;
331    my $pclass = $self->_load_obj_class($otype) or return;
332    if (defined($rev) && $pclass->can('list_from_rev')) {
333        return $pclass->list_from_rev($self, $rev);
334    } else {
335        # no support, return all objects...
336        return $self->list_objects($otype);
337    }
338}
339
340=head2 sync_object
341
342Synchronise an object into this base
343
344=cut
345
346sub sync_object {
347    my ($self, $srcobj, %options) = @_;
348    $self->is_supported_object($srcobj->type) or return '';
349    my @fields = $options{attrs}
350        ? @{ $options{attrs} }
351        : $self->list_canonical_fields($srcobj->type, 'w');
352    my %data;
353    foreach (@fields) {
354        $srcobj->get_field_name($_, 'r') or next;
355        $data{$_} = $srcobj->get_c_field($_);
356    }
357    if (my $dstobj = $self->get_object($srcobj->type, $srcobj->id)) {
358        return 'SYNCHED' if ($dstobj->set_c_fields(%data));
359    } elsif(!$options{nocreate}) {
360        return 'CREATE' if ($self->create_c_object($srcobj->type, $srcobj->id, %data));
361    } else {
362        # No error, but creation is denied
363        return 'Creation skipped';
364    }
365
366    return;
367}
368
369=head2 search_objects($otype, %filter)
370
371Search object according %filter. %filter is a list
372of field/value which should match.
373
374A default function is provided but each db driver can provide
375an optimize version.
376
377=cut
378
379sub search_objects {
380    my ($self, $otype, %filter) = @_;
381    my $pclass = $self->_load_obj_class($otype) or return;
382    $pclass->search($self, %filter);
383}
384
385sub attributes_summary {
386    my ($self, $otype, $attr) = @_;
387    my $pclass = $self->_load_obj_class($otype) or return;
388    $pclass->attributes_summary($self, $attr);
389}
390
391sub find_next_numeric_id {
392    my ($self, $otype, $field, $min, $max) = @_;
393    my $pclass = $self->_load_obj_class($otype) or return;
394    $pclass->find_next_numeric_id($self, $field, $min, $max);
395}
396
397sub authenticate_user {
398    my ($self, $username, $passwd) = @_;
399    $username or return;
400    my $uobj = $self->get_object('user', $username) or do {
401        la_log(LA_ERR, "Cannot authenticate non existing user $username");
402        return;
403    };
404    my $password = $uobj->get_c_field('userPassword') or do {
405        la_log(LA_ERR, "Cannot authenticate user $username having no passwd");
406        return;
407    };
408    if ($password eq crypt($passwd, $password)) { # crypt unix
409        return 1;
410    } else {
411        la_log(LA_ERR, "Cannot authenticate user $username");
412        return 0;
413    }
414}
415
4161;
417
418__END__
419
420=head1 SEE ALSO
421
422=head1 AUTHOR
423
424Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.fr<gt>
425
426=head1 COPYRIGHT AND LICENSE
427
428Copyright (C) 2009 by Thauvin Olivier
429
430This library is free software; you can redistribute it and/or modify
431it under the same terms as Perl itself, either Perl version 5.10.0 or,
432at your option, any later version of Perl 5 you may have available.
433
434=cut
Note: See TracBrowser for help on using the repository browser.