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

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