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

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