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

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

Add filter based aliases (dynamics)

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