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

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

Store creator and last modifier into objects

  • Property svn:keywords set to Id Rev
File size: 16.2 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
477=head2 get_datarequest ($id)
478
479Return user request C<$id>
480
481=cut
482
483sub get_datarequest {
484    my ($self, $id) = @_;
485
486    my $sth = $self->db->prepare(q{
487        select name from request
488        where id = ?
489        });
490    $sth->execute($id);
491    if (my $res = $sth->fetchrow_hashref) {
492        my $accreq = $self->get_object('accreq', $res->{name});
493        return LATMOS::Accounts::Bases::Sql::DataRequest->new($accreq, $id);
494    } else {
495        return;
496    }
497}
498
499=head2 list_requests
500
501list user request currently waiting in base
502
503=cut
504
505sub list_requests {
506    my ($self, $due) = @_;
507
508    my $sth = $self->db->prepare(
509        sprintf(
510            q{
511            select id from request
512            where done is null
513            %s
514            order by apply
515            },
516            defined($due)
517                ? 'and apply ' . ($due ? '<' : '>=') . ' now()'
518                : ''
519        )
520    );
521    $sth->execute;
522    my @ids;
523    while (my $res = $sth->fetchrow_hashref) {
524        push(@ids, $res->{id});
525    }
526
527    @ids
528}
529
530=head2 list_requests_by_submitter ($id)
531
532list user request currently waiting in base ask by user C<$id>
533
534=cut
535
536sub list_requests_by_submitter {
537    my ($self, $id) = @_;
538
539    my $sth = $self->db->prepare(q{
540        select id from request
541        where done is null and "user" = ?
542        order by apply
543    });
544    $sth->execute($id);
545    my @ids;
546    while (my $res = $sth->fetchrow_hashref) {
547        push(@ids, $res->{id});
548    }
549
550    @ids
551}
552
553
554=head2 list_request_by_object ($otype, $id, $req)
555
556Return the list of pending request for a specific object
557
558C<$req> is an optional forms name to limit search
559
560=cut
561
562sub list_request_by_object {
563    my ($self, $otype, $id, $req) = @_;
564
565    my $sth = $self->db->prepare(q{
566        select * from request join
567        accreq on request.name = accreq.name
568        join accreq_attributes on accreq_attributes.okey = accreq.ikey
569        where
570        request.applied is NULL and
571        accreq_attributes.attr = 'oType' and
572        accreq_attributes.value = ?
573        and request.object = ?
574    } .
575    ($req ? ' and request.name = ? ' : '')
576    . q{
577        order by apply
578    });
579    $sth->execute($otype, $id, ($req ? ($req) : ()));
580    my @ids;
581    while (my $res = $sth->fetchrow_hashref) {
582        push(@ids, $res->{id});
583    }
584
585    @ids
586}
587
588=head2 list_pending_requests
589
590List user request to apply
591
592=cut
593
594sub list_pending_requests {
595    my ($self) = @_;
596
597    my $sth = $self->db->prepare(q{
598        select id from request
599        where done is null
600            and apply < now()
601        order by apply
602    });
603    $sth->execute;
604    my @ids;
605    while (my $res = $sth->fetchrow_hashref) {
606        push(@ids, $res->{id});
607    }
608
609    @ids
610}
611
612=head2 list_auto_pending_requests
613
614List automatic request
615
616=cut
617
618sub list_auto_pending_requests {
619    my ($self) = @_;
620
621    my $sth = $self->db->prepare(q{
622        select id from request
623        where done is null
624            and apply < now()
625            and automated = true
626        order by apply
627    });
628    $sth->execute;
629    my @ids;
630    while (my $res = $sth->fetchrow_hashref) {
631        push(@ids, $res->{id});
632    }
633
634    @ids
635}
636
637sub ReportChange {
638    my ($self, $otype, $name, $ref, $changetype, $message, @args) = @_;
639
640    my $sthmodifiedby = $self->db->prepare(q{
641        UPDATE objects set modifiedby = ? where ikey = ?
642    });
643
644    $sthmodifiedby->execute(
645        $self->user || '@Console',
646        $ref,
647    );
648
649    my $sth = $self->db->prepare(q{
650        INSERT into objectslogs (ikey, irev, otype, name, changetype, username, message)
651        VALUES (?,?,?,?,?,?,?)
652        });
653
654    $sth->execute(
655        $ref,
656        $self->current_rev,
657        $otype,
658        $name,
659        $changetype,
660        $self->user || '@Console',
661        sprintf($message, @args),
662    );
663}
664
665=head2 getobjectlogs($otype, $name)
666
667Return logs for object type C<$otype> having C<$name>.
668
669=cut 
670
671sub getobjectlogs {
672    my ($self, $otype, $name) = @_;
673
674    my $sth = $self->db->prepare(q{
675        select ikey from objectslogs where
676            otype = ? and
677            name  = ?
678        group by ikey
679    });
680    $sth->execute($otype, $name);
681    my @ids;
682    while (my $res = $sth->fetchrow_hashref) {
683        push(@ids, $res->{ikey});
684    }
685    @ids or return;
686
687    my $sth2 = $self->db->prepare(sprintf(
688        q{
689            select * from objectslogs where ikey IN (%s)
690            order by logdate asc
691        },
692        join(',', ('?') x scalar(@ids))
693    ));
694
695    $sth2->execute(@ids);
696    my @logs;
697    while (my $res = $sth2->fetchrow_hashref) {
698        push(@logs, $res);
699    }
700
701    return @logs;
702}
703
704=head2 getlogs
705
706Return logs for last year
707
708=cut
709
710sub getlogs {
711    my ($self) = @_;
712    my $sth2 = $self->db->prepare(
713        q{
714            select * from objectslogs
715            where logdate > now() - '1 year'::interval
716            order by logdate asc
717        },
718    );
719
720    $sth2->execute();
721    my @logs;
722    while (my $res = $sth2->fetchrow_hashref) {
723        push(@logs, $res);
724    }
725
726    return @logs;
727}
728
729
7301;
731
732__END__
733
734=head1 SEE ALSO
735
736=head1 AUTHOR
737
738Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
739
740=head1 COPYRIGHT AND LICENSE
741
742Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
743
744This library is free software; you can redistribute it and/or modify
745it under the same terms as Perl itself, either Perl version 5.10.0 or,
746at your option, any later version of Perl 5 you may have available.
747
748
749=cut
Note: See TracBrowser for help on using the repository browser.