source: trunk/lib/Vote/DB/Poll.pm @ 196

Last change on this file since 196 was 196, checked in by nanardon, 15 years ago
  • use new table for ballot value map
  • Property svn:keywords set to Id Rev
File size: 18.3 KB
Line 
1package Vote::DB::Poll;
2
3# $Id$
4
5use strict;
6use warnings;
7use base 'Vote::DB::common';
8use Crypt::RSA;
9use Crypt::RSA::Key::Public::SSH;
10use Crypt::RSA::Key::Private::SSH;
11use Crypt::CBC;
12use XML::Simple;
13use MIME::Base64;
14use Vote::DB::Ballot;
15use Vote::DB::Voting;
16use Vote::DB::Choice;
17
18=head1 NAME
19
20Vote::Model::Vote - Catalyst Model
21
22=head1 DESCRIPTION
23
24Catalyst Model.
25
26=cut
27
28sub new {
29    my ($class, $dbstring, $voteid) = @_;
30   
31    bless {
32        voteid => $voteid,
33        dbstring => $dbstring,
34        db => Vote::DB::common::_newdb($dbstring),
35    }, $class;
36}
37
38sub voteid { $_[0]->{voteid} }
39
40sub setup {
41    my ($self) = @_;
42    $self->param(
43        free_choice => 0,
44        choice_count => 1,
45    );
46}
47
48sub param {
49    my ($self, %attr) = @_;
50
51    keys %attr or return;
52    my @online_f = qw(label start end owner password);
53
54    if (grep { exists($attr{$_}) } @online_f) {
55        my $sth = $self->db->prepare_cached(
56            q{update poll set } .
57            join(',', map { qq("$_" = ?) } grep { exists $attr{$_} } @online_f) .
58            q{ where id = ?}
59        );
60        $sth->execute((map { $attr{$_} } grep { exists $attr{$_} } @online_f), $self->voteid)
61            or do {
62            $self->db->rollback;
63            return;
64        };
65    }
66
67    # vote settings in settings table
68    foreach my $var (keys %attr) {
69        grep { $var eq $_ } @online_f and next;
70        $self->set_settings($var, $attr{$var});
71    }
72    1
73}
74
75sub status {
76    my ($self) = @_;
77   
78    my $sth = $self->db->prepare_cached(
79        q{
80        select (start > now() or start is null) as before,
81               "end" < now() as after
82        from poll
83        where id = ?
84        }
85    );
86    $sth->execute($self->voteid);
87    my $res = $sth->fetchrow_hashref;
88    $sth->finish;
89    $res or return;
90    if ($res->{before}) {
91        return 'BEFORE';
92    } elsif ($res->{after}) {
93        return 'AFTER';
94    } else {
95        return 'RUNNING';
96    }
97}
98
99sub info {
100    my ($self) = @_;
101
102    my $sth = $self->db->prepare_cached(
103        q{
104        select *,
105        to_char("start", 'DD/MM/YYYY') as dstart,
106        to_char("start", 'HH24:MI:SS') as hstart,
107        to_char("end", 'DD/MM/YYYY') as dend,
108        to_char("end", 'HH24:MI:SS') as hend
109        from poll where id = ?
110        }
111    );
112
113    $sth->execute($self->voteid);
114    my $res = $sth->fetchrow_hashref;
115    $sth->finish;
116    if ($res) {
117        my $get = $self->db->prepare_cached(
118            q{select var, val from settings where poll = ?}
119        );
120        $get->execute($self->voteid);
121        while (my $set = $get->fetchrow_hashref) {
122            $res->{$set->{var}} = $set->{val};
123        }
124    }
125    $res->{free_choice} ||= 0; # avoiding undef
126    $res
127}
128
129sub set_settings {
130    my ($self, $var, $val) = @_;
131
132    my $upd = $self->db->prepare_cached(
133        q{update settings set val = ? where poll = ? and var = ?}
134    );
135
136    if ($upd->execute($val, $self->voteid, $var) == 0) {
137        my $add = $self->db->prepare_cached(
138            q{insert into settings (poll, var, val) values (?,?,?)}
139        );
140
141        $add->execute($self->voteid, $var, $val);
142    }
143}
144
145sub signing {
146    my ($self) = @_;
147
148    my $sth = $self->db->prepare_cached(
149        q{
150        select *, voting.key as vkey from voting left join signing
151        on signing.key = voting.key
152        where poll = ? order by voting.mail
153        }
154    );
155    $sth->execute($self->voteid);
156    my @people;
157    while (my $res = $sth->fetchrow_hashref) {
158        push(@people, $res);
159    }
160    @people
161}
162
163sub voting {
164    my ($self, $votingkey) = @_;
165
166    my $sth = $self->db->prepare_cached(
167        q{
168        select key from voting where poll = ? and key = ?
169        }
170    );
171
172    $sth->execute($self->voteid, $votingkey);
173    my $res = $sth->fetchrow_hashref;
174    $sth->finish;
175    return $res ? Vote::DB::Voting->new($self->{dbstring}, $votingkey) : undef;
176}
177
178sub voting_from_mail {
179    my ($self, $mail) = @_;
180
181    my $sth = $self->db->prepare_cached(
182        q{
183        select key from voting where poll = ? and mail = ?
184        }
185    );
186
187    $sth->execute($self->voteid, $mail);
188    my $res = $sth->fetchrow_hashref;
189    $sth->finish;
190    return $res ? Vote::DB::Voting->new($self->{dbstring}, $res->{key}) : undef;
191}
192
193sub voting_keys {
194    my ($self) = @_;
195
196    my $sth = $self->db->prepare_cached(
197        q{
198        select key from voting
199        where poll = ? order by voting.mail
200        }
201    );
202    $sth->execute($self->voteid);
203    my @people;
204    while (my $res = $sth->fetchrow_hashref) {
205        push(@people, $res->{key});
206    }
207    @people
208}
209
210sub voting_info {
211    my ($self) = @_;
212
213    my $sth = $self->db->prepare_cached(
214        q{
215        select *, voting.key as vkey from voting left join signing
216        on signing.key = voting.key
217        where voting.key = ?
218        }
219    );
220    $sth->execute($self->voteid);
221
222    my $res = $sth->fetchrow_hashref;
223    $sth->finish;
224    $res
225}
226
227sub choice {
228    my ($self, $chid) = @_;
229   
230    my $sth = $self->db->prepare_cached(
231        q{
232        select key from choice where poll = ? and key = ?
233        }
234    );
235    $sth->execute($self->voteid, $chid);
236    my $res = $sth->fetchrow_hashref;
237    $sth->finish;
238    return $res ? Vote::DB::Choice->new($self->{dbstring}, $chid) : undef;
239}
240
241sub choices {
242    my ($self) = @_;
243
244    my $sth = $self->db->prepare_cached(
245        q{
246        select key from choice where poll = ?
247        order by label
248        }
249    );
250    $sth->execute($self->voteid);
251    my @ch;
252    while (my $res = $sth->fetchrow_hashref) {
253        push(@ch, $res->{key});
254    }
255    @ch
256}
257
258sub add_choice {
259    my ($self, $label) = @_;
260
261    my $sth = $self->db->prepare_cached(
262        q{insert into choice (poll, label) values (?,?)}
263    );
264
265    $sth->execute($self->voteid, $label) or do {
266        $self->db->rollback;
267        return;
268    };
269
270    1
271}
272
273sub delete_choice {
274    my ($self, $chid) = @_;
275
276    my $sth = $self->db->prepare_cached(
277        q{delete from choice where key = ?}
278    );
279
280    $sth->execute($chid);
281}
282
283sub _register_signing {
284    my ($self, $mail, $referal) = @_;
285
286    my $vinfo = $self->voting_info_id($mail) or return;
287
288    my $sth = $self->db->prepare_cached(
289        q{
290        insert into signing (key, referal) values (?,?)
291        }
292    );
293    $sth->execute($vinfo->{key}, $referal) or do {
294        $self->db->rollback;
295        return;
296    };
297
298    1;
299}
300
301sub _register_ballot {
302    my ($self, $choice, $fchoice) = @_;
303
304    my $uid = ($self->is_crypted
305        ? $self->_register_ballot_crypted($choice, $fchoice)
306        : $self->_register_ballot_clear($choice, $fchoice))
307        or do {
308            self->db->rollback;
309            return;
310        };
311
312    $uid
313}
314
315sub _register_ballot_clear {
316    my ($self, $choice, $fchoice, $uid) = @_;
317
318    my $addb = $self->db->prepare_cached(
319        q{
320        insert into ballot (id, poll, invalid) values (?,?,?)
321        }
322    );
323    $uid ||= Vote::DB::common::gen_uid();
324    $addb->execute($uid, $self->voteid, scalar(@{$fchoice || []}) ? undef : 'f') or do {
325        self->db->rollback;
326        return;
327    };
328
329    $self->_register_ballot_items($uid, $choice, $fchoice) or do {
330        self->db->rollback;
331        return;
332    };
333
334    $uid
335}
336
337sub find_choice_key {
338    my ($self, $value) = @_;
339
340    my $sth = $self->prepare_cached(
341        q{select key from choice where lower(label) = ? and poll = ?}
342    );
343    $sth->execute(lc($value), $self->pollid);
344    my $res = $sth->fetchrow_hashref;
345    $sth->finish;
346    $res->{key}
347}
348
349sub _register_ballot_items {
350    my ($self, $uid, $choice, $fchoice) = @_;
351
352    my $addbc = $self->db->prepare_cached(
353        q{
354        insert into ballot_item (id, value, fromlist) values (?,?,?)
355        }
356    );
357    foreach (@{ $choice || []}) {
358        $addbc->execute($uid, $_, 't') or do {
359            $self->db->rollback;
360            return;
361        };
362    }
363    foreach (@{ $fchoice || []}) {
364        $_ or next;
365        my $chkey = $self->find_choice_key($_);
366        $addbc->execute($uid, $_, $chkey ? 't' : 'f') or do {
367            $self->db->rollback;
368            return;
369        };
370    }
371
372    $uid;
373}
374
375sub _register_ballot_crypted {
376    my ($self, $choice, $fchoice) = @_;
377    my $xml = XML::Simple->new(ForceArray => 1, RootName => 'ballot');
378    my $symkey = map{ chr(rand(256)) } (1 .. (256 / 8));
379    my $cipher = new Crypt::CBC($symkey, 'DES');
380    my $ballotuid = Vote::DB::common::gen_uid();
381    my $encryptedballot = $cipher->encrypt_hex(
382        $xml->XMLout({
383            id => $ballotuid,
384            sbal => $choice,
385            fsbal => $fchoice
386        })
387    );
388    my $encsymkey = $self->rsa->encrypt (
389        Message    => $symkey,
390        Key        => $self->public_key,
391        Armour     => 1,
392    ) || die $self->rsa->errstr();
393
394    my $addenc = $self->db->prepare_cached(
395        q{insert into ballot_enc (id, data, enckey, poll) values (?,?,?,?)}
396    );
397
398    my $uid = Vote::DB::common::gen_uid();
399    $addenc->execute($uid, $encryptedballot, $encsymkey, $self->voteid);
400    $ballotuid;
401}
402
403sub _decrypted_ballot {
404    my ($self, $ballotid, $privkey) = @_;
405    my $sth = $self->db->prepare_cached(
406        q{select * from ballot_enc where id = ? for update}
407    );
408    $sth->execute($ballotid);
409    my $ballot = $sth->fetchrow_hashref;
410    $sth->finish;
411    my $encsymkey = $ballot->{enckey};
412    my $data = $ballot->{data};
413    my $symkey = $self->rsa->decrypt (
414        Cyphertext => $encsymkey,
415        Key        => $privkey,
416        Armour     => 1,
417    ) || die $self->rsa->errstr();
418    my $cipher = new Crypt::CBC($symkey, 'DES');
419    my $xmldata = XMLin($cipher->decrypt_hex($data), ForceArray => 1);
420    $self->_register_ballot_clear($xmldata->{sbal}, $xmldata->{fsbal}, $xmldata->{id});
421    my $upd = $self->db->prepare_cached(q{update ballot_enc set decrypted = true where id = ?});
422    if ($upd->execute($ballotid)) {
423        $self->db->commit;
424        return;
425    } else {
426        $self->db->rollback;
427        return 1;
428    }
429}   
430
431sub decrypted_ballots {
432    my ($self, $password) = @_;
433    my $privkey = $self->private_key($password);
434    foreach ($self->list_ballot_need_dec) {
435        $self->_decrypted_ballot($_, $privkey);
436    }
437}
438
439sub register_ballot {
440    my ($self, $vid, $choice, $fchoice, $referal) = @_;
441
442    my $uid;
443    for (0..2) { # 3 try
444    # First we register voting has voted
445    $self->_register_signing($vid, $referal) or return; # TODO error ?
446
447    # registring choices
448    $uid = $self->_register_ballot($choice, $fchoice);
449    defined($uid) and last;
450
451    }
452    # everything went fine, saving!
453    $self->db->commit;
454
455   
456    $uid
457}
458
459sub voting_count {
460    my ($self) = @_;
461
462    my $sth = $self->db->prepare_cached(
463        q{
464        select count(*) from voting
465        where poll = ?
466        }
467    );
468    $sth->execute($self->voteid);
469    my $res = $sth->fetchrow_hashref;
470    $sth->finish;
471    $res->{count}
472}
473
474sub signing_count {
475    my ($self) = @_;
476
477    my $sth = $self->db->prepare_cached(
478        q{
479        select count(*) from signing join voting
480        on voting.key = signing.key where poll = ?
481        }
482    );
483
484    $sth->execute($self->voteid);
485    my $res = $sth->fetchrow_hashref;
486    $sth->finish;
487    $res->{count}
488}
489
490sub is_crypted {
491    my ($self) = @_;
492    return $self->info->{public_key} ? 1 : 0;
493}
494
495sub ballot_count {
496    my ($self) = @_;
497    return $self->is_crypted
498        ? $self->ballot_count_crypt
499        : $self->ballot_count_clear;
500}
501
502sub ballot_count_clear {
503    my ($self) = @_;
504
505    my $sth = $self->db->prepare_cached(
506        q{select count(*) from ballot where poll = ?}
507    );
508
509    $sth->execute($self->voteid);
510    my $res = $sth->fetchrow_hashref;
511    $sth->finish;
512    $res->{count}
513}
514
515sub ballot_count_crypt {
516    my ($self) = @_;
517
518    my $sth = $self->db->prepare_cached(
519        q{select count(*) from ballot_enc where poll = ?}
520    );
521
522    $sth->execute($self->voteid);
523    my $res = $sth->fetchrow_hashref;
524    $sth->finish;
525    $res->{count}
526}
527
528sub voting_info_id {
529    my ($self, $mail) = @_;
530
531    my $sth = $self->db->prepare_cached(
532        q{
533        select * from voting where mail = ? and poll = ?
534        }
535    );
536    $sth->execute($mail, $self->voteid);
537    my $res = $sth->fetchrow_hashref();
538    $sth->finish;
539    $res
540}
541
542sub auth_voting {
543    my ($self, $mail, $password) = @_;
544    my $userinfo = $self->voting_info_id($mail) or return;
545
546    $userinfo->{passwd} or return;
547    if (crypt($password, $userinfo->{passwd} || '') eq $userinfo->{passwd}) {
548        return 1;
549    } else {
550        return 0;
551    }
552}
553
554sub auth_poll {
555    my ($self, $passwd) = @_;
556
557    my $vinfo = $self->info or return;
558
559    $vinfo->{password} or return;
560    $passwd or return;
561    if (crypt($passwd, $vinfo->{password} || '') eq $vinfo->{password}) {
562        return 1;
563    } else {
564        return 0;
565    }
566}
567
568sub voting_has_sign {
569    my ($self, $user) = @_;
570    warn $user;
571    $self->voting_from_mail($user)->has_sign;
572}
573
574# Requete de decompte des voix:
575
576sub can_show_result {
577    my ($self) = @_;
578
579    # If ballot are encrypted, no
580    if ($self->list_ballot_need_dec) {
581        return;
582    }
583
584    return 1;
585}
586
587sub ballot {
588    my ($self, $id) = @_;
589
590    my $sth = $self->db->prepare_cached(
591        q{
592        select id from ballot where poll = ? and id = ?
593        }
594    );
595
596    $sth->execute($self->voteid, $id);
597    my $res = $sth->fetchrow_hashref;
598    $sth->finish;
599    return $res ? Vote::DB::Ballot->new($self->{dbstring}, $id) : undef;
600}
601
602sub list_ballot {
603    my ($self) = @_;
604
605    my $sth = $self->db->prepare_cached(
606        q{
607        select id from ballot where poll = ?
608        order by id
609        }
610    );
611    $sth->execute($self->voteid);
612    my @ids;
613    while (my $res = $sth->fetchrow_hashref) {
614        push(@ids, $res->{id});
615    }
616    @ids
617}
618
619sub list_ballot_enc {
620    my ($self) = @_;
621
622    my $sth = $self->db->prepare_cached(
623        q{
624        select id from ballot_enc where poll = ?
625        order by id
626        }
627    );
628    $sth->execute($self->voteid);
629    my @ids;
630    while (my $res = $sth->fetchrow_hashref) {
631        push(@ids, $res->{id});
632    }
633    @ids
634}
635
636sub list_ballot_need_dec {
637    my ($self) = @_;
638
639    my $sth = $self->db->prepare_cached(
640        q{
641        select id from ballot_enc where poll = ? and decrypted = 'false'
642        order by id
643        }
644    );
645    $sth->execute($self->voteid);
646    my @ids;
647    while (my $res = $sth->fetchrow_hashref) {
648        push(@ids, $res->{id});
649    }
650    @ids
651}
652
653sub list_ballot_needvalid {
654    my ($self) = @_;
655
656    my $sth = $self->db->prepare_cached(
657        q{
658        select id from ballot where poll = ?
659        and invalid is null order by id
660        }
661    );
662    $sth->execute($self->voteid);
663    my @ids;
664    while (my $res = $sth->fetchrow_hashref) {
665        push(@ids, $res->{id});
666    }
667    @ids
668}
669
670sub ballot_untrusted_values {
671    my ($self) = @_;
672
673    my $getval = $self->db->prepare_cached(
674        q{
675        select value from ballot join ballot_item
676        on ballot.id = ballot_item.id
677        where poll = ? and fromlist = false and corrected is null
678        group by value order by value
679        }
680    );
681    $getval->execute($self->voteid);
682    my @vals;
683    while (my $res = $getval->fetchrow_hashref) {
684        push(@vals, $res->{value});
685    }
686    @vals
687}
688
689sub ballot_values {
690    my ($self) = @_;
691
692    my $getval = $self->db->prepare_cached(
693        q{
694        select coalesce(corrected, value) as value from ballot join ballot_item
695        on ballot.id = ballot_item.id
696        where poll = ?
697        group by coalesce(corrected, value) order by coalesce(corrected, value)
698        }
699    );
700    $getval->execute($self->voteid);
701    my @vals;
702    while (my $res = $getval->fetchrow_hashref) {
703        push(@vals, $res->{value});
704    }
705    @vals
706}
707
708sub map_value {
709    my ($self, $from, $to) = @_;
710
711    my $sth = $self->db->prepare_cached(
712        q{
713        insert in ballot_map (poll, "from", to) values (?,?,?)
714        }
715    );
716
717    $sth->execute($self->voteid, $from, $to) or $self->db->rollback;
718    $self->db->commit;
719}
720
721sub addupd_voting {
722    my ($self, $mail, $id) = @_;
723
724    $mail =~ s/\s*$//;
725    $mail =~ s/^\s*//;
726    $mail = lc($mail);
727    $id =~ s/\s*$//;
728    $id =~ s/^\s//;
729    my $upd = $self->db->prepare_cached(
730        q{
731        update voting set label = ? where mail = ? and poll = ?
732        }
733    );
734
735    if ($upd->execute($id || '', $mail, $self->voteid) == 0) {
736        my $add = $self->db->prepare_cached(q{
737            insert into voting (poll, label, mail) values (?,?,?)
738        });
739
740        $add->execute($self->voteid, $id || '', $mail);
741    }
742}
743
744sub voting_from_file {
745    my ($self, $fh, $delete) = @_;
746
747    if ($delete) {
748        my $sth = $self->db->prepare(q{delete from voting where poll = ?});
749        $sth->execute($self->voteid);
750    }
751
752    while (my $line = <$fh>) {
753        chomp($line);
754        my ($mail, $name) = split(';', $line);
755        $mail or do {
756            $self->db->rollback;
757            return;
758        };
759        $self->addupd_voting($self->voteid, $mail, $name || '');
760    }
761    1;
762}
763
764sub list_voting_no_passwd {
765    my ($self) = @_;
766
767    my $list_voting = $self->db->prepare_cached(
768        q{select key from voting where poll = ? and passwd is null or passwd = ''}
769    );
770
771    $list_voting->execute($self->voteid);
772    my @ids;
773    while (my $res = $list_voting->fetchrow_hashref) {
774        push(@ids, $res->{key});
775    }
776    @ids
777}
778
779sub mail_voting_passwd {
780    my ($self, $id, $mailinfo) = @_;
781    $self->voting($id)->mail_voting_passwd($mailinfo); 
782}
783
784# crypto part
785
786sub rsa {
787    my ($self) = @_;
788    $self->{rsa} ||= new Crypt::RSA ES => 'PKCS1v15';
789}
790
791sub gen_poll_keys {
792    my ($self, $password) = @_;
793    my ($public, $private) = $self->rsa->keygen (
794        Identity  => 'Epoll Vote ' . $self->voteid,
795        Size      => 768,
796        Password  => $password,
797        Verbosity => 0,
798        KF=>'SSH',
799    ) or die $self->rsa->errstr(); # TODO avoid die
800    $self->param(
801        public_key => $public->serialize,
802        private_key => encode_base64($private->serialize),
803    );
804}
805
806sub public_key {
807    my ($self) = @_;
808    my $serialize = $self->info->{public_key} or return;
809    my $pubkey = Crypt::RSA::Key::Public::SSH->new;
810    $pubkey->deserialize(String => [ $serialize ]);
811    $pubkey
812}
813
814sub private_key {
815    my ($self, $password) = @_;
816    my $serialize = $self->info->{private_key} or return;
817    my $privkey = Crypt::RSA::Key::Private::SSH->new;
818    $privkey->deserialize(String => [ decode_base64($serialize) ], Passphrase => $password);
819    $privkey
820}
821=head1 AUTHOR
822
823Thauvin Olivier
824
825=head1 LICENSE
826
827This library is free software, you can redistribute it and/or modify
828it under the same terms as Perl itself or CeCILL.
829
830=cut
831
8321;
Note: See TracBrowser for help on using the repository browser.