source: trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql.pm @ 1402

Last change on this file since 1402 was 1402, checked in by nanardon, 9 years ago

Missing doc

  • Property svn:keywords set to Id Rev
File size: 16.9 KB
Line 
1package LATMOS::Accounts::Bases::Sql;
2
3use 5.010000;
4use strict;
5use warnings;
6
7use base qw(LATMOS::Accounts::Bases);
8use LATMOS::Accounts::Log;
9use LATMOS::Accounts::Bases::Sql::DataRequest;
10use DBI;
11use Crypt::RSA;
12use Crypt::RSA::Key::Public::SSH;
13use Crypt::RSA::Key::Private::SSH;
14use Crypt::RSA::Key::Public;
15use Crypt::RSA::Key::Private;
16use MIME::Base64;
17
18our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
19
20sub SCHEMA_VERSION { 11 };
21
22=head1 NAME
23
24LATMOS::Ad - Perl extension for blah blah blah
25
26=head1 SYNOPSIS
27
28  use LATMOS::Accounts::Bases;
29  my $base = LATMOS::Accounts::Bases->new('unix');
30  ...
31
32=head1 DESCRIPTION
33
34Account base access over standard unix file format.
35
36=head1 FUNCTIONS
37
38=cut
39
40=head2 SCHEMA_VERSION
41
42Return the SQL schema version to use for this software version.
43
44=head2 new(%config)
45
46Create a new LATMOS::Ad object for windows AD $domain.
47
48domain / server: either the Ad domain or directly the server
49
50ldap_args is an optionnal list of arguments to pass to L<Net::LDAP>.
51
52=cut
53
54sub new {
55    my ($class, %config) = @_;
56   
57    my $base = {
58        db_conn => $config{db_conn},
59    };
60
61    bless($base, $class);
62}
63
64sub DESTROY {
65    my ($self) = @_;
66    $self->{_db} && $self->{_db}->rollback;
67}
68
69=head2 db
70
71Return a L<DBI> handle over database, load it if need.
72
73=cut
74
75sub db {
76    my ($self) = @_;
77
78    if ($self->{_db} && $self->{_db}->ping) {
79        return $self->{_db};
80    } else {
81        $self->{_db} = DBI->connect_cached(
82            'dbi:Pg:' . $self->{db_conn},
83            undef, undef,
84            {
85                RaiseError => 0,
86                AutoCommit => 0,
87                PrintWarn => 1,
88                PrintError => 1,
89            }
90        ) or do {
91            $self->log(LA_ERR, "Cannot connect to database: %s", $DBI::errstr);   
92            return;
93        };
94        $self->{_db}->do(q(SET SESSION CHARACTERISTICS AS TRANSACTION
95                    ISOLATION LEVEL SERIALIZABLE));
96        $self->{_db}->do(q{set DATESTYLE to 'DMY'});
97        $self->log(LA_DEBUG, 'New connection to DB');
98        $self->{_db}->commit;
99        return $self->{_db};
100    }
101}
102
103=head2 load
104
105Read file and load data into memory
106
107=cut
108
109sub load {
110    my ($self) = @_;
111    if (!$self->db) { return 0 };
112
113    my $sv = $self->get_global_value('schema_version') || 1;
114    if ($sv < SCHEMA_VERSION) {
115        $self->log(LA_CRIT,
116            "Schema version %d found, %d is need, please update db using " .
117            "`la-sql-upgrade' tool for `%s' base",
118            $sv,
119            SCHEMA_VERSION,
120            $self->label,
121        );
122        # return;
123    }
124
125    1;
126}
127
128sub _sync_dyn_group {
129    my ($self) = @_;
130
131    my @groups = $self->search_objects('group', 'autoMemberFilter=*');
132
133    foreach (@groups) {
134        my $g = $self->get_object('group', $_) or next;
135        $g->populate_dyn_group;
136    }
137}
138
139sub _commit {
140    my ($self) = @_;
141
142    $self->_sync_dyn_group;
143
144    if ($ENV{LA_NO_COMMIT}) {
145        $self->log(LA_DEBUG, 'DB::COMMIT (ignore due to LA_NO_COMMIT)');
146        return 1;
147    } else {
148        $self->log(LA_DEBUG, 'DB::COMMIT');
149    }
150    $self->{__cache} = undef;
151    $self->db->commit;
152}
153
154sub _rollback {
155    my ($self) = @_;
156    if ($ENV{LA_NO_COMMIT}) {
157        $self->log(LA_DEBUG, 'DB::ROLLBACK (ignore due to LA_NO_COMMIT)');
158        return 1
159    } else {
160        $self->log(LA_DEBUG, 'DB::ROLLBACK');
161    }
162    $self->{__cache} = undef;
163    $self->db->rollback;
164}
165
166sub list_supported_objects {
167    my ($self, @otype) = @_;
168    $self->SUPER::list_supported_objects(qw(site), @otype);
169}
170
171sub current_rev {
172    my ($self) = @_;
173    my $sth = $self->db->prepare_cached(
174        q{select max(rev) from revisions}
175    );
176    $sth->execute;
177    my $res = $sth->fetchrow_hashref;
178    $sth->finish;
179    return ($res->{max});
180} 
181
182sub authenticate_user {
183    my ($self, $username, $passwd) = @_;
184    $username or return;
185    my $uobj = $self->get_object('user', $username) or do {
186        la_log(LA_ERR, "Cannot authenticate non existing user $username");
187        return;
188    };
189
190    if ($self->attribute('user', 'exported')) {
191        if (!$uobj->_get_c_field('exported')) {
192            la_log(LA_ERR, "User $username found but currently unexported");
193            return;
194        }
195    }
196
197    $self->SUPER::authenticate_user($username, $passwd);
198}
199
200=head1 SPECIFICS FUNCTIONS
201
202=head2 get_global_value ($varname)
203
204Return global value set into base
205
206=cut
207
208sub get_global_value {
209    my ($self, $varname) = @_;
210
211    my $sth = $self->db->prepare_cached(q{
212        select val from settings where varname = ?
213        });
214    $sth->execute($varname);
215    my $res = $sth->fetchrow_hashref;
216    $sth->finish;
217    $res->{val}
218}
219
220=head2 set_global_value ($varname, $value)
221
222Set global value.
223
224=cut
225
226sub set_global_value {
227    my ($self, $varname, $value) = @_;
228    my $sth = $self->db->prepare(q{
229        update settings set val = ? where varname = ?
230        });
231    $sth->execute($value, $varname) == 0 and do {
232        my $sth2 = $self->db->prepare(q{
233            insert into settings (val, varname) values (?,?)
234            });
235        $sth2->execute($value, $varname);
236    };
237}
238
239=head2 generate_rsa_key ($password)
240
241Return public and private peer rsa keys
242
243=cut
244
245sub generate_rsa_key {
246    my ($self, $password) = @_;
247
248    my $rsa = new Crypt::RSA ES => 'PKCS1v15';
249    my ($public, $private) = $rsa->keygen (
250        Identity  => 'LATMOS-Accounts',
251        Size      => 2048,
252        Password  => $password,
253        Verbosity => 0,
254    ) or die $rsa->errstr(); # TODO avoid die
255    return ($public, $private);
256}
257
258=head2 private_key ($password)
259
260Load and return private rsa key
261
262=cut
263
264sub private_key {
265    my ($self, $password) = @_;
266    my $base = $self;
267    my $serialize = $base->get_global_value('rsa_private_key') or return;
268    my $string = decode_base64($serialize);
269    my $privkey = $string =~ /^SSH PRIVATE KEY FILE/
270        ? Crypt::RSA::Key::Private::SSH->new
271        : Crypt::RSA::Key::Private->new;
272    $privkey = $privkey->deserialize(
273        String => [ $string ],
274        Password => $password
275    );
276    $privkey->reveal( Password => $password );
277    $privkey;
278}
279
280=head2 get_rsa_password
281
282Return hash with peer username => encryptedPassword
283
284=cut
285
286sub get_rsa_password {
287    my ($self) = @_;
288    my $base = $self;
289    my $sth = $base->db->prepare(q{
290        select "name", value from "user" join user_attributes_base
291        on "user".ikey = user_attributes_base.okey
292        where user_attributes_base.attr = 'encryptedPassword'
293    });
294    $sth->execute;
295    my %users;
296    while (my $res = $sth->fetchrow_hashref) {
297        $users{$res->{name}} = $res->{value};
298    }
299    %users
300}
301
302=head2 store_rsa_key ($public, $private)
303
304Store public and private RSA key info data base
305
306=cut
307
308sub store_rsa_key {
309    my ($self, $public, $private) = @_;
310    my $base = $self;
311    $private->hide;
312    $base->set_global_value('rsa_private_key',
313        encode_base64($private->serialize));
314    $base->set_global_value('rsa_public_key',
315        $public->serialize);
316    return;
317}
318
319=head2 find_next_expire_users ($expire)
320
321Search user expiring in C<$expire> delay
322
323=cut
324
325sub find_next_expire_users {
326    my ($self, $expire) = @_;
327
328    my $sth= $self->db->prepare(q{
329        select name from "user" where
330            expire < now() + ?::interval
331            and expire > now()
332            and expire is not null
333            } . ($self->{wexported} ? '' : 'and exported = true') . q{
334            order by expire
335        }
336    );
337    $sth->execute($expire || '1 month');
338    my @users;
339    while (my $res = $sth->fetchrow_hashref) {
340        push(@users, $res->{name});
341    }
342    @users
343}
344
345=head2 find_expired_users ($expire)
346
347Return list of user going to expires in C<$expire> delay
348
349=cut
350
351sub find_expired_users {
352    my ($self, $expire) = @_;
353
354    my $sth= $self->db->prepare(q{
355        select name from "user" where
356            expire < now() - ?::interval
357            and expire is not null
358        } . ($self->{wexported} ? '' : 'and exported = true') . q{
359            order by expire
360        }
361    );
362    $sth->execute($expire || '1 second');
363    my @users;
364    while (my $res = $sth->fetchrow_hashref) {
365        push(@users, $res->{name});
366    }
367    @users
368}
369
370=head2 rename_nethost ($nethostname, $to, %config)
371
372Facility function to rename computer to new name
373
374=cut
375
376sub rename_nethost {
377    my ($self, $nethostname, $to, %config) = @_;
378    {
379        my $obj = $self->get_object('nethost', $nethostname) or do {
380            $self->log(LA_ERR, 'Unable to rename non exisant host %s', $nethostname);
381            return;
382        };
383        $obj->_delAttributeValue(cname => $to);
384    }
385    $self->rename_object('nethost', $nethostname, $to) or return;
386    if ($config{'addcname'}) {
387        my $obj = $self->get_object('nethost', $to);
388        $obj->_addAttributeValue(cname => $nethostname);
389    }
390    return 1;
391}
392
393=head2 nethost_exchange_ip ($ip1, $ip2)
394
395Exchange ip1 with ip2 in base
396
397=cut
398
399sub nethost_exchange_ip {
400    my ($self, $ip1, $ip2) = @_;
401    my ($obj1, $obj2);
402    if (my ($host1) = $self->search_objects('nethost', "ip=$ip1")) {
403        $obj1 = $self->get_object('nethost', $host1);
404    } else {
405        $self->log(LA_ERR, "Cannot find host having $ip1");
406        return;
407    }
408    if (my ($host2) = $self->search_objects('nethost', "ip=$ip2")) {
409        $obj2 = $self->get_object('nethost', $host2);
410    } else {
411        $self->log(LA_ERR, "Cannot find host having $ip2");
412        return;
413    }
414    if ($obj1->id eq $obj2->id) {
415        $self->log(LA_ERR, "Both ip belong to same host (%s)", $obj1->id);
416        return;
417    }
418
419    $self->log(LA_NOTICE, "Exchanging IP between %s and %s", $obj1->id, $obj2->id);
420    $obj1->delAttributeValue('ip', $ip1) or return;
421    $obj2->delAttributeValue('ip', $ip2) or return;
422    $obj1->addAttributeValue('ip', $ip2) or return;
423    $obj2->addAttributeValue('ip', $ip1) or return;
424    return 1;
425}
426
427=head1 ATTRIBUTES FUNCTIONS
428
429=head2 register_attribute ($otype, $attribute, $comment)
430
431Register a new attribute in base
432
433=cut
434
435sub register_attribute {
436    my ($self, $otype, $attribute, $comment) = @_;
437    my $pclass = $self->_load_obj_class($otype) or return;
438    $pclass->register_attribute($self, $attribute, $comment);
439}
440
441=head2 is_registered_attribute ($otype, $attribute)
442
443Return true is attribute already exists
444
445=cut
446
447sub is_registered_attribute {
448    my ($self, $otype, $attribute) = @_;
449    my $pclass = $self->_load_obj_class($otype) or return;
450    $pclass->is_registered_attribute($self, $attribute);
451}
452
453=head2 get_attribute_comment ($otype, $attribute)
454
455Return the comment associated to attribute
456
457=cut
458
459sub get_attribute_comment {
460    my ($self, $otype, $attribute) = @_;
461    my $pclass = $self->_load_obj_class($otype) or return;
462    $pclass->get_attribute_comment($self, $attribute);
463}
464
465=head2 set_attribute_comment ($otype, $attribute, $comment)
466
467Set comment to attribute
468
469=cut
470
471sub set_attribute_comment {
472    my ($self, $otype, $attribute, $comment) = @_;
473    my $pclass = $self->_load_obj_class($otype) or return;
474    $pclass->set_attribute_comment($self, $attribute, $comment);
475}
476
477sub _check_user_manager {
478    $_[0]->_handle_by_unexported('user', 'manager', 'active');
479}
480
481sub _check_group_manager {
482    $_[0]->_handle_by_unexported('group', 'managedBy');
483}
484
485sub _check_nethost_owner {
486    $_[0]->_handle_by_unexported('nethost', 'owner', 'active');
487}
488
489sub _handle_by_unexported {
490    my ($self, $otype, $refattr, $chkattr) = @_;
491
492    my $ptrotype = $self->attribute($otype, $refattr)->reference();
493
494    my %unhandle;
495    foreach my $objname ($self->search_objects($otype, 'active=1', 'exported=1', "$refattr=*")) {
496        my $obj = $self->get_object($otype, $objname) or next;
497        my $val = $obj->get_attributes($refattr) or next;
498        if (my $refobj = $self->get_object($ptrotype, $val)) {
499            if (!$refobj->get_attributes($chkattr || 'exported')) {
500                $unhandle{$objname} = $val;
501            }
502        } else {
503            $unhandle{$objname} = $val;
504        }
505    }
506    %unhandle;
507}
508
509=head2 get_datarequest ($id)
510
511Return user request C<$id>
512
513=cut
514
515sub get_datarequest {
516    my ($self, $id) = @_;
517
518    my $sth = $self->db->prepare(q{
519        select name from request
520        where id = ?
521        });
522    $sth->execute($id);
523    if (my $res = $sth->fetchrow_hashref) {
524        my $accreq = $self->get_object('accreq', $res->{name});
525        return LATMOS::Accounts::Bases::Sql::DataRequest->new($accreq, $id);
526    } else {
527        return;
528    }
529}
530
531=head2 list_requests
532
533list user request currently waiting in base
534
535=cut
536
537sub list_requests {
538    my ($self, $due) = @_;
539
540    my $sth = $self->db->prepare(
541        sprintf(
542            q{
543            select id from request
544            where done is null
545            %s
546            order by apply
547            },
548            defined($due)
549                ? 'and apply ' . ($due ? '<' : '>=') . ' now()'
550                : ''
551        )
552    );
553    $sth->execute;
554    my @ids;
555    while (my $res = $sth->fetchrow_hashref) {
556        push(@ids, $res->{id});
557    }
558
559    @ids
560}
561
562=head2 list_requests_by_submitter ($id)
563
564list user request currently waiting in base ask by user C<$id>
565
566=cut
567
568sub list_requests_by_submitter {
569    my ($self, $id) = @_;
570
571    my $sth = $self->db->prepare(q{
572        select id from request
573        where done is null and "user" = ?
574        order by apply
575    });
576    $sth->execute($id);
577    my @ids;
578    while (my $res = $sth->fetchrow_hashref) {
579        push(@ids, $res->{id});
580    }
581
582    @ids
583}
584
585
586=head2 list_request_by_object ($otype, $id, $req)
587
588Return the list of pending request for a specific object
589
590C<$req> is an optional forms name to limit search
591
592=cut
593
594sub list_request_by_object {
595    my ($self, $otype, $id, $req) = @_;
596
597    my $sth = $self->db->prepare(q{
598        select * from request join
599        accreq on request.name = accreq.name
600        join accreq_attributes on accreq_attributes.okey = accreq.ikey
601        where
602        request.applied is NULL and
603        accreq_attributes.attr = 'oType' and
604        accreq_attributes.value = ?
605        and request.object = ?
606    } .
607    ($req ? ' and request.name = ? ' : '')
608    . q{
609        order by apply
610    });
611    $sth->execute($otype, $id, ($req ? ($req) : ()));
612    my @ids;
613    while (my $res = $sth->fetchrow_hashref) {
614        push(@ids, $res->{id});
615    }
616
617    @ids
618}
619
620=head2 list_pending_requests
621
622List user request to apply
623
624=cut
625
626sub list_pending_requests {
627    my ($self) = @_;
628
629    my $sth = $self->db->prepare(q{
630        select id from request
631        where done is null
632            and apply < now()
633        order by apply
634    });
635    $sth->execute;
636    my @ids;
637    while (my $res = $sth->fetchrow_hashref) {
638        push(@ids, $res->{id});
639    }
640
641    @ids
642}
643
644=head2 list_auto_pending_requests
645
646List automatic request
647
648=cut
649
650sub list_auto_pending_requests {
651    my ($self) = @_;
652
653    my $sth = $self->db->prepare(q{
654        select id from request
655        where done is null
656            and apply < now()
657            and automated = true
658        order by apply
659    });
660    $sth->execute;
661    my @ids;
662    while (my $res = $sth->fetchrow_hashref) {
663        push(@ids, $res->{id});
664    }
665
666    @ids
667}
668
669sub ReportChange {
670    my ($self, $otype, $name, $ref, $changetype, $message, @args) = @_;
671
672    my $sth = $self->db->prepare(q{
673        INSERT into objectslogs (ikey, irev, otype, name, changetype, username, message)
674        VALUES (?,?,?,?,?,?,?)
675        });
676
677    $sth->execute(
678        $ref,
679        $self->current_rev,
680        $otype,
681        $name,
682        $changetype,
683        $self->user || '@Console',
684        sprintf($message, @args),
685    );
686}
687
688=head2 getobjectlogs($otype, $name)
689
690Return logs for object type C<$otype> having C<$name>.
691
692=cut 
693
694sub getobjectlogs {
695    my ($self, $otype, $name) = @_;
696
697    my $sth = $self->db->prepare(q{
698        select ikey from objectslogs where
699            otype = ? and
700            name  = ?
701        group by ikey
702    });
703    $sth->execute($otype, $name);
704    my @ids;
705    while (my $res = $sth->fetchrow_hashref) {
706        push(@ids, $res->{ikey});
707    }
708    @ids or return;
709
710    my $sth2 = $self->db->prepare(sprintf(
711        q{
712            select * from objectslogs where ikey IN (%s)
713            order by logdate asc
714        },
715        join(',', ('?') x scalar(@ids))
716    ));
717
718    $sth2->execute(@ids);
719    my @logs;
720    while (my $res = $sth2->fetchrow_hashref) {
721        push(@logs, $res);
722    }
723
724    return @logs;
725}
726
727=head2 getlogs
728
729Return logs for last year
730
731=cut
732
733sub getlogs {
734    my ($self) = @_;
735    my $sth2 = $self->db->prepare(
736        q{
737            select * from objectslogs
738            where logdate > now() - '1 year'::interval
739            order by logdate asc
740        },
741    );
742
743    $sth2->execute();
744    my @logs;
745    while (my $res = $sth2->fetchrow_hashref) {
746        push(@logs, $res);
747    }
748
749    return @logs;
750}
751
7521;
753
754__END__
755
756=head1 SEE ALSO
757
758=head1 AUTHOR
759
760Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
761
762=head1 COPYRIGHT AND LICENSE
763
764Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
765
766This library is free software; you can redistribute it and/or modify
767it under the same terms as Perl itself, either Perl version 5.10.0 or,
768at your option, any later version of Perl 5 you may have available.
769
770
771=cut
Note: See TracBrowser for help on using the repository browser.