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

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

Don't use SSH storage form

After several hour to try to figure out it seems Crypt::RSA::Key::Private::SSH
is unable to properly encrypt the private, making everything readable.

This patch replace the SSH form by the native one.

  • Property svn:keywords set to Id Rev
File size: 16.3 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        my @cname = grep { $_ && $_ ne $to}
359        $obj->get_attributes('cname');
360        $obj->set_c_fields(cname => [ @cname ]) or return;
361    }
362    $self->rename_object('nethost', $nethostname, $to) or return;
363    if ($config{'addcname'}) {
364        my $obj = $self->get_object('nethost', $to);
365        my @cname = grep { $_ } $obj->get_attributes('cname');
366        $obj->set_c_fields(cname => [ @cname, $nethostname ]);
367    }
368    return 1;
369}
370
371=head2 nethost_exchange_ip ($ip1, $ip2)
372
373Exchange ip1 with ip2 in base
374
375=cut
376
377sub nethost_exchange_ip {
378    my ($self, $ip1, $ip2) = @_;
379    my ($obj1, $obj2);
380    if (my ($host1) = $self->search_objects('nethost', "ip=$ip1")) {
381        $obj1 = $self->get_object('nethost', $host1);
382    } else {
383        $self->la_log(LA_ERR, "Cannot find host having $ip1");
384        return;
385    }
386    if (my ($host2) = $self->search_objects('nethost', "ip=$ip2")) {
387        $obj2 = $self->get_object('nethost', $host2);
388    } else {
389        $self->la_log(LA_ERR, "Cannot find host having $ip2");
390        return;
391    }
392    if ($obj1->id eq $obj2->id) {
393        $self->la_log(LA_ERR, "Both ip belong to same host (%s)", $obj1->id);
394        return;
395    }
396
397    my @ip1 = grep { $_ && $_ ne $ip1 } $obj1->get_attributes('ip');
398    $obj1->set_c_fields(ip => [ @ip1 ]);
399    my @ip2 = grep { $_ && $_ ne $ip2 } $obj2->get_attributes('ip');
400    $obj2->set_c_fields(ip => [ @ip2, $ip1 ]) or return;
401    $obj1->set_c_fields(ip => [ @ip1, $ip2 ]) or return;
402    return 1;
403}
404
405=head1 ATTRIBUTES FUNCTIONS
406
407=head2 register_attribute ($otype, $attribute, $comment)
408
409Register a new attribute in base
410
411=cut
412
413sub register_attribute {
414    my ($self, $otype, $attribute, $comment) = @_;
415    my $pclass = $self->_load_obj_class($otype) or return;
416    $pclass->register_attribute($self, $attribute, $comment);
417}
418
419=head2 is_registered_attribute ($otype, $attribute)
420
421Return true is attribute already exists
422
423=cut
424
425sub is_registered_attribute {
426    my ($self, $otype, $attribute) = @_;
427    my $pclass = $self->_load_obj_class($otype) or return;
428    $pclass->is_registered_attribute($self, $attribute);
429}
430
431=head2 get_attribute_comment ($otype, $attribute)
432
433Return the comment associated to attribute
434
435=cut
436
437sub get_attribute_comment {
438    my ($self, $otype, $attribute) = @_;
439    my $pclass = $self->_load_obj_class($otype) or return;
440    $pclass->get_attribute_comment($self, $attribute);
441}
442
443=head2 set_attribute_comment ($otype, $attribute, $comment)
444
445Set comment to attribute
446
447=cut
448
449sub set_attribute_comment {
450    my ($self, $otype, $attribute, $comment) = @_;
451    my $pclass = $self->_load_obj_class($otype) or return;
452    $pclass->set_attribute_comment($self, $attribute, $comment);
453}
454
455sub _check_user_manager {
456    $_[0]->_handle_by_unexported('user', 'manager', 'active');
457}
458
459sub _check_group_manager {
460    $_[0]->_handle_by_unexported('group', 'managedBy');
461}
462
463sub _check_nethost_owner {
464    $_[0]->_handle_by_unexported('nethost', 'owner', 'active');
465}
466
467sub _handle_by_unexported {
468    my ($self, $otype, $refattr, $chkattr) = @_;
469
470    my $ptrotype = $self->attribute($otype, $refattr)->reference();
471
472    my %unhandle;
473    foreach my $objname ($self->search_objects($otype, 'active=1', 'exported=1', "$refattr=*")) {
474        my $obj = $self->get_object($otype, $objname) or next;
475        my $val = $obj->get_attributes($refattr) or next;
476        if (my $refobj = $self->get_object($ptrotype, $val)) {
477            if (!$refobj->get_attributes($chkattr || 'exported')) {
478                $unhandle{$objname} = $val;
479            }
480        } else {
481            $unhandle{$objname} = $val;
482        }
483    }
484    %unhandle;
485}
486
487=head2 get_datarequest ($id)
488
489Return user request C<$id>
490
491=cut
492
493sub get_datarequest {
494    my ($self, $id) = @_;
495
496    my $sth = $self->db->prepare(q{
497        select name from request
498        where id = ?
499        });
500    $sth->execute($id);
501    if (my $res = $sth->fetchrow_hashref) {
502        my $accreq = $self->get_object('accreq', $res->{name});
503        return LATMOS::Accounts::Bases::Sql::DataRequest->new($accreq, $id);
504    } else {
505        return;
506    }
507}
508
509=head2 list_requests
510
511list user request currently waiting in base
512
513=cut
514
515sub list_requests {
516    my ($self, $due) = @_;
517
518    my $sth = $self->db->prepare(
519        sprintf(
520            q{
521            select id from request
522            where done is null
523            %s
524            order by apply
525            },
526            defined($due)
527                ? 'and apply ' . ($due ? '<' : '>=') . ' now()'
528                : ''
529        )
530    );
531    $sth->execute;
532    my @ids;
533    while (my $res = $sth->fetchrow_hashref) {
534        push(@ids, $res->{id});
535    }
536
537    @ids
538}
539
540=head2 list_requests_by_submitter ($id)
541
542list user request currently waiting in base ask by user C<$id>
543
544=cut
545
546sub list_requests_by_submitter {
547    my ($self, $id) = @_;
548
549    my $sth = $self->db->prepare(q{
550        select id from request
551        where done is null and "user" = ?
552        order by apply
553    });
554    $sth->execute($id);
555    my @ids;
556    while (my $res = $sth->fetchrow_hashref) {
557        push(@ids, $res->{id});
558    }
559
560    @ids
561}
562
563
564=head2 list_request_by_object ($otype, $id, $req)
565
566Return the list of pending request for a specific object
567
568C<$req> is an optional forms name to limit search
569
570=cut
571
572sub list_request_by_object {
573    my ($self, $otype, $id, $req) = @_;
574
575    my $sth = $self->db->prepare(q{
576        select * from request join
577        accreq on request.name = accreq.name
578        join accreq_attributes on accreq_attributes.okey = accreq.ikey
579        where
580        request.applied is NULL and
581        accreq_attributes.attr = 'oType' and
582        accreq_attributes.value = ?
583        and request.object = ?
584    } .
585    ($req ? ' and request.name = ? ' : '')
586    . q{
587        order by apply
588    });
589    $sth->execute($otype, $id, ($req ? ($req) : ()));
590    my @ids;
591    while (my $res = $sth->fetchrow_hashref) {
592        push(@ids, $res->{id});
593    }
594
595    @ids
596}
597
598=head2 list_pending_requests
599
600List user request to apply
601
602=cut
603
604sub list_pending_requests {
605    my ($self) = @_;
606
607    my $sth = $self->db->prepare(q{
608        select id from request
609        where done is null
610            and apply < now()
611        order by apply
612    });
613    $sth->execute;
614    my @ids;
615    while (my $res = $sth->fetchrow_hashref) {
616        push(@ids, $res->{id});
617    }
618
619    @ids
620}
621
622=head2 list_auto_pending_requests
623
624List automatic request
625
626=cut
627
628sub list_auto_pending_requests {
629    my ($self) = @_;
630
631    my $sth = $self->db->prepare(q{
632        select id from request
633        where done is null
634            and apply < now()
635            and automated = true
636        order by apply
637    });
638    $sth->execute;
639    my @ids;
640    while (my $res = $sth->fetchrow_hashref) {
641        push(@ids, $res->{id});
642    }
643
644    @ids
645}
646
647sub ReportChange {
648    my ($self, $otype, $name, $ref, $changetype, $message, @args) = @_;
649
650    my $sth = $self->db->prepare(q{
651        INSERT into objectslogs (ikey, otype, name, changetype, username, message)
652        VALUES (?,?,?,?,?,?)
653        });
654
655    $sth->execute(
656        $ref,
657        $otype,
658        $name,
659        $changetype,
660        $self->user || '@Console',
661        sprintf($message, @args),
662    );
663}
664
665=head2 getobjectlogs($otype, $name)
666
667Return logs for object type C<$otype> having C<$name>.
668
669=cut 
670
671sub getobjectlogs {
672    my ($self, $otype, $name) = @_;
673
674    my $sth = $self->db->prepare(q{
675        select ikey from objectslogs where
676            otype = ? and
677            name  = ?
678        group by ikey
679    });
680    $sth->execute($otype, $name);
681    my @ids;
682    while (my $res = $sth->fetchrow_hashref) {
683        push(@ids, $res->{ikey});
684    }
685    @ids or return;
686
687    my $sth2 = $self->db->prepare(sprintf(
688        q{
689            select * from objectslogs where ikey IN (%s)
690            order by logdate asc
691        },
692        join(',', ('?') x scalar(@ids))
693    ));
694
695    $sth2->execute(@ids);
696    my @logs;
697    while (my $res = $sth2->fetchrow_hashref) {
698        push(@logs, $res);
699    }
700
701    return @logs;
702}
703
704=head2 getlogs
705
706Return logs for last year
707
708=cut
709
710sub getlogs {
711    my ($self) = @_;
712    my $sth2 = $self->db->prepare(
713        q{
714            select * from objectslogs
715            where logdate > now() - '1 year'::interval
716            order by logdate asc
717        },
718    );
719
720    $sth2->execute();
721    my @logs;
722    while (my $res = $sth2->fetchrow_hashref) {
723        push(@logs, $res);
724    }
725
726    return @logs;
727}
728
7291;
730
731__END__
732
733=head1 SEE ALSO
734
735=head1 AUTHOR
736
737Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
738
739=head1 COPYRIGHT AND LICENSE
740
741Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
742
743This library is free software; you can redistribute it and/or modify
744it under the same terms as Perl itself, either Perl version 5.10.0 or,
745at your option, any later version of Perl 5 you may have available.
746
747
748=cut
Note: See TracBrowser for help on using the repository browser.