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

Last change on this file since 2243 was 2243, checked in by nanardon, 5 years ago

Improve schema upgrade procedure

  • Property svn:keywords set to Id Rev
File size: 26.6 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 { 32 };
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        my %sshUser = map { $_ => 1 } (
390            $self->search_objects('user', 'authorizedKeys=*', 'oalias=NULL'),
391            $self->search_objects('user', 'sshPublicKeyFilter=*', 'oalias=NULL'),
392            $self->search_objects('user', 'sshPublicKey=*', 'oalias=NULL'),
393        );
394
395        foreach my $user (keys %sshUser) {
396            my $ouser = $self->get_object('user', $user) or next;
397            $ouser->set_fields( 'authorizedKeys', $ouser->_get_c_field('_authorizedKeys') );
398        }
399
400    }, 0);
401
402    return 1;
403}
404
405sub _commit {
406    my ($self) = @_;
407
408    # Let sync-manager update data in background
409    $self->PopulateDynData unless($self->config('ASyncDynData'));
410
411    if ($ENV{LA_NO_COMMIT}) {
412        $self->log(LA_DEBUG, 'DB::COMMIT (ignore due to LA_NO_COMMIT)');
413        return 1;
414    } else {
415        $self->log(LA_DEBUG, 'DB::COMMIT');
416    }
417    $self->{__cache} = undef;
418    $self->db->commit;
419}
420
421sub _rollback {
422    my ($self) = @_;
423    if ($ENV{LA_NO_COMMIT}) {
424        $self->log(LA_DEBUG, 'DB::ROLLBACK (ignore due to LA_NO_COMMIT)');
425        return 1
426    } else {
427        $self->log(LA_DEBUG, 'DB::ROLLBACK');
428    }
429    $self->{__cache} = undef;
430    $self->db->rollback;
431}
432
433sub list_supported_objects {
434    my ($self, @otype) = @_;
435    $self->SUPER::list_supported_objects(qw(site), @otype);
436}
437
438# For SQL listRealObjects != list_objects
439sub listRealObjects {
440    my ($self, $otype) = @_;
441    my $pclass = $self->_load_obj_class($otype) or return;
442    $pclass->listReal($self);
443}
444
445sub current_rev {
446    my ($self) = @_;
447    my $sth = $self->db->prepare_cached(
448        q{select max(rev) from revisions}
449    );
450    $sth->execute;
451    my $res = $sth->fetchrow_hashref;
452    $sth->finish;
453    return ($res->{max});
454} 
455
456sub authenticate_user {
457    my ($self, $username, $passwd) = @_;
458    $username or return;
459    my $uobj = $self->get_object('user', $username) or do {
460        la_log(LA_ERR, "Cannot authenticate non existing SQL user $username");
461        return;
462    };
463
464    if ($self->attribute('user', 'exported')) {
465        if (!$uobj->_get_c_field('exported')) {
466            la_log(LA_ERR, "User $username found but currently unexported");
467            return;
468        }
469    }
470
471    $self->SUPER::authenticate_user($username, $passwd);
472}
473
474=head1 SPECIFICS FUNCTIONS
475
476=head2 GetAlias($base, $id)
477
478Return object having id C<$id> only if it is an object alias
479
480=cut
481
482sub GetAlias {
483    my ($self, $otype, $id) = @_;
484
485    my $pclass = $self->_load_obj_class($otype) or return;
486
487    # Object Alias: checking if object is alias, then returning it:
488    my $sth = $self->db->prepare_cached(
489        sprintf(q{select oalias from %s where %s = ? and oalias IS NOT NULL and internobject = false %s},
490            $self->db->quote_identifier($pclass->_object_table),
491            $self->db->quote_identifier($pclass->_key_field),
492            ($self->{wexported} ? '' : 'and exported = true'),
493        ),
494    );
495    $sth->execute($id);
496    my $res = $sth->fetchrow_hashref;
497    $sth->finish;
498    if ($res) {
499        return $self->SUPER::get_object($otype, $id);
500    } else {
501        return;
502    }
503}
504
505=head2 CreateAlias($otype, $name, $for)
506
507Create an object alias named C<$name> for ovbject C<$for>
508
509=cut
510
511sub CreateAlias {
512    my ($self, $otype, $name, $for) = @_;
513
514    my $pclass = $self->_load_obj_class($otype) or return;
515
516    $for or die "Cannot create alias without giving object to point";
517
518    my $res = $pclass->CreateAlias($self, $name, $for);
519
520    if ($res) {
521        $self->ReportChange(
522            $otype,
523            $name,
524            $pclass->_get_ikey($self, $name),
525            'Create', "Alias %s %s => %s", $otype, $name, $for
526        );
527        $self->log(LA_DEBUG, "Alias $otype $name => $for created");
528        my $oalias = $self->GetAlias($otype, $name);
529        $oalias->_update_aliases_ptr();
530        return 1;
531    } else {
532        $self->log(LA_ERR, "Error when creating alias $otype $name");
533        return;
534    }
535}
536
537=head2 RenameAlias($otype, $name, $to)
538
539Rename an object alias
540
541=cut
542
543sub RenameAlias {
544    my ($self, $otype, $name, $to) = @_;
545
546    my $pclass = $self->_load_obj_class($otype) or return;
547
548    my $obj = $self->GetAlias($otype, $name) or do {
549        $self->log('Cannot get alias %s/%s for removal', $otype, $name);
550        return;
551    };
552
553    my $sth = $self->db->prepare_cached(sprintf(
554        'UPDATE %s SET %s = ? WHERE %s = ?',
555        $self->db->quote_identifier($pclass->_key_field),
556        $self->db->quote_identifier($pclass->_object_table),
557        $self->db->quote_identifier($pclass->_key_field),
558    ));
559
560    my $res = $sth->execute($to, $name);
561
562    return $res;
563}
564
565=head2 RemoveAlias($otype, $name, $for)
566
567Create an object alias named C<$name> for ovbject C<$for>
568
569=cut
570
571sub RemoveAlias {
572    my ($self, $otype, $name) = @_;
573
574    my $pclass = $self->_load_obj_class($otype) or return;
575
576    my $obj = $self->GetAlias($otype, $name) or do {
577        $self->log('Cannot get alias %s/%s for removal', $otype, $name);
578        return;
579    };
580
581    if ($obj->_get_attributes('internobject')) {
582        # Cannot happend: internal are not fetchable
583        $self->log(LA_ERR,'Cannot delete %s/%s: is an internal object', $pclass->type, $name);
584        return;
585    }
586    if ($obj->_get_attributes('nodelete')) {
587        $self->log(LA_ERR,'Cannot delete %s/%s: is write protected', $pclass->type, $name);
588        return;
589    }
590
591    my $id = $obj->Iid;
592
593    my $sth = $self->db->prepare_cached(sprintf(
594        'DELETE FROM %s WHERE %s = ?',
595        $self->db->quote_identifier($pclass->_object_table),
596        $self->db->quote_identifier($pclass->_key_field),
597    ));
598
599    $obj->_update_aliases_ptr;
600    my $res = $sth->execute($name);
601
602    if ($res) {
603        $self->ReportChange(
604            $otype,
605            $name,
606            $id,
607            'Delete', "Alias %s %s deleted", $otype, $name
608        );
609        $self->log(LA_DEBUG, "Alias $otype $name removed");
610        return 1;
611    } else {
612        $self->log(LA_ERR, "Error when removing alias $otype $name");
613        return;
614    }
615}
616
617=head2 get_global_value ($varname)
618
619Return global value set into base
620
621=cut
622
623sub get_global_value {
624    my ($self, $varname) = @_;
625
626    my $sth = $self->db->prepare_cached(q{
627        select val from settings where varname = ?
628        });
629    $sth->execute($varname);
630    my $res = $sth->fetchrow_hashref;
631    $sth->finish;
632    $res->{val}
633}
634
635=head2 set_global_value ($varname, $value)
636
637Set global value.
638
639=cut
640
641sub set_global_value {
642    my ($self, $varname, $value) = @_;
643    my $sth = $self->db->prepare(q{
644        update settings set val = ? where varname = ?
645        });
646    $sth->execute($value, $varname) == 0 and do {
647        my $sth2 = $self->db->prepare(q{
648            insert into settings (val, varname) values (?,?)
649            });
650        $sth2->execute($value, $varname);
651    };
652}
653
654=head2 del_global_value ($varname)
655
656Delete global value from base
657
658=cut
659
660sub del_global_value {
661    my ($self, $varname) = @_;
662
663    my $sth = $self->db->prepare_cached(q{
664        delete from settings where varname = ?
665        });
666    return $sth->execute($varname);
667}
668
669=head2 generate_rsa_key ($password)
670
671Return public and private peer rsa keys
672
673=cut
674
675sub generate_rsa_key {
676    my ($self, $password) = @_;
677
678    my $rsa = new Crypt::RSA ES => 'PKCS1v15';
679    my ($public, $private) = $rsa->keygen (
680        Identity  => 'LATMOS-Accounts',
681        Size      => 2048,
682        Password  => $password,
683        Verbosity => 0,
684    ) or die $rsa->errstr(); # TODO avoid die
685    return ($public, $private);
686}
687
688=head2 private_key ($password)
689
690Load and return private rsa key
691
692=cut
693
694sub private_key {
695    my ($self, $password) = @_;
696    my $base = $self;
697    my $serialize = $base->get_global_value('rsa_private_key') or return;
698    my $string = decode_base64($serialize);
699    my $privkey = $string =~ /^SSH PRIVATE KEY FILE/
700        ? Crypt::RSA::Key::Private::SSH->new
701        : Crypt::RSA::Key::Private->new;
702    $privkey = $privkey->deserialize(
703        String => [ $string ],
704        Password => $password
705    );
706    $privkey->reveal( Password => $password );
707    $privkey;
708}
709
710=head2 get_rsa_password
711
712Return hash with peer username => encryptedPassword
713
714=cut
715
716sub get_rsa_password {
717    my ($self) = @_;
718    my $base = $self;
719    my $sth = $base->db->prepare(q{
720        select "name", value from "user" join user_attributes_base
721        on "user".ikey = user_attributes_base.okey
722        where user_attributes_base.attr = 'encryptedPassword'
723    });
724    $sth->execute;
725    my %users;
726    while (my $res = $sth->fetchrow_hashref) {
727        $users{$res->{name}} = $res->{value};
728    }
729    %users
730}
731
732=head2 store_rsa_key ($public, $private)
733
734Store public and private RSA key info data base
735
736=cut
737
738sub store_rsa_key {
739    my ($self, $public, $private) = @_;
740    my $base = $self;
741    $private->hide;
742    $base->set_global_value('rsa_private_key',
743        encode_base64($private->serialize));
744    $base->set_global_value('rsa_public_key',
745        $public->serialize);
746    return;
747}
748
749=head2 find_next_expire_users ($expire)
750
751Search user expiring in C<$expire> delay
752
753=cut
754
755sub find_next_expire_users {
756    my ($self, $expire) = @_;
757
758    my $sth= $self->db->prepare(q{
759        select name from "user" where
760            expire < now() + ?::interval
761            and expire > now()
762            and expire is not null
763            and internobject = false
764            } . ($self->{wexported} ? '' : 'and exported = true') . q{
765            order by expire
766        }
767    );
768    $sth->execute($expire || '1 month');
769    my @users;
770    while (my $res = $sth->fetchrow_hashref) {
771        push(@users, $res->{name});
772    }
773    @users
774}
775
776=head2 find_expired_users ($expire)
777
778Return list of user going to expires in C<$expire> delay
779
780=cut
781
782sub find_expired_users {
783    my ($self, $expire) = @_;
784
785    my $sth= $self->db->prepare(q{
786        select name from "user" where
787            expire < now() - ?::interval
788            and expire is not null
789            and internobject = false
790        } . ($self->{wexported} ? '' : 'and exported = true') . q{
791            order by expire
792        }
793    );
794    $sth->execute($expire || '1 second');
795    my @users;
796    while (my $res = $sth->fetchrow_hashref) {
797        push(@users, $res->{name});
798    }
799    @users
800}
801
802=head2 rename_nethost ($nethostname, $to, %config)
803
804Facility function to rename computer to new name
805
806=cut
807
808sub rename_nethost {
809    my ($self, $nethostname, $to, %config) = @_;
810    {
811        my $obj = $self->get_object('nethost', $nethostname) or do {
812            $self->log(LA_ERR, 'Unable to rename non exisant host %s', $nethostname);
813            return;
814        };
815        $obj->_delAttributeValue(cname => $to);
816    }
817    $self->rename_object('nethost', $nethostname, $to) or return;
818    if ($config{'addcname'}) {
819        my $obj = $self->get_object('nethost', $to);
820        $obj->_addAttributeValue(cname => $nethostname);
821    }
822    return 1;
823}
824
825=head2 nethost_exchange_ip ($ip1, $ip2)
826
827Exchange ip1 with ip2 in base
828
829=cut
830
831sub nethost_exchange_ip {
832    my ($self, $ip1, $ip2) = @_;
833    my ($obj1, $obj2);
834    if (my ($host1) = $self->search_objects('nethost', "ip=$ip1")) {
835        $obj1 = $self->get_object('nethost', $host1);
836    } else {
837        $self->log(LA_ERR, "Cannot find host having $ip1");
838        return;
839    }
840    if (my ($host2) = $self->search_objects('nethost', "ip=$ip2")) {
841        $obj2 = $self->get_object('nethost', $host2);
842    } else {
843        $self->log(LA_ERR, "Cannot find host having $ip2");
844        return;
845    }
846    if ($obj1->id eq $obj2->id) {
847        $self->log(LA_ERR, "Both ip belong to same host (%s)", $obj1->id);
848        return;
849    }
850
851    $self->log(LA_NOTICE, "Exchanging IP between %s and %s", $obj1->id, $obj2->id);
852    $obj1->delAttributeValue('ip', $ip1) ;
853    $obj2->delAttributeValue('ip', $ip2) ;
854    $obj1->addAttributeValue('ip', $ip2) ;
855    $obj2->addAttributeValue('ip', $ip1) ;
856    return 1;
857}
858
859=head1 ATTRIBUTES FUNCTIONS
860
861=cut
862
863sub obj_attr_allowed_values {
864    my ($self, $otype, $attr) = @_;
865    if (my @values = $self->SUPER::obj_attr_allowed_values("$otype.$attr", 'allowed')) {
866        return @values;
867    } else {
868        $self->ListAttrValue($otype, $attr);
869    }
870}
871
872=head2 ListAttrValue($otype, $attribute)
873
874List values allow for an attribute set into SQL database
875
876=cut
877
878sub ListAttrValue {
879    my ($self, $otype, $attr) = @_;
880
881    my @sqlvalues;
882    my $getAllow = $self->db->prepare_cached(q{
883        SELECT * FROM attributes_values WHERE otype = ? AND attributes = ?
884        ORDER BY "value"
885    });
886    $getAllow->execute($otype, $attr);
887    while (my $res = $getAllow->fetchrow_hashref) {
888        push(@sqlvalues, $res->{value});
889    }
890    return @sqlvalues;
891}
892
893=head2 AddAttrValue($otype, $attr, @values)
894
895Add given values to allowed attribute list
896
897=cut
898
899sub AddAttrValue {
900    my ($self, $otype, $attr, @values) = @_;
901   
902    my $addAllow = $self->db->prepare_cached(q{
903        INSERT INTO attributes_values (otype, attributes, "value") values (?,?,?)
904    });
905   
906    foreach my $value (@values) {
907        if ($addAllow->execute($otype, $attr, $value)) {
908        } else {
909            $self->rollback;
910            return;
911        }
912    }
913
914    return 1;
915}
916
917=head2 DelAttrValue
918
919Delete a
920
921=cut
922
923sub DelAttrValue {
924    my ($self, $otype, $attr, @values) = @_;
925
926    if (@values) {
927        my $delAllow = $self->db->prepare_cached(q{
928            DELETE FROM attributes_values WHERE otype = ? and attributes = ? and "value" = ?
929        });
930
931        foreach my $value (@values) {
932
933            if ($delAllow->execute($otype, $attr, $value)) {
934            } else {
935                $self->rollback;
936                return;
937            }
938
939        }
940        return 1;
941    } else {
942        my $delAllow = $self->db->prepare_cached(q{
943            DELETE FROM attributes_values WHERE otype = ? and attributes = ?
944        });
945        if ($delAllow->execute($otype, $attr)) {
946            return 1;
947        } else {
948            $self->rollback;
949            return;
950        }
951    }
952}
953
954=head2 getEmploymentRange
955
956Return date range within employment can be found in database
957
958=cut
959
960sub getEmploymentRange {
961    my ($self, @filters) = @_;
962
963    my ($min,$max);
964
965    if (@filters) {
966        my @flist = $self->search_objects('employment', @filters);
967        my $minSql = $self->db->prepare(q{
968            SELECT min(firstday) as min FROM employment
969            WHERE name = ANY (?)
970        });
971        $minSql->execute(\@flist);
972        if (my $res = $minSql->fetchrow_hashref) {
973            $min = $res->{min}
974        }
975        my $maxSql = $self->db->prepare(q{
976            SELECT max(lastday) as max FROM employment
977            WHERE name = ANY (?)
978        });
979        $maxSql->execute(\@flist);
980        if (my $res = $maxSql->fetchrow_hashref) {
981            $max = $res->{max}
982        }
983    } else {
984        my $minSql = $self->db->prepare(q{
985            SELECT min(firstday) as min FROM employment
986        });
987        $minSql->execute;
988        if (my $res = $minSql->fetchrow_hashref) {
989            $min = $res->{min}
990        }
991        my $maxSql = $self->db->prepare(q{
992            SELECT max(lastday) as max FROM employment
993        });
994        $maxSql->execute;
995        if (my $res = $maxSql->fetchrow_hashref) {
996            $max = $res->{max}
997        }
998    }
999
1000    return ($min,$max);
1001}
1002
1003sub ReportChange {
1004    my ($self, $otype, $name, $ref, $changetype, $message, @args) = @_;
1005
1006    my $sthmodifiedby = $self->db->prepare(q{
1007        UPDATE objects set modifiedby = ? where ikey = ?
1008    });
1009
1010    $sthmodifiedby->execute(
1011        $self->user || '@Console',
1012        $ref,
1013    );
1014
1015    my ($potype, $pname, $pikey);
1016
1017    if (my $obj = $self->get_object($otype, $name)) {
1018        if (my $parent = $obj->ParentObject) {
1019            $potype = $parent->type;
1020            $pname  = $parent->id;
1021            $pikey  = $parent->Iid; 
1022        }
1023    }
1024
1025    my $sth = $self->db->prepare(q{
1026        INSERT into objectslogs (ikey, irev, otype, name, changetype, username, message, parentotype, parentname, parentikey)
1027        VALUES (?,?,?,?,?,?,?,?,?,?)
1028        });
1029
1030    $sth->execute(
1031        $ref,
1032        $self->current_rev,
1033        $otype,
1034        $name,
1035        $changetype,
1036        $self->user || '@Console',
1037        sprintf($message, @args),
1038        $potype, $pname, $pikey,
1039    );
1040}
1041
1042=head2 getobjectlogs($otype, @names)
1043
1044Return logs for object type C<$otype> having C<$name>.
1045
1046=cut 
1047
1048sub getobjectlogs {
1049    my ($self, $otype, @names) = @_;
1050
1051    my $sth = $self->db->prepare(q{
1052        select ikey from objectslogs where
1053            (otype = $1 and name  = $2)
1054        group by ikey
1055    });
1056    my @ids;
1057    foreach my $name (@names) {
1058        $sth->execute($otype, $name);
1059        while (my $res = $sth->fetchrow_hashref) {
1060            push(@ids, $res->{ikey});
1061        }
1062    }
1063    @ids or return;
1064
1065    my $sth2 = $self->db->prepare(
1066        q{
1067            select * from objectslogs where ikey = ANY ($1)
1068                or parentikey = ANY ($1)
1069            order by logdate asc
1070        },
1071    );
1072
1073    $sth2->execute( \@ids );
1074    my @logs;
1075    while (my $res = $sth2->fetchrow_hashref) {
1076        push(@logs, $res);
1077    }
1078
1079    return @logs;
1080}
1081
1082=head2 getlogs
1083
1084Return logs for last year
1085
1086=cut
1087
1088sub getlogs {
1089    my ($self) = @_;
1090    my $sth2 = $self->db->prepare(
1091        q{
1092            select * from objectslogs
1093            where logdate > now() - '1 year'::interval
1094            order by logdate asc
1095        },
1096    );
1097
1098    $sth2->execute();
1099    my @logs;
1100    while (my $res = $sth2->fetchrow_hashref) {
1101        push(@logs, $res);
1102    }
1103
1104    return @logs;
1105}
1106
11071;
1108
1109__END__
1110
1111=head1 SEE ALSO
1112
1113=head1 AUTHOR
1114
1115Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
1116
1117=head1 COPYRIGHT AND LICENSE
1118
1119Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
1120
1121This library is free software; you can redistribute it and/or modify
1122it under the same terms as Perl itself, either Perl version 5.10.0 or,
1123at your option, any later version of Perl 5 you may have available.
1124
1125
1126=cut
Note: See TracBrowser for help on using the repository browser.