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

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

Split authentication function per base fonctionnality

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