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

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

Add task modules

Add two task module:

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