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

Last change on this file since 1843 was 1843, checked in by nanardon, 8 years ago

Automatically register attributes a db start

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