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

Last change on this file since 213 was 213, checked in by nanardon, 15 years ago
  • more tests and the fix need to have it passing
  • Property svn:keywords set to Id Rev
File size: 19.9 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 voting_count {
516    my ($self) = @_;
517
518    my $sth = $self->db->prepare_cached(
519        q{
520        select count(*) from voting
521        where poll = ?
522        }
523    );
524    $sth->execute($self->voteid);
525    my $res = $sth->fetchrow_hashref;
526    $sth->finish;
527    $res->{count}
528}
529
530sub signing_count {
531    my ($self) = @_;
532
533    my $sth = $self->db->prepare_cached(
534        q{
535        select count(*) from signing join voting
536        on voting.key = signing.key where poll = ?
537        }
538    );
539
540    $sth->execute($self->voteid);
541    my $res = $sth->fetchrow_hashref;
542    $sth->finish;
543    $res->{count}
544}
545
546sub is_crypted {
547    my ($self) = @_;
548    return $self->info->{public_key} ? 1 : 0;
549}
550
551sub ballot_count {
552    my ($self) = @_;
553    return $self->is_crypted
554        ? $self->ballot_count_crypt
555        : $self->ballot_count_clear;
556}
557
558sub ballot_count_clear {
559    my ($self) = @_;
560
561    my $sth = $self->db->prepare_cached(
562        q{select count(*) from ballot where poll = ?}
563    );
564
565    $sth->execute($self->voteid);
566    my $res = $sth->fetchrow_hashref;
567    $sth->finish;
568    $res->{count}
569}
570
571sub ballot_count_crypt {
572    my ($self) = @_;
573
574    my $sth = $self->db->prepare_cached(
575        q{select count(*) from ballot_enc where poll = ?}
576    );
577
578    $sth->execute($self->voteid);
579    my $res = $sth->fetchrow_hashref;
580    $sth->finish;
581    $res->{count}
582}
583
584sub voting_info_id {
585    my ($self, $mail) = @_;
586
587    my $sth = $self->db->prepare_cached(
588        q{
589        select * from voting where mail = ? and poll = ?
590        }
591    );
592    $sth->execute($mail, $self->voteid);
593    my $res = $sth->fetchrow_hashref();
594    $sth->finish;
595    $res
596}
597
598sub auth_voting {
599    my ($self, $mail, $password) = @_;
600    my $userinfo = $self->voting_info_id($mail) or return;
601
602    $userinfo->{passwd} or return;
603    if (crypt($password, $userinfo->{passwd} || '') eq $userinfo->{passwd}) {
604        return 1;
605    } else {
606        return 0;
607    }
608}
609
610sub auth_poll {
611    my ($self, $passwd) = @_;
612
613    my $vinfo = $self->info or return;
614
615    $vinfo->{password} or return;
616    $passwd or return;
617    if (crypt($passwd, $vinfo->{password} || '') eq $vinfo->{password}) {
618        return 1;
619    } else {
620        return 0;
621    }
622}
623
624sub voting_has_sign {
625    my ($self, $user) = @_;
626    $self->voting_from_mail($user)->has_sign;
627}
628
629# Requete de decompte des voix:
630
631sub can_show_result {
632    my ($self) = @_;
633
634    # If ballot are encrypted, no
635    if ($self->list_ballot_need_dec) {
636        return;
637    }
638
639    return 1;
640}
641
642sub ballot {
643    my ($self, $id) = @_;
644
645    my $sth = $self->db->prepare_cached(
646        q{
647        select id from ballot where poll = ? and id = ?
648        }
649    );
650
651    $sth->execute($self->voteid, $id);
652    my $res = $sth->fetchrow_hashref;
653    $sth->finish;
654    return $res ? Vote::DB::Ballot->new($self->{dbstring}, $id) : undef;
655}
656
657# TODO kill this:
658sub list_ballot {
659    ballot_keys(@_);
660}
661
662sub ballot_keys {
663    my ($self) = @_;
664
665    my $sth = $self->db->prepare_cached(
666        q{
667        select id from ballot where poll = ?
668        order by id
669        }
670    );
671    $sth->execute($self->voteid);
672    my @ids;
673    while (my $res = $sth->fetchrow_hashref) {
674        push(@ids, $res->{id});
675    }
676    @ids
677}
678
679sub list_ballot_enc {
680    my ($self) = @_;
681
682    my $sth = $self->db->prepare_cached(
683        q{
684        select id from ballot_enc where poll = ?
685        order by id
686        }
687    );
688    $sth->execute($self->voteid);
689    my @ids;
690    while (my $res = $sth->fetchrow_hashref) {
691        push(@ids, $res->{id});
692    }
693    @ids
694}
695
696sub list_ballot_need_dec {
697    my ($self) = @_;
698
699    my $sth = $self->db->prepare_cached(
700        q{
701        select id from ballot_enc where poll = ? and decrypted = 'false'
702        order by id
703        }
704    );
705    $sth->execute($self->voteid);
706    my @ids;
707    while (my $res = $sth->fetchrow_hashref) {
708        push(@ids, $res->{id});
709    }
710    @ids
711}
712
713sub list_ballot_needvalid {
714    my ($self) = @_;
715
716    my $sth = $self->db->prepare_cached(
717        q{
718        select id from ballot where poll = ?
719        and invalid is null order by id
720        }
721    );
722    $sth->execute($self->voteid);
723    my @ids;
724    while (my $res = $sth->fetchrow_hashref) {
725        push(@ids, $res->{id});
726    }
727    @ids
728}
729
730sub ballot_untrusted_values {
731    my ($self) = @_;
732
733    my $getval = $self->db->prepare_cached(
734        q{
735        select value from ballot join ballot_item
736        on ballot.id = ballot_item.id
737        where poll = ? and fromlist = false and corrected is null
738        group by value order by value
739        }
740    );
741    $getval->execute($self->voteid);
742    my @vals;
743    while (my $res = $getval->fetchrow_hashref) {
744        push(@vals, $res->{value});
745    }
746    @vals
747}
748
749sub ballot_values {
750    my ($self) = @_;
751
752    my $getval = $self->db->prepare_cached(
753        q{
754        select coalesce(corrected, value) as value from ballot join ballot_item
755        on ballot.id = ballot_item.id
756        where poll = ?
757        group by coalesce(corrected, value) order by coalesce(corrected, value)
758        }
759    );
760    $getval->execute($self->voteid);
761    my @vals;
762    while (my $res = $getval->fetchrow_hashref) {
763        push(@vals, $res->{value});
764    }
765    @vals
766}
767
768sub map_value {
769    my ($self, $from, $to) = @_;
770
771    my $sth = $self->db->prepare_cached(
772        q{
773        insert into ballot_map (poll, "from", "to") values (?,?,?)
774        }
775    );
776
777    $sth->execute($self->voteid, $from, $to) or $self->rollback;
778    $self->commit;
779}
780
781sub addupd_voting {
782    my ($self, $mail, $id) = @_;
783
784    $mail =~ s/\s*$//;
785    $mail =~ s/^\s*//;
786    $mail = lc($mail);
787    $id ||= '';
788    $id =~ s/\s*$//;
789    $id =~ s/^\s//;
790    my $upd = $self->db->prepare_cached(
791        q{
792        update voting set label = ? where mail = ? and poll = ?
793        }
794    );
795
796    if ($upd->execute($id, $mail, $self->voteid) == 0) {
797        my $add = $self->db->prepare_cached(q{
798            insert into voting (poll, label, mail) values (?,?,?)
799        });
800
801        $add->execute($self->voteid, $id || '', $mail);
802    }
803}
804
805sub voting_from_file {
806    my ($self, $fh, $delete) = @_;
807
808    if ($delete) {
809        my $sth = $self->db->prepare(q{delete from voting where poll = ?});
810        $sth->execute($self->voteid);
811    }
812
813    while (my $line = <$fh>) {
814        chomp($line);
815        my ($mail, $name) = split(';', $line);
816        $mail or do {
817            $self->rollback;
818            return;
819        };
820        $self->addupd_voting($self->voteid, $mail, $name || '');
821    }
822    1;
823}
824
825sub delete_voting {
826    my ($self, $key) = @_;
827    $self->voting($key)->has_sign and return;
828    my $sth = $self->db->prepare_cached(
829        q{delete from voting where key = ? and poll = ?}
830    );
831
832    $sth->execute($key, $self->voteid);
833}
834
835sub list_voting_no_passwd {
836    my ($self) = @_;
837
838    my $list_voting = $self->db->prepare_cached(
839        q{select key from voting where poll = ? and passwd is null or passwd = ''}
840    );
841
842    $list_voting->execute($self->voteid);
843    my @ids;
844    while (my $res = $list_voting->fetchrow_hashref) {
845        push(@ids, $res->{key});
846    }
847    @ids
848}
849
850sub mail_voting_passwd {
851    my ($self, $id, $mailinfo) = @_;
852    $self->voting($id)->mail_voting_passwd($mailinfo); 
853}
854
855# crypto part
856
857sub rsa {
858    my ($self) = @_;
859    $self->{rsa} ||= new Crypt::RSA ES => 'PKCS1v15';
860}
861
862sub gen_poll_keys {
863    my ($self, $password) = @_;
864    my ($public, $private) = $self->rsa->keygen (
865        Identity  => 'Epoll Vote ' . $self->voteid,
866        Size      => 768,
867        Password  => $password,
868        Verbosity => 0,
869        KF=>'SSH',
870    ) or die $self->rsa->errstr(); # TODO avoid die
871    $self->param(
872        public_key => $public->serialize,
873        private_key => encode_base64($private->serialize),
874    );
875}
876
877sub public_key {
878    my ($self) = @_;
879    my $serialize = $self->info->{public_key} or return;
880    my $pubkey = Crypt::RSA::Key::Public::SSH->new;
881    $pubkey->deserialize(String => [ $serialize ]);
882    $pubkey
883}
884
885sub private_key {
886    my ($self, $password) = @_;
887    my $serialize = $self->info->{private_key} or return;
888    my $privkey = Crypt::RSA::Key::Private::SSH->new;
889    $privkey->deserialize(String => [ decode_base64($serialize) ], Passphrase => $password);
890    $privkey
891}
892=head1 AUTHOR
893
894Thauvin Olivier
895
896=head1 LICENSE
897
898This library is free software, you can redistribute it and/or modify
899it under the same terms as Perl itself or CeCILL.
900
901=cut
902
9031;
Note: See TracBrowser for help on using the repository browser.