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

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

Use new delAttributeValue and addAttributeValue to make code shorter (and working)

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