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

Last change on this file since 1771 was 1771, checked in by nanardon, 8 years ago

Add log search page

  • Property svn:keywords set to Id Rev
File size: 17.4 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
128=head2 getObjectFromOKey ($okey)
129
130Return the object from the db internal key
131
132=cut
133
134sub getObjectFromOKey {
135    my ($self, $okey) = @_;
136
137    my $findobj = $self->{_db}->prepare_cached(q{
138        select * from objects_table where ikey = ?
139    });
140
141    $findobj->execute($okey);
142
143    my $res = $findobj->fetchrow_hashref;
144    $findobj->finish;
145
146    if ($res) {
147        return $self->get_object($res->{relname}, $res->{name});
148    } else {
149        return;
150    }
151}
152
153sub _sync_dyn_group {
154    my ($self) = @_;
155
156    my @groups = $self->search_objects('group', 'autoMemberFilter=*');
157
158    my $res = 0;
159    foreach (@groups) {
160        my $g = $self->get_object('group', $_) or next;
161        $res += $g->populate_dyn_group;
162    }
163
164    $self->log(LA_DEBUG, "Group Dyn res %d", $res);
165    $res
166}
167
168sub _sync_dyn_aliases {
169    my ($self) = @_;
170
171    my @groups = $self->search_objects('aliases', 'autoMemberFilter=*');
172
173    my $res = 0;
174    foreach (@groups) {
175        my $g = $self->get_object('aliases', $_) or next;
176        $res += $g->populate_dyn_aliases;
177    }
178
179    $self->log(LA_DEBUG, "Aliases Dyn res %d", $res);
180    $res
181}
182
183=head2 PopulateDynData
184
185Recomputate dynamics attributes (autoMembersFilters) if need
186
187=cut
188
189sub PopulateDynData {
190    my ($self) = @_;
191
192    foreach (1 .. 5) {
193        $self->log(LA_DEBUG, "%d loop for PopulateDynData", $_);
194        my $res = 0;
195        $res += $self->_sync_dyn_group   || 0;
196        $res += $self->_sync_dyn_aliases || 0;
197
198        if ($res == 0) {
199            last;
200        }
201    }
202
203    return 1;
204}
205
206sub _commit {
207    my ($self) = @_;
208
209    $self->PopulateDynData;
210
211    if ($ENV{LA_NO_COMMIT}) {
212        $self->log(LA_DEBUG, 'DB::COMMIT (ignore due to LA_NO_COMMIT)');
213        return 1;
214    } else {
215        $self->log(LA_DEBUG, 'DB::COMMIT');
216    }
217    $self->{__cache} = undef;
218    $self->db->commit;
219}
220
221sub _rollback {
222    my ($self) = @_;
223    if ($ENV{LA_NO_COMMIT}) {
224        $self->log(LA_DEBUG, 'DB::ROLLBACK (ignore due to LA_NO_COMMIT)');
225        return 1
226    } else {
227        $self->log(LA_DEBUG, 'DB::ROLLBACK');
228    }
229    $self->{__cache} = undef;
230    $self->db->rollback;
231}
232
233sub list_supported_objects {
234    my ($self, @otype) = @_;
235    $self->SUPER::list_supported_objects(qw(site), @otype);
236}
237
238sub current_rev {
239    my ($self) = @_;
240    my $sth = $self->db->prepare_cached(
241        q{select max(rev) from revisions}
242    );
243    $sth->execute;
244    my $res = $sth->fetchrow_hashref;
245    $sth->finish;
246    return ($res->{max});
247} 
248
249sub authenticate_user {
250    my ($self, $username, $passwd) = @_;
251    $username or return;
252    my $uobj = $self->get_object('user', $username) or do {
253        la_log(LA_ERR, "Cannot authenticate non existing user $username");
254        return;
255    };
256
257    if ($self->attribute('user', 'exported')) {
258        if (!$uobj->_get_c_field('exported')) {
259            la_log(LA_ERR, "User $username found but currently unexported");
260            return;
261        }
262    }
263
264    $self->SUPER::authenticate_user($username, $passwd);
265}
266
267=head1 SPECIFICS FUNCTIONS
268
269=head2 get_global_value ($varname)
270
271Return global value set into base
272
273=cut
274
275sub get_global_value {
276    my ($self, $varname) = @_;
277
278    my $sth = $self->db->prepare_cached(q{
279        select val from settings where varname = ?
280        });
281    $sth->execute($varname);
282    my $res = $sth->fetchrow_hashref;
283    $sth->finish;
284    $res->{val}
285}
286
287=head2 set_global_value ($varname, $value)
288
289Set global value.
290
291=cut
292
293sub set_global_value {
294    my ($self, $varname, $value) = @_;
295    my $sth = $self->db->prepare(q{
296        update settings set val = ? where varname = ?
297        });
298    $sth->execute($value, $varname) == 0 and do {
299        my $sth2 = $self->db->prepare(q{
300            insert into settings (val, varname) values (?,?)
301            });
302        $sth2->execute($value, $varname);
303    };
304}
305
306=head2 generate_rsa_key ($password)
307
308Return public and private peer rsa keys
309
310=cut
311
312sub generate_rsa_key {
313    my ($self, $password) = @_;
314
315    my $rsa = new Crypt::RSA ES => 'PKCS1v15';
316    my ($public, $private) = $rsa->keygen (
317        Identity  => 'LATMOS-Accounts',
318        Size      => 2048,
319        Password  => $password,
320        Verbosity => 0,
321    ) or die $rsa->errstr(); # TODO avoid die
322    return ($public, $private);
323}
324
325=head2 private_key ($password)
326
327Load and return private rsa key
328
329=cut
330
331sub private_key {
332    my ($self, $password) = @_;
333    my $base = $self;
334    my $serialize = $base->get_global_value('rsa_private_key') or return;
335    my $string = decode_base64($serialize);
336    my $privkey = $string =~ /^SSH PRIVATE KEY FILE/
337        ? Crypt::RSA::Key::Private::SSH->new
338        : Crypt::RSA::Key::Private->new;
339    $privkey = $privkey->deserialize(
340        String => [ $string ],
341        Password => $password
342    );
343    $privkey->reveal( Password => $password );
344    $privkey;
345}
346
347=head2 get_rsa_password
348
349Return hash with peer username => encryptedPassword
350
351=cut
352
353sub get_rsa_password {
354    my ($self) = @_;
355    my $base = $self;
356    my $sth = $base->db->prepare(q{
357        select "name", value from "user" join user_attributes_base
358        on "user".ikey = user_attributes_base.okey
359        where user_attributes_base.attr = 'encryptedPassword'
360    });
361    $sth->execute;
362    my %users;
363    while (my $res = $sth->fetchrow_hashref) {
364        $users{$res->{name}} = $res->{value};
365    }
366    %users
367}
368
369=head2 store_rsa_key ($public, $private)
370
371Store public and private RSA key info data base
372
373=cut
374
375sub store_rsa_key {
376    my ($self, $public, $private) = @_;
377    my $base = $self;
378    $private->hide;
379    $base->set_global_value('rsa_private_key',
380        encode_base64($private->serialize));
381    $base->set_global_value('rsa_public_key',
382        $public->serialize);
383    return;
384}
385
386=head2 find_next_expire_users ($expire)
387
388Search user expiring in C<$expire> delay
389
390=cut
391
392sub find_next_expire_users {
393    my ($self, $expire) = @_;
394
395    my $sth= $self->db->prepare(q{
396        select name from "user" where
397            expire < now() + ?::interval
398            and expire > now()
399            and expire is not null
400            } . ($self->{wexported} ? '' : 'and exported = true') . q{
401            order by expire
402        }
403    );
404    $sth->execute($expire || '1 month');
405    my @users;
406    while (my $res = $sth->fetchrow_hashref) {
407        push(@users, $res->{name});
408    }
409    @users
410}
411
412=head2 find_expired_users ($expire)
413
414Return list of user going to expires in C<$expire> delay
415
416=cut
417
418sub find_expired_users {
419    my ($self, $expire) = @_;
420
421    my $sth= $self->db->prepare(q{
422        select name from "user" where
423            expire < now() - ?::interval
424            and expire is not null
425        } . ($self->{wexported} ? '' : 'and exported = true') . q{
426            order by expire
427        }
428    );
429    $sth->execute($expire || '1 second');
430    my @users;
431    while (my $res = $sth->fetchrow_hashref) {
432        push(@users, $res->{name});
433    }
434    @users
435}
436
437=head2 rename_nethost ($nethostname, $to, %config)
438
439Facility function to rename computer to new name
440
441=cut
442
443sub rename_nethost {
444    my ($self, $nethostname, $to, %config) = @_;
445    {
446        my $obj = $self->get_object('nethost', $nethostname) or do {
447            $self->log(LA_ERR, 'Unable to rename non exisant host %s', $nethostname);
448            return;
449        };
450        $obj->_delAttributeValue(cname => $to);
451    }
452    $self->rename_object('nethost', $nethostname, $to) or return;
453    if ($config{'addcname'}) {
454        my $obj = $self->get_object('nethost', $to);
455        $obj->_addAttributeValue(cname => $nethostname);
456    }
457    return 1;
458}
459
460=head2 nethost_exchange_ip ($ip1, $ip2)
461
462Exchange ip1 with ip2 in base
463
464=cut
465
466sub nethost_exchange_ip {
467    my ($self, $ip1, $ip2) = @_;
468    my ($obj1, $obj2);
469    if (my ($host1) = $self->search_objects('nethost', "ip=$ip1")) {
470        $obj1 = $self->get_object('nethost', $host1);
471    } else {
472        $self->log(LA_ERR, "Cannot find host having $ip1");
473        return;
474    }
475    if (my ($host2) = $self->search_objects('nethost', "ip=$ip2")) {
476        $obj2 = $self->get_object('nethost', $host2);
477    } else {
478        $self->log(LA_ERR, "Cannot find host having $ip2");
479        return;
480    }
481    if ($obj1->id eq $obj2->id) {
482        $self->log(LA_ERR, "Both ip belong to same host (%s)", $obj1->id);
483        return;
484    }
485
486    $self->log(LA_NOTICE, "Exchanging IP between %s and %s", $obj1->id, $obj2->id);
487    $obj1->delAttributeValue('ip', $ip1) or return;
488    $obj2->delAttributeValue('ip', $ip2) or return;
489    $obj1->addAttributeValue('ip', $ip2) or return;
490    $obj2->addAttributeValue('ip', $ip1) or return;
491    return 1;
492}
493
494=head1 ATTRIBUTES FUNCTIONS
495
496=head2 register_attribute ($otype, $attribute, $comment)
497
498Register a new attribute in base
499
500=cut
501
502sub register_attribute {
503    my ($self, $otype, $attribute, $comment) = @_;
504    my $pclass = $self->_load_obj_class($otype) or return;
505    $pclass->register_attribute($self, $attribute, $comment);
506}
507
508=head2 is_registered_attribute ($otype, $attribute)
509
510Return true is attribute already exists
511
512=cut
513
514sub is_registered_attribute {
515    my ($self, $otype, $attribute) = @_;
516    my $pclass = $self->_load_obj_class($otype) or return;
517    $pclass->is_registered_attribute($self, $attribute);
518}
519
520=head2 get_attribute_comment ($otype, $attribute)
521
522Return the comment associated to attribute
523
524=cut
525
526sub get_attribute_comment {
527    my ($self, $otype, $attribute) = @_;
528    my $pclass = $self->_load_obj_class($otype) or return;
529    $pclass->get_attribute_comment($self, $attribute);
530}
531
532=head2 set_attribute_comment ($otype, $attribute, $comment)
533
534Set comment to attribute
535
536=cut
537
538sub set_attribute_comment {
539    my ($self, $otype, $attribute, $comment) = @_;
540    my $pclass = $self->_load_obj_class($otype) or return;
541    $pclass->set_attribute_comment($self, $attribute, $comment);
542}
543
544=head2 get_datarequest ($id)
545
546Return user request C<$id>
547
548=cut
549
550sub get_datarequest {
551    my ($self, $id) = @_;
552
553    my $sth = $self->db->prepare(q{
554        select name from request
555        where id = ?
556        });
557    $sth->execute($id);
558    if (my $res = $sth->fetchrow_hashref) {
559        my $accreq = $self->get_object('accreq', $res->{name});
560        return LATMOS::Accounts::Bases::Sql::DataRequest->new($accreq, $id);
561    } else {
562        return;
563    }
564}
565
566=head2 list_requests
567
568list user request currently waiting in base
569
570=cut
571
572sub list_requests {
573    my ($self, $due) = @_;
574
575    my $sth = $self->db->prepare(
576        sprintf(
577            q{
578            select id from request
579            where done is null
580            %s
581            order by apply
582            },
583            defined($due)
584                ? 'and apply ' . ($due ? '<' : '>=') . ' now()'
585                : ''
586        )
587    );
588    $sth->execute;
589    my @ids;
590    while (my $res = $sth->fetchrow_hashref) {
591        push(@ids, $res->{id});
592    }
593
594    @ids
595}
596
597=head2 list_requests_by_submitter ($id)
598
599list user request currently waiting in base ask by user C<$id>
600
601=cut
602
603sub list_requests_by_submitter {
604    my ($self, $id) = @_;
605
606    my $sth = $self->db->prepare(q{
607        select id from request
608        where done is null and "user" = ?
609        order by apply
610    });
611    $sth->execute($id);
612    my @ids;
613    while (my $res = $sth->fetchrow_hashref) {
614        push(@ids, $res->{id});
615    }
616
617    @ids
618}
619
620
621=head2 list_request_by_object ($otype, $id, $req)
622
623Return the list of pending request for a specific object
624
625C<$req> is an optional forms name to limit search
626
627=cut
628
629sub list_request_by_object {
630    my ($self, $otype, $id, $req) = @_;
631
632    my $sth = $self->db->prepare(q{
633        select * from request join
634        accreq on request.name = accreq.name
635        join accreq_attributes on accreq_attributes.okey = accreq.ikey
636        where
637        request.applied is NULL and
638        accreq_attributes.attr = 'oType' and
639        accreq_attributes.value = ?
640        and request.object = ?
641    } .
642    ($req ? ' and request.name = ? ' : '')
643    . q{
644        order by apply
645    });
646    $sth->execute($otype, $id, ($req ? ($req) : ()));
647    my @ids;
648    while (my $res = $sth->fetchrow_hashref) {
649        push(@ids, $res->{id});
650    }
651
652    @ids
653}
654
655=head2 list_pending_requests
656
657List user request to apply
658
659=cut
660
661sub list_pending_requests {
662    my ($self) = @_;
663
664    my $sth = $self->db->prepare(q{
665        select id from request
666        where done is null
667            and apply < now()
668        order by apply
669    });
670    $sth->execute;
671    my @ids;
672    while (my $res = $sth->fetchrow_hashref) {
673        push(@ids, $res->{id});
674    }
675
676    @ids
677}
678
679=head2 list_auto_pending_requests
680
681List automatic request
682
683=cut
684
685sub list_auto_pending_requests {
686    my ($self) = @_;
687
688    my $sth = $self->db->prepare(q{
689        select id from request
690        where done is null
691            and apply < now()
692            and automated = true
693        order by apply
694    });
695    $sth->execute;
696    my @ids;
697    while (my $res = $sth->fetchrow_hashref) {
698        push(@ids, $res->{id});
699    }
700
701    @ids
702}
703
704sub ReportChange {
705    my ($self, $otype, $name, $ref, $changetype, $message, @args) = @_;
706
707    my $sthmodifiedby = $self->db->prepare(q{
708        UPDATE objects set modifiedby = ? where ikey = ?
709    });
710
711    $sthmodifiedby->execute(
712        $self->user || '@Console',
713        $ref,
714    );
715
716    my $sth = $self->db->prepare(q{
717        INSERT into objectslogs (ikey, irev, otype, name, changetype, username, message)
718        VALUES (?,?,?,?,?,?,?)
719        });
720
721    $sth->execute(
722        $ref,
723        $self->current_rev,
724        $otype,
725        $name,
726        $changetype,
727        $self->user || '@Console',
728        sprintf($message, @args),
729    );
730}
731
732=head2 getobjectlogs($otype, $name)
733
734Return logs for object type C<$otype> having C<$name>.
735
736=cut 
737
738sub getobjectlogs {
739    my ($self, $otype, $name) = @_;
740
741    my $sth = $self->db->prepare(q{
742        select ikey from objectslogs where
743            otype = ? and
744            name  = ?
745        group by ikey
746    });
747    $sth->execute($otype, $name);
748    my @ids;
749    while (my $res = $sth->fetchrow_hashref) {
750        push(@ids, $res->{ikey});
751    }
752    @ids or return;
753
754    my $sth2 = $self->db->prepare(sprintf(
755        q{
756            select * from objectslogs where ikey IN (%s)
757            order by logdate asc
758        },
759        join(',', ('?') x scalar(@ids))
760    ));
761
762    $sth2->execute(@ids);
763    my @logs;
764    while (my $res = $sth2->fetchrow_hashref) {
765        push(@logs, $res);
766    }
767
768    return @logs;
769}
770
771=head2 getlogs
772
773Return logs for last year
774
775=cut
776
777sub getlogs {
778    my ($self) = @_;
779    my $sth2 = $self->db->prepare(
780        q{
781            select * from objectslogs
782            where logdate > now() - '1 year'::interval
783            order by logdate asc
784        },
785    );
786
787    $sth2->execute();
788    my @logs;
789    while (my $res = $sth2->fetchrow_hashref) {
790        push(@logs, $res);
791    }
792
793    return @logs;
794}
795
796
7971;
798
799__END__
800
801=head1 SEE ALSO
802
803=head1 AUTHOR
804
805Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
806
807=head1 COPYRIGHT AND LICENSE
808
809Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
810
811This library is free software; you can redistribute it and/or modify
812it under the same terms as Perl itself, either Perl version 5.10.0 or,
813at your option, any later version of Perl 5 you may have available.
814
815
816=cut
Note: See TracBrowser for help on using the repository browser.