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

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

Fix cross ref auto member discovery

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