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

Last change on this file since 2471 was 2471, checked in by nanardon, 3 years ago

Don't deref if olias don't exists

  • Property svn:keywords set to Id Rev
File size: 26.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 DBI;
10use Crypt::RSA;
11use Crypt::RSA::Key::Public::SSH;
12use Crypt::RSA::Key::Private::SSH;
13use Crypt::RSA::Key::Public;
14use Crypt::RSA::Key::Private;
15use Crypt::Blowfish;
16use MIME::Base64;
17
18our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
19
20sub SCHEMA_VERSION { 38 };
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 SCHEMA_VERSION
41
42Return the SQL schema version to use for this software version.
43
44=head2 new(%config)
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 {
55    my ($class, %config) = @_;
56   
57    my $base = {
58        db_conn => $config{db_conn},
59    };
60
61    bless($base, $class);
62}
63
64sub DESTROY {
65    my ($self) = @_;
66    $self->{_db} && $self->{_db}->rollback;
67}
68
69=head2 db
70
71Return a L<DBI> handle over database, load it if need.
72
73=cut
74
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            {
85                RaiseError => 0,
86                AutoCommit => 0,
87                PrintWarn => 1,
88                PrintError => 1,
89            }
90        ) or do {
91            $self->log(LA_ERR, "Cannot connect to database: %s", $DBI::errstr);   
92            return;
93        };
94        $self->{_db}->do(q{set DATESTYLE to 'DMY'});
95        $self->log(LA_DEBUG, 'New connection to DB');
96
97        return $self->{_db};
98    }
99}
100
101sub IsSchemaUpToDate {
102    my ($self) = @_;
103
104    my $sv = $self->get_global_value('schema_version') || 1;
105
106    return $sv == SCHEMA_VERSION;
107}
108
109sub SchemaUpgrade {
110    my ($self) = @_;
111
112    if (!$self->IsSchemaUpToDate) {
113        require LATMOS::Accounts::Bases::Sql::upgrade;
114        if ($self->_SchemaUpgrade()) {
115            $self->commit;
116        } else {
117            $self->rollback;
118            return;
119        }
120    } else {
121        return 1;
122    }
123}
124
125=head2 load
126
127Read file and load data into memory
128
129=cut
130
131sub load {
132    my ($self) = @_;
133    if (!$self->db) { return 0 };
134
135    if (!$self->IsSchemaUpToDate) {
136        my $sv = $self->get_global_value('schema_version') || 1;
137        $self->log(LA_ERR, "Schema need update, please run `la-sql-upgrade -b %s'", $self->label); 
138        return;
139    }
140
141    if (!$self->_CreateInternalObjects) {
142        $self->rollback;
143    }
144
145    1;
146}
147
148sub _CreateInternalObjects {
149    my ($self) = @_;
150
151    my $dbi = $self->{_db};
152
153    my @objects = (
154        {
155            name => 'dpmt',
156            otype => 'sutype',
157            attrs => {
158                description => 'Department',
159            },
160        },
161        {
162            name => 'contrattype',
163            otype => 'sutype',
164            attrs => {
165                description => 'Contract',
166            },
167        },
168        {
169            name => '-useralias',
170            otype => 'group',
171            attrs => {
172                description => 'Internal group for user alias object',
173                gidnumber => -1,
174                unexported => 1,
175            },
176            intern => 1,
177        },
178    );
179
180    my $change = 0;
181
182    $self->temp_switch_unexported( sub {
183
184            my $setnodel  = $dbi->prepare('UPDATE objects SET nodelete     = true where name = ? and nodelete     = false');
185            my $setintern = $dbi->prepare('UPDATE objects SET internobject = true where name = ? and internobject = false');
186
187            foreach (@objects) {
188                if (!$self->GetRawObject($_->{otype}, $_->{name})) {
189                    printf("Creating object %s/%s\n", $_->{otype}, $_->{name});
190                    $self->create_object($_->{otype}, $_->{name}, %{$_->{attrs} || {}})
191                        or warn sprintf("cannot create %s/%s\n", $_->{otype}, $_->{name});
192            $change += 1;
193                }
194
195                $change +=  $setnodel->execute($_->{name});
196            $change +=  $setintern->execute($_->{name}) if ($_->{intern});
197            }
198
199    }, 1);
200
201    if ($change > 0) {
202        $self->db->commit;
203    }
204
205    return 1;
206}
207
208=head2 ListInternalObjects($otype)
209
210List objects flags as internal for type C<$otype>
211
212=cut
213
214sub ListInternalObjects {
215    my ($self, $otype) = @_;
216
217    my $pclass = $self->_load_obj_class($otype) or return;
218
219    # Object Alias: checking if object is alias, then returning it:
220    my $sth = $self->db->prepare_cached(
221        sprintf(q{select %s as k from %s where and internobject = true},
222            $self->db->quote_identifier($pclass->_key_field),
223            $self->db->quote_identifier($pclass->_object_table),
224        ),
225    );
226    $sth->execute();
227    my @list;
228    while (my $res = $sth->fetchrow_hashref) {
229       push(@list, $_->{k});
230   }
231   return(@list);
232}
233
234=head2 GetRawObject($otype, $id)
235
236Return an object even it is internal, alias are not follow and even
237unexported object are returned
238
239This function must be used only for maintenance operation.
240
241=cut
242
243sub GetRawObject {
244    my ($self, $otype, $id) = @_;
245
246    my $pclass = $self->_load_obj_class($otype) or return;
247
248    return $self->SUPER::get_object($otype, $id);
249}
250
251sub _derefObject {
252    my ($self, $otype, $oalias) = @_;
253    $oalias or return;
254    if (my ($aliasotype, $aliasoname, $aliasattr) = $oalias =~ m/^([^\/]+)\.([^\.]+)\.(.*)$/) {
255        my $attribute = $self->attribute($aliasotype, $aliasattr) or do {
256            $self->log(LA_DEBUG, "Oalias %s (%s): can fetch attibute %s/%s",
257                $otype, $oalias, $aliasotype, $aliasattr);
258            return;
259        };
260        my $refotype = $attribute->reference or do {
261            $self->log(LA_DEBUG, "Oalias %s (%s): Attribute does not reference an object",
262                $otype, $oalias);
263            return;
264        };
265        $refotype eq $otype or do {
266            $self->log(LA_DEBUG, "Oalias %s (%s): Attribute does not reference same object type",
267                $otype, $oalias, $refotype);
268            return;
269        };
270        my $robj = $self->get_object($aliasotype, $aliasoname) or do {
271            $self->log(LA_DEBUG, "Oalias %s (%s): can fetch object %s/%s",
272                $otype, $oalias, $aliasotype, $aliasoname);
273            return;
274        };
275        my $rvalue = $robj->get_attributes($aliasattr) or do {
276            $self->log(LA_DEBUG, "Oalias %s (%s): attribute value is empty",
277                $otype, $oalias);
278            return;
279        };
280        return $self->get_object($refotype, $rvalue);
281    } else {
282        return $self->get_object( $otype, $oalias )
283    }
284}
285
286sub get_object {
287    my ($self, $otype, $id) = @_;
288
289    my $pclass = $self->_load_obj_class($otype) or return;
290
291    # Object Alias: checking if object is alias, then returning it:
292    my $sth = $self->db->prepare_cached(
293        sprintf(q{select oalias from %s where %s = ? and internobject = false %s},
294            $self->db->quote_identifier($pclass->_object_table),
295            $self->db->quote_identifier($pclass->_key_field),
296            ($self->{wexported} ? '' : 'and exported = true'),
297        ),
298    );
299    $sth->execute($id);
300    my $res = $sth->fetchrow_hashref;
301    $sth->finish;
302    if (my $oalias = $res->{oalias}) {
303        # Cross reference over object/attribute
304        $self->_derefObject($otype, $oalias);
305    } else {
306        return $self->SUPER::get_object($otype, $id);
307    }
308}
309
310=head2 getObjectFromOKey ($okey)
311
312Return the object from the db internal key
313
314=cut
315
316sub getObjectFromOKey {
317    my ($self, $okey) = @_;
318
319    my $findobj = $self->{_db}->prepare_cached(q{
320        select * from objects_table where ikey = ?
321    });
322
323    $findobj->execute($okey);
324
325    my $res = $findobj->fetchrow_hashref;
326    $findobj->finish;
327
328    if ($res) {
329        return $self->get_object($res->{relname}, $res->{name});
330    } else {
331        return;
332    }
333}
334
335sub _sync_dyn_group {
336    my ($self) = @_;
337
338    my @groups = $self->search_objects('group', 'autoMemberFilter=*');
339
340    my $res = 0;
341    foreach (@groups) {
342        my $g = $self->get_object('group', $_) or next;
343        $res += $g->populate_dyn_group;
344    }
345
346    $self->log(LA_DEBUG, "Group Dyn res %d", $res);
347    $res
348}
349
350sub _sync_dyn_aliases {
351    my ($self) = @_;
352
353    my @groups = $self->search_objects('aliases', 'autoMemberFilter=*');
354
355    my $res = 0;
356    foreach (@groups) {
357        my $g = $self->get_object('aliases', $_) or next;
358        $res += $g->populate_dyn_aliases;
359    }
360
361    $self->log(LA_DEBUG, "Aliases Dyn res %d", $res);
362    $res
363}
364
365=head2 PopulateDynData
366
367Recomputate dynamics attributes (autoMembersFilters) if need
368
369=cut
370
371sub PopulateDynData {
372    my ($self) = @_;
373
374    $self->log(LA_DEBUG, 'Running PopulateDynData()');
375
376    $self->temp_switch_unexported(sub {
377
378        foreach (1 .. 5) {
379            $self->log(LA_DEBUG, "%d loop for PopulateDynData", $_);
380            my $res = 0;
381            $res += $self->_sync_dyn_group   || 0;
382            $res += $self->_sync_dyn_aliases || 0;
383
384            if ($res == 0) {
385                last;
386            }
387        }
388
389        { # Trick for ssh keys
390            my %sshUser = map { $_ => 1 } (
391                $self->search_objects('user', 'authorizedKeys=*', 'oalias=NULL'),
392                $self->search_objects('user', 'sshPublicKeyFilter=*', 'oalias=NULL'),
393                $self->search_objects('user', 'sshPublicKey=*', 'oalias=NULL'),
394            );
395
396            foreach my $user (keys %sshUser) {
397                my $ouser = $self->get_object('user', $user) or next;
398                $ouser->set_fields( 'authorizedKeys', $ouser->_get_c_field('_authorizedKeys') );
399            }
400        }
401
402    }, 0);
403
404    return 1;
405}
406
407sub _commit {
408    my ($self) = @_;
409
410    # Let sync-manager update data in background
411    $self->PopulateDynData unless($self->config('ASyncDynData'));
412
413    if ($ENV{LA_NO_COMMIT}) {
414        $self->log(LA_DEBUG, 'DB::COMMIT (ignore due to LA_NO_COMMIT)');
415        return 1;
416    } else {
417        $self->log(LA_DEBUG, 'DB::COMMIT');
418    }
419    $self->{__cache} = undef;
420    $self->db->commit;
421}
422
423sub _rollback {
424    my ($self) = @_;
425    if ($ENV{LA_NO_COMMIT}) {
426        $self->log(LA_DEBUG, 'DB::ROLLBACK (ignore due to LA_NO_COMMIT)');
427        return 1
428    } else {
429        $self->log(LA_DEBUG, 'DB::ROLLBACK');
430    }
431    $self->{__cache} = undef;
432    $self->db->rollback;
433}
434
435sub list_supported_objects {
436    my ($self, @otype) = @_;
437    $self->SUPER::list_supported_objects(qw(site), @otype);
438}
439
440# For SQL listRealObjects != list_objects
441sub listRealObjects {
442    my ($self, $otype) = @_;
443    my $pclass = $self->_load_obj_class($otype) or return;
444    $pclass->listReal($self);
445}
446
447sub current_rev {
448    my ($self) = @_;
449    my $sth = $self->db->prepare_cached(
450        q{select max(rev) from revisions}
451    );
452    $sth->execute;
453    my $res = $sth->fetchrow_hashref;
454    $sth->finish;
455    return ($res->{max});
456} 
457
458sub authenticate_user {
459    my ($self, $username, $passwd) = @_;
460    $username or return;
461    my $uobj = $self->get_object('user', $username) or do {
462        la_log(LA_ERR, "Cannot authenticate non existing SQL user $username");
463        return;
464    };
465
466    if ($self->attribute('user', 'exported')) {
467        if (!$uobj->_get_c_field('exported')) {
468            la_log(LA_ERR, "User $username found but currently unexported");
469            return;
470        }
471    }
472
473    $self->SUPER::authenticate_user($username, $passwd);
474}
475
476=head1 SPECIFICS FUNCTIONS
477
478=head2 GetAlias($base, $id)
479
480Return object having id C<$id> only if it is an object alias
481
482=cut
483
484sub GetAlias {
485    my ($self, $otype, $id) = @_;
486
487    my $pclass = $self->_load_obj_class($otype) or return;
488
489    # Object Alias: checking if object is alias, then returning it:
490    my $sth = $self->db->prepare_cached(
491        sprintf(q{select oalias from %s where %s = ? and oalias IS NOT NULL and internobject = false %s},
492            $self->db->quote_identifier($pclass->_object_table),
493            $self->db->quote_identifier($pclass->_key_field),
494            ($self->{wexported} ? '' : 'and exported = true'),
495        ),
496    );
497    $sth->execute($id);
498    my $res = $sth->fetchrow_hashref;
499    $sth->finish;
500    if ($res) {
501        return $self->SUPER::get_object($otype, $id);
502    } else {
503        return;
504    }
505}
506
507=head2 CreateAlias($otype, $name, $for)
508
509Create an object alias named C<$name> for ovbject C<$for>
510
511=cut
512
513sub CreateAlias {
514    my ($self, $otype, $name, $for) = @_;
515
516    my $pclass = $self->_load_obj_class($otype) or return;
517
518    $for or die "Cannot create alias without giving object to point";
519
520    my $res = $pclass->CreateAlias($self, $name, $for);
521
522    if ($res) {
523        $self->ReportChange(
524            $otype,
525            $name,
526            $pclass->_get_ikey($self, $name),
527            'Create', "Alias %s %s => %s", $otype, $name, $for
528        );
529        $self->log(LA_DEBUG, "Alias $otype $name => $for created");
530        my $oalias = $self->GetAlias($otype, $name);
531        $oalias->_update_aliases_ptr();
532        return 1;
533    } else {
534        $self->log(LA_ERR, "Error when creating alias $otype $name");
535        return;
536    }
537}
538
539=head2 RenameAlias($otype, $name, $to)
540
541Rename an object alias
542
543=cut
544
545sub RenameAlias {
546    my ($self, $otype, $name, $to) = @_;
547
548    my $pclass = $self->_load_obj_class($otype) or return;
549
550    my $obj = $self->GetAlias($otype, $name) or do {
551        $self->log('Cannot get alias %s/%s for removal', $otype, $name);
552        return;
553    };
554
555    my $sth = $self->db->prepare_cached(sprintf(
556        'UPDATE %s SET %s = ? WHERE %s = ?',
557        $self->db->quote_identifier($pclass->_key_field),
558        $self->db->quote_identifier($pclass->_object_table),
559        $self->db->quote_identifier($pclass->_key_field),
560    ));
561
562    my $res = $sth->execute($to, $name);
563
564    return $res;
565}
566
567=head2 RemoveAlias($otype, $name, $for)
568
569Create an object alias named C<$name> for ovbject C<$for>
570
571=cut
572
573sub RemoveAlias {
574    my ($self, $otype, $name) = @_;
575
576    my $pclass = $self->_load_obj_class($otype) or return;
577
578    my $obj = $self->GetAlias($otype, $name) or do {
579        $self->log('Cannot get alias %s/%s for removal', $otype, $name);
580        return;
581    };
582
583    if ($obj->_get_attributes('internobject')) {
584        # Cannot happend: internal are not fetchable
585        $self->log(LA_ERR,'Cannot delete %s/%s: is an internal object', $pclass->type, $name);
586        return;
587    }
588    if ($obj->_get_attributes('nodelete')) {
589        $self->log(LA_ERR,'Cannot delete %s/%s: is write protected', $pclass->type, $name);
590        return;
591    }
592
593    my $id = $obj->Iid;
594
595    my $sth = $self->db->prepare_cached(sprintf(
596        'DELETE FROM %s WHERE %s = ?',
597        $self->db->quote_identifier($pclass->_object_table),
598        $self->db->quote_identifier($pclass->_key_field),
599    ));
600
601    $obj->_update_aliases_ptr;
602    my $res = $sth->execute($name);
603
604    if ($res) {
605        $self->ReportChange(
606            $otype,
607            $name,
608            $id,
609            'Delete', "Alias %s %s deleted", $otype, $name
610        );
611        $self->log(LA_DEBUG, "Alias $otype $name removed");
612        return 1;
613    } else {
614        $self->log(LA_ERR, "Error when removing alias $otype $name");
615        return;
616    }
617}
618
619=head2 get_global_value ($varname)
620
621Return global value set into base
622
623=cut
624
625sub get_global_value {
626    my ($self, $varname) = @_;
627
628    my $sth = $self->db->prepare_cached(q{
629        select val from settings where varname = ?
630        });
631    $sth->execute($varname);
632    my $res = $sth->fetchrow_hashref;
633    $sth->finish;
634    $res->{val}
635}
636
637=head2 set_global_value ($varname, $value)
638
639Set global value.
640
641=cut
642
643sub set_global_value {
644    my ($self, $varname, $value) = @_;
645    my $sth = $self->db->prepare(q{
646        update settings set val = ? where varname = ?
647        });
648    $sth->execute($value, $varname) == 0 and do {
649        my $sth2 = $self->db->prepare(q{
650            insert into settings (val, varname) values (?,?)
651            });
652        $sth2->execute($value, $varname);
653    };
654}
655
656=head2 del_global_value ($varname)
657
658Delete global value from base
659
660=cut
661
662sub del_global_value {
663    my ($self, $varname) = @_;
664
665    my $sth = $self->db->prepare_cached(q{
666        delete from settings where varname = ?
667        });
668    return $sth->execute($varname);
669}
670
671=head2 generate_rsa_key ($password)
672
673Return public and private peer rsa keys
674
675=cut
676
677sub generate_rsa_key {
678    my ($self, $password) = @_;
679
680    my $rsa = new Crypt::RSA ES => 'PKCS1v15';
681    my ($public, $private) = $rsa->keygen (
682        Identity  => 'LATMOS-Accounts',
683        Size      => 2048,
684        Password  => $password,
685        Verbosity => 0,
686    ) or die $rsa->errstr(); # TODO avoid die
687    return ($public, $private);
688}
689
690=head2 private_key ($password)
691
692Load and return private rsa key
693
694=cut
695
696sub private_key {
697    my ($self, $password) = @_;
698    my $base = $self;
699    my $serialize = $base->get_global_value('rsa_private_key') or return;
700    my $string = decode_base64($serialize);
701    my $privkey = $string =~ /^SSH PRIVATE KEY FILE/
702        ? Crypt::RSA::Key::Private::SSH->new
703        : Crypt::RSA::Key::Private->new;
704    $privkey = $privkey->deserialize(
705        String => [ $string ],
706        Password => $password
707    );
708    $privkey->reveal( Password => $password );
709    $privkey;
710}
711
712=head2 get_rsa_password
713
714Return hash with peer username => encryptedPassword
715
716=cut
717
718sub get_rsa_password {
719    my ($self) = @_;
720    my $base = $self;
721    my $sth = $base->db->prepare(q{
722        select "name", value from "user" join user_attributes_base
723        on "user".ikey = user_attributes_base.okey
724        where user_attributes_base.attr = 'encryptedPassword'
725    });
726    $sth->execute;
727    my %users;
728    while (my $res = $sth->fetchrow_hashref) {
729        $users{$res->{name}} = $res->{value};
730    }
731    %users
732}
733
734=head2 store_rsa_key ($public, $private)
735
736Store public and private RSA key info data base
737
738=cut
739
740sub store_rsa_key {
741    my ($self, $public, $private) = @_;
742    my $base = $self;
743    $private->hide;
744    $base->set_global_value('rsa_private_key',
745        encode_base64($private->serialize));
746    $base->set_global_value('rsa_public_key',
747        $public->serialize);
748    return;
749}
750
751=head2 find_next_expire_users ($expire)
752
753Search user expiring in C<$expire> delay
754
755=cut
756
757sub find_next_expire_users {
758    my ($self, $expire) = @_;
759
760    my $sth= $self->db->prepare(q{
761        select name from "user" where
762            expire < now() + ?::interval
763            and expire > now()
764            and expire is not null
765            and internobject = false
766            } . ($self->{wexported} ? '' : 'and exported = true') . q{
767            order by expire
768        }
769    );
770    $sth->execute($expire || '1 month');
771    my @users;
772    while (my $res = $sth->fetchrow_hashref) {
773        push(@users, $res->{name});
774    }
775    @users
776}
777
778=head2 find_expired_users ($expire)
779
780Return list of user going to expires in C<$expire> delay
781
782=cut
783
784sub find_expired_users {
785    my ($self, $expire) = @_;
786
787    my $sth= $self->db->prepare(q{
788        select name from "user" where
789            expire < now() - ?::interval
790            and expire is not null
791            and internobject = false
792        } . ($self->{wexported} ? '' : 'and exported = true') . q{
793            order by expire
794        }
795    );
796    $sth->execute($expire || '1 second');
797    my @users;
798    while (my $res = $sth->fetchrow_hashref) {
799        push(@users, $res->{name});
800    }
801    @users
802}
803
804=head2 rename_nethost ($nethostname, $to, %config)
805
806Facility function to rename computer to new name
807
808=cut
809
810sub rename_nethost {
811    my ($self, $nethostname, $to, %config) = @_;
812    {
813        my $obj = $self->get_object('nethost', $nethostname) or do {
814            $self->log(LA_ERR, 'Unable to rename non exisant host %s', $nethostname);
815            return;
816        };
817        $obj->_delAttributeValue(cname => $to);
818    }
819    $self->rename_object('nethost', $nethostname, $to) or return;
820    if ($config{'addcname'}) {
821        my $obj = $self->get_object('nethost', $to);
822        $obj->_addAttributeValue(cname => $nethostname);
823    }
824    return 1;
825}
826
827=head2 nethost_exchange_ip ($ip1, $ip2)
828
829Exchange ip1 with ip2 in base
830
831=cut
832
833sub nethost_exchange_ip {
834    my ($self, $ip1, $ip2) = @_;
835    my ($obj1, $obj2);
836    if (my ($host1) = $self->search_objects('nethost', "ip=$ip1")) {
837        $obj1 = $self->get_object('nethost', $host1);
838    } else {
839        $self->log(LA_ERR, "Cannot find host having $ip1");
840        return;
841    }
842    if (my ($host2) = $self->search_objects('nethost', "ip=$ip2")) {
843        $obj2 = $self->get_object('nethost', $host2);
844    } else {
845        $self->log(LA_ERR, "Cannot find host having $ip2");
846        return;
847    }
848    if ($obj1->id eq $obj2->id) {
849        $self->log(LA_ERR, "Both ip belong to same host (%s)", $obj1->id);
850        return;
851    }
852
853    $self->log(LA_NOTICE, "Exchanging IP between %s and %s", $obj1->id, $obj2->id);
854    $obj1->delAttributeValue('ip', $ip1) ;
855    $obj2->delAttributeValue('ip', $ip2) ;
856    $obj1->addAttributeValue('ip', $ip2) ;
857    $obj2->addAttributeValue('ip', $ip1) ;
858    return 1;
859}
860
861=head1 ATTRIBUTES FUNCTIONS
862
863=cut
864
865sub obj_attr_allowed_values {
866    my ($self, $otype, $attr) = @_;
867    if (my @values = $self->SUPER::obj_attr_allowed_values($otype, $attr)) {
868        return @values;
869    } else {
870        $self->ListAttrValue($otype, $attr);
871    }
872}
873
874=head2 ListAttrValue($otype, $attribute)
875
876List values allow for an attribute set into SQL database
877
878=cut
879
880sub ListAttrValue {
881    my ($self, $otype, $attr) = @_;
882
883    my @sqlvalues;
884    my $getAllow = $self->db->prepare_cached(q{
885        SELECT * FROM attributes_values WHERE otype = ? AND attributes = ?
886        ORDER BY "value"
887    });
888    $getAllow->execute($otype, $attr);
889    while (my $res = $getAllow->fetchrow_hashref) {
890        push(@sqlvalues, $res->{value});
891    }
892    return @sqlvalues;
893}
894
895=head2 AddAttrValue($otype, $attr, @values)
896
897Add given values to allowed attribute list
898
899=cut
900
901sub AddAttrValue {
902    my ($self, $otype, $attr, @values) = @_;
903   
904    my $addAllow = $self->db->prepare_cached(q{
905        INSERT INTO attributes_values (otype, attributes, "value") values (?,?,?)
906    });
907   
908    foreach my $value (@values) {
909        if ($addAllow->execute($otype, $attr, $value)) {
910        } else {
911            $self->rollback;
912            return;
913        }
914    }
915
916    return 1;
917}
918
919=head2 DelAttrValue
920
921Delete a
922
923=cut
924
925sub DelAttrValue {
926    my ($self, $otype, $attr, @values) = @_;
927
928    if (@values) {
929        my $delAllow = $self->db->prepare_cached(q{
930            DELETE FROM attributes_values WHERE otype = ? and attributes = ? and "value" = ?
931        });
932
933        foreach my $value (@values) {
934
935            if ($delAllow->execute($otype, $attr, $value)) {
936            } else {
937                $self->rollback;
938                return;
939            }
940
941        }
942        return 1;
943    } else {
944        my $delAllow = $self->db->prepare_cached(q{
945            DELETE FROM attributes_values WHERE otype = ? and attributes = ?
946        });
947        if ($delAllow->execute($otype, $attr)) {
948            return 1;
949        } else {
950            $self->rollback;
951            return;
952        }
953    }
954}
955
956=head2 getEmploymentRange
957
958Return date range within employment can be found in database
959
960=cut
961
962sub getEmploymentRange {
963    my ($self, @filters) = @_;
964
965    my ($min,$max);
966
967    if (@filters) {
968        my @flist = $self->search_objects('employment', @filters);
969        my $minSql = $self->db->prepare(q{
970            SELECT min(firstday) as min FROM employment
971            WHERE name = ANY (?)
972        });
973        $minSql->execute(\@flist);
974        if (my $res = $minSql->fetchrow_hashref) {
975            $min = $res->{min}
976        }
977        my $maxSql = $self->db->prepare(q{
978            SELECT max(lastday) as max FROM employment
979            WHERE name = ANY (?)
980        });
981        $maxSql->execute(\@flist);
982        if (my $res = $maxSql->fetchrow_hashref) {
983            $max = $res->{max}
984        }
985    } else {
986        my $minSql = $self->db->prepare(q{
987            SELECT min(firstday) as min FROM employment
988        });
989        $minSql->execute;
990        if (my $res = $minSql->fetchrow_hashref) {
991            $min = $res->{min}
992        }
993        my $maxSql = $self->db->prepare(q{
994            SELECT max(lastday) as max FROM employment
995        });
996        $maxSql->execute;
997        if (my $res = $maxSql->fetchrow_hashref) {
998            $max = $res->{max}
999        }
1000    }
1001
1002    return ($min,$max);
1003}
1004
1005sub ReportChange {
1006    my ($self, $otype, $name, $ref, $changetype, $message, @args) = @_;
1007
1008    my $sthmodifiedby = $self->db->prepare(q{
1009        UPDATE objects set modifiedby = ? where ikey = ?
1010    });
1011
1012    $sthmodifiedby->execute(
1013        $self->LogUser,
1014        $ref,
1015    );
1016
1017    my ($potype, $pname, $pikey);
1018
1019    if (my $obj = $self->get_object($otype, $name)) {
1020        if (my $parent = $obj->ParentObject) {
1021            $potype = $parent->type;
1022            $pname  = $parent->id;
1023            $pikey  = $parent->Iid; 
1024        }
1025    }
1026
1027    my $sth = $self->db->prepare(q{
1028        INSERT into objectslogs (ikey, irev, otype, name, changetype, username, message, parentotype, parentname, parentikey)
1029        VALUES (?,?,?,?,?,?,?,?,?,?)
1030        });
1031
1032    $sth->execute(
1033        $ref,
1034        $self->current_rev,
1035        $otype,
1036        $name,
1037        $changetype,
1038        $self->LogUser,
1039        sprintf($message, @args),
1040        $potype, $pname, $pikey,
1041    );
1042}
1043
1044=head2 getobjectlogs($otype, @names)
1045
1046Return logs for object type C<$otype> having C<$name>.
1047
1048=cut 
1049
1050sub getobjectlogs {
1051    my ($self, $otype, @names) = @_;
1052
1053    my $sth = $self->db->prepare(q{
1054        select ikey from objectslogs where
1055            (otype = $1 and name  = $2)
1056        group by ikey
1057    });
1058    my @ids;
1059    foreach my $name (@names) {
1060        $sth->execute($otype, $name);
1061        while (my $res = $sth->fetchrow_hashref) {
1062            push(@ids, $res->{ikey});
1063        }
1064    }
1065    @ids or return;
1066
1067    my $sth2 = $self->db->prepare(
1068        q{
1069            select * from objectslogs where ikey = ANY ($1)
1070                or parentikey = ANY ($1)
1071            order by logdate asc
1072        },
1073    );
1074
1075    $sth2->execute( \@ids );
1076    my @logs;
1077    while (my $res = $sth2->fetchrow_hashref) {
1078        push(@logs, $res);
1079    }
1080
1081    return @logs;
1082}
1083
1084=head2 getlogs
1085
1086Return logs for last year
1087
1088=cut
1089
1090sub getlogs {
1091    my ($self) = @_;
1092    my $sth2 = $self->db->prepare(
1093        q{
1094            select * from objectslogs
1095            where logdate > now() - '1 year'::interval
1096            order by logdate asc
1097        },
1098    );
1099
1100    $sth2->execute();
1101    my @logs;
1102    while (my $res = $sth2->fetchrow_hashref) {
1103        push(@logs, $res);
1104    }
1105
1106    return @logs;
1107}
1108
11091;
1110
1111__END__
1112
1113=head1 SEE ALSO
1114
1115=head1 AUTHOR
1116
1117Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
1118
1119=head1 COPYRIGHT AND LICENSE
1120
1121Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
1122
1123This library is free software; you can redistribute it and/or modify
1124it under the same terms as Perl itself, either Perl version 5.10.0 or,
1125at your option, any later version of Perl 5 you may have available.
1126
1127
1128=cut
Note: See TracBrowser for help on using the repository browser.