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

Last change on this file since 1096 was 1096, checked in by nanardon, 12 years ago

List pending request on left menu

  • Property svn:keywords set to Id Rev
File size: 14.0 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 _commit {
123    my ($self) = @_;
124    if ($ENV{LA_NO_COMMIT}) {
125        $self->log(LA_DEBUG, 'DB::COMMIT (ignore due to LA_NO_COMMIT)');
126        return 1;
127    } else {
128        $self->log(LA_DEBUG, 'DB::COMMIT');
129    }
130    $self->{__cache} = undef;
131    $self->db->commit;
132}
133
134sub _rollback {
135    my ($self) = @_;
136    if ($ENV{LA_NO_COMMIT}) {
137        $self->log(LA_DEBUG, 'DB::ROLLBACK (ignore due to LA_NO_COMMIT)');
138        return 1
139    } else {
140        $self->log(LA_DEBUG, 'DB::ROLLBACK');
141    }
142    $self->{__cache} = undef;
143    $self->db->rollback;
144}
145
146sub list_supported_objects {
147    my ($self, @otype) = @_;
148    $self->SUPER::list_supported_objects(qw(site), @otype);
149}
150
151sub current_rev {
152    my ($self) = @_;
153    my $sth = $self->db->prepare_cached(
154        q{select max(rev) from revisions}
155    );
156    $sth->execute;
157    my $res = $sth->fetchrow_hashref;
158    $sth->finish;
159    return ($res->{max});
160} 
161
162=head1 SPECIFICS FUNCTIONS
163
164=head2 get_global_value ($varname)
165
166Return global value set into base
167
168=cut
169
170sub get_global_value {
171    my ($self, $varname) = @_;
172
173    my $sth = $self->db->prepare_cached(q{
174        select val from settings where varname = ?
175        });
176    $sth->execute($varname);
177    my $res = $sth->fetchrow_hashref;
178    $sth->finish;
179    $res->{val}
180}
181
182=head2 set_global_value ($varname, $value)
183
184Set global value.
185
186=cut
187
188sub set_global_value {
189    my ($self, $varname, $value) = @_;
190    my $sth = $self->db->prepare(q{
191        update settings set val = ? where varname = ?
192        });
193    $sth->execute($value, $varname) == 0 and do {
194        my $sth2 = $self->db->prepare(q{
195            insert into settings (val, varname) values (?,?)
196            });
197        $sth2->execute($value, $varname);
198    };
199}
200
201=head2 generate_rsa_key ($password)
202
203Return public and private peer rsa keys
204
205=cut
206
207sub generate_rsa_key {
208    my ($self, $password) = @_;
209
210    my $rsa = new Crypt::RSA ES => 'PKCS1v15';
211    my ($public, $private) = $rsa->keygen (
212        Identity  => 'LATMOS-Accounts',
213        Size      => 768,
214        Password  => $password,
215        Verbosity => 0,
216        KF=>'SSH',
217    ) or die $rsa->errstr(); # TODO avoid die
218    return ($public, $private);
219}
220
221=head2 private_key ($password)
222
223Load and return private rsa key
224
225=cut
226
227sub private_key {
228    my ($self, $password) = @_;
229    my $base = $self;
230    my $serialize = $base->get_global_value('rsa_private_key') or return;
231    my $privkey = Crypt::RSA::Key::Private::SSH->new;
232    $privkey->deserialize(String => [ decode_base64($serialize) ],
233        Passphrase => $password);
234    $privkey
235}
236
237=head2 get_rsa_password
238
239Return hash with peer username => encryptedPassword
240
241=cut
242
243sub get_rsa_password {
244    my ($self) = @_;
245    my $base = $self;
246    my $sth = $base->db->prepare(q{
247        select "name", value from "user" join user_attributes_base
248        on "user".ikey = user_attributes_base.okey
249        where user_attributes_base.attr = 'encryptedPassword'
250    });
251    $sth->execute;
252    my %users;
253    while (my $res = $sth->fetchrow_hashref) {
254        $users{$res->{name}} = $res->{value};
255    }
256    %users
257}
258
259=head2 store_rsa_key ($public, $private)
260
261Store public and private RSA key info data base
262
263=cut
264
265sub store_rsa_key {
266    my ($self, $public, $private) = @_;
267    my $base = $self;
268    $base->set_global_value('rsa_private_key',
269        encode_base64($private->serialize));
270    $base->set_global_value('rsa_public_key',
271        $public->serialize);
272    return;
273}
274
275=head2 find_next_expire_users ($expire)
276
277Search user expiring in C<$expire> delay
278
279=cut
280
281sub find_next_expire_users {
282    my ($self, $expire) = @_;
283
284    my $sth= $self->db->prepare(q{
285        select name from "user" where
286            expire < now() + ?::interval
287            and expire > now()
288            and expire is not null
289            } . ($self->{wexported} ? '' : 'and exported = true') . q{
290            order by expire
291        }
292    );
293    $sth->execute($expire || '1 month');
294    my @users;
295    while (my $res = $sth->fetchrow_hashref) {
296        push(@users, $res->{name});
297    }
298    @users
299}
300
301=head2 find_expired_users ($expire)
302
303Return list of user going to expires in C<$expire> delay
304
305=cut
306
307sub find_expired_users {
308    my ($self, $expire) = @_;
309
310    my $sth= $self->db->prepare(q{
311        select name from "user" where
312            expire < now() - ?::interval
313            and expire is not null
314        } . ($self->{wexported} ? '' : 'and exported = true') . q{
315            order by expire
316        }
317    );
318    $sth->execute($expire || '1 second');
319    my @users;
320    while (my $res = $sth->fetchrow_hashref) {
321        push(@users, $res->{name});
322    }
323    @users
324}
325
326=head2 rename_nethost ($nethostname, $to, %config)
327
328Facility function to rename computer to new name
329
330=cut
331
332sub rename_nethost {
333    my ($self, $nethostname, $to, %config) = @_;
334    {
335        my $obj = $self->get_object('nethost', $nethostname);
336        my @cname = grep { $_ && $_ ne $to}
337        $obj->get_attributes('cname');
338        $obj->set_c_fields(cname => [ @cname ]) or return;
339    }
340    $self->rename_object('nethost', $nethostname, $to) or return;
341    if ($config{'addcname'}) {
342        my $obj = $self->get_object('nethost', $to);
343        my @cname = grep { $_ } $obj->get_attributes('cname');
344        $obj->set_c_fields(cname => [ @cname, $nethostname ]);
345    }
346    return 1;
347}
348
349=head2 nethost_exchange_ip ($ip1, $ip2)
350
351Exchange ip1 with ip2 in base
352
353=cut
354
355sub nethost_exchange_ip {
356    my ($self, $ip1, $ip2) = @_;
357    my ($obj1, $obj2);
358    if (my ($host1) = $self->search_objects('nethost', "ip=$ip1")) {
359        $obj1 = $self->get_object('nethost', $host1);
360    } else {
361        $self->la_log(LA_ERR, "Cannot find host having $ip1");
362        return;
363    }
364    if (my ($host2) = $self->search_objects('nethost', "ip=$ip2")) {
365        $obj2 = $self->get_object('nethost', $host2);
366    } else {
367        $self->la_log(LA_ERR, "Cannot find host having $ip2");
368        return;
369    }
370    if ($obj1->id eq $obj2->id) {
371        $self->la_log(LA_ERR, "Both ip belong to same host (%s)", $obj1->id);
372        return;
373    }
374
375    my @ip1 = grep { $_ && $_ ne $ip1 } $obj1->get_attributes('ip');
376    $obj1->set_c_fields(ip => [ @ip1 ]);
377    my @ip2 = grep { $_ && $_ ne $ip2 } $obj2->get_attributes('ip');
378    $obj2->set_c_fields(ip => [ @ip2, $ip1 ]) or return;
379    $obj1->set_c_fields(ip => [ @ip1, $ip2 ]) or return;
380    return 1;
381}
382
383=head1 ATTRIBUTES FUNCTIONS
384
385=head2 register_attribute ($otype, $attribute, $comment)
386
387Register a new attribute in base
388
389=cut
390
391sub register_attribute {
392    my ($self, $otype, $attribute, $comment) = @_;
393    my $pclass = $self->_load_obj_class($otype) or return;
394    $pclass->register_attribute($self, $attribute, $comment);
395}
396
397=head2 is_registered_attribute ($otype, $attribute)
398
399Return true is attribute already exists
400
401=cut
402
403sub is_registered_attribute {
404    my ($self, $otype, $attribute) = @_;
405    my $pclass = $self->_load_obj_class($otype) or return;
406    $pclass->is_registered_attribute($self, $attribute);
407}
408
409=head2 get_attribute_comment ($otype, $attribute)
410
411Return the comment associated to attribute
412
413=cut
414
415sub get_attribute_comment {
416    my ($self, $otype, $attribute) = @_;
417    my $pclass = $self->_load_obj_class($otype) or return;
418    $pclass->get_attribute_comment($self, $attribute);
419}
420
421=head2 set_attribute_comment ($otype, $attribute, $comment)
422
423Set comment to attribute
424
425=cut
426
427sub set_attribute_comment {
428    my ($self, $otype, $attribute, $comment) = @_;
429    my $pclass = $self->_load_obj_class($otype) or return;
430    $pclass->set_attribute_comment($self, $attribute, $comment);
431}
432
433sub _check_user_manager {
434    $_[0]->_handle_by_unexported('user', 'manager', 'active');
435}
436
437sub _check_group_manager {
438    $_[0]->_handle_by_unexported('group', 'managedBy');
439}
440
441sub _check_nethost_owner {
442    $_[0]->_handle_by_unexported('nethost', 'owner', 'active');
443}
444
445sub _handle_by_unexported {
446    my ($self, $otype, $refattr, $chkattr) = @_;
447
448    my $ptrotype = $self->attribute($otype, $refattr)->reference();
449
450    my %unhandle;
451    foreach my $objname ($self->search_objects($otype, 'active=1', 'exported=1', "$refattr=*")) {
452        my $obj = $self->get_object($otype, $objname) or next;
453        my $val = $obj->get_attributes($refattr) or next;
454        if (my $refobj = $self->get_object($ptrotype, $val)) {
455            if (!$refobj->get_attributes($chkattr || 'exported')) {
456                $unhandle{$objname} = $val;
457            }
458        } else {
459            $unhandle{$objname} = $val;
460        }
461    }
462    %unhandle;
463}
464
465=head2 get_datarequest ($id)
466
467Return user request C<$id>
468
469=cut
470
471sub get_datarequest {
472    my ($self, $id) = @_;
473
474    my $sth = $self->db->prepare(q{
475        select name from request
476        where id = ?
477        });
478    $sth->execute($id);
479    if (my $res = $sth->fetchrow_hashref) {
480        my $accreq = $self->get_object('accreq', $res->{name});
481        return LATMOS::Accounts::Bases::Sql::DataRequest->new($accreq, $id);
482    } else {
483        return;
484    }
485}
486
487=head2 list_requests
488
489list user request currently waiting in base
490
491=cut
492
493sub list_requests {
494    my ($self, $due) = @_;
495
496    my $sth = $self->db->prepare(
497        sprintf(
498            q{
499            select id from request
500            where done is null
501            %s
502            order by apply
503            },
504            defined($due)
505                ? 'and apply ' . ($due ? '<' : '>=') . ' now()'
506                : ''
507        )
508    );
509    $sth->execute;
510    my @ids;
511    while (my $res = $sth->fetchrow_hashref) {
512        push(@ids, $res->{id});
513    }
514
515    @ids
516}
517
518=head2 list_requests_by_submitter ($id)
519
520list user request currently waiting in base ask by user C<$id>
521
522=cut
523
524sub list_requests_by_submitter {
525    my ($self, $id) = @_;
526
527    my $sth = $self->db->prepare(q{
528        select id from request
529        where done is null and "user" = ?
530        order by apply
531    });
532    $sth->execute($id);
533    my @ids;
534    while (my $res = $sth->fetchrow_hashref) {
535        push(@ids, $res->{id});
536    }
537
538    @ids
539}
540
541
542=head2 list_request_by_object ($otype, $id)
543
544Return the list of pending request for a specific object
545
546=cut
547
548sub list_request_by_object {
549    my ($self, $otype, $id) = @_;
550
551    my $sth = $self->db->prepare(q{
552        select * from request join
553        accreq on request.name = accreq.name
554        join accreq_attributes on accreq_attributes.okey = accreq.ikey
555        where
556        request.applied is NULL and
557        accreq_attributes.attr = 'oType' and
558        accreq_attributes.value = ?
559        and request.object = ?
560        order by apply
561    });
562    $sth->execute($otype, $id);
563    my @ids;
564    while (my $res = $sth->fetchrow_hashref) {
565        push(@ids, $res->{id});
566    }
567
568    @ids
569}
570
571=head2 list_pending_requests
572
573List user request to apply
574
575=cut
576
577sub list_pending_requests {
578    my ($self) = @_;
579
580    my $sth = $self->db->prepare(q{
581        select id from request
582        where done is null
583            and apply < now()
584        order by apply
585    });
586    $sth->execute;
587    my @ids;
588    while (my $res = $sth->fetchrow_hashref) {
589        push(@ids, $res->{id});
590    }
591
592    @ids
593}
594
595=head2 list_auto_pending_requests
596
597List automatic request
598
599=cut
600
601sub list_auto_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            and automated = true
609        order by apply
610    });
611    $sth->execute;
612    my @ids;
613    while (my $res = $sth->fetchrow_hashref) {
614        push(@ids, $res->{id});
615    }
616
617    @ids
618}
619
6201;
621
622__END__
623
624=head1 SEE ALSO
625
626=head1 AUTHOR
627
628Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
629
630=head1 COPYRIGHT AND LICENSE
631
632Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
633
634This library is free software; you can redistribute it and/or modify
635it under the same terms as Perl itself, either Perl version 5.10.0 or,
636at your option, any later version of Perl 5 you may have available.
637
638
639=cut
Note: See TracBrowser for help on using the repository browser.