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

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

new version

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