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

Last change on this file since 208 was 208, checked in by nanardon, 15 years ago
  • poll uid is password like
  • 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 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->prepare_cached(
398        q{select key from choice where lower(label) = ? and poll = ?}
399    );
400    $sth->execute(lc($value), $self->pollid);
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   
513    $uid
514}
515
516sub voting_count {
517    my ($self) = @_;
518
519    my $sth = $self->db->prepare_cached(
520        q{
521        select count(*) from voting
522        where poll = ?
523        }
524    );
525    $sth->execute($self->voteid);
526    my $res = $sth->fetchrow_hashref;
527    $sth->finish;
528    $res->{count}
529}
530
531sub signing_count {
532    my ($self) = @_;
533
534    my $sth = $self->db->prepare_cached(
535        q{
536        select count(*) from signing join voting
537        on voting.key = signing.key where poll = ?
538        }
539    );
540
541    $sth->execute($self->voteid);
542    my $res = $sth->fetchrow_hashref;
543    $sth->finish;
544    $res->{count}
545}
546
547sub is_crypted {
548    my ($self) = @_;
549    return $self->info->{public_key} ? 1 : 0;
550}
551
552sub ballot_count {
553    my ($self) = @_;
554    return $self->is_crypted
555        ? $self->ballot_count_crypt
556        : $self->ballot_count_clear;
557}
558
559sub ballot_count_clear {
560    my ($self) = @_;
561
562    my $sth = $self->db->prepare_cached(
563        q{select count(*) from ballot where poll = ?}
564    );
565
566    $sth->execute($self->voteid);
567    my $res = $sth->fetchrow_hashref;
568    $sth->finish;
569    $res->{count}
570}
571
572sub ballot_count_crypt {
573    my ($self) = @_;
574
575    my $sth = $self->db->prepare_cached(
576        q{select count(*) from ballot_enc where poll = ?}
577    );
578
579    $sth->execute($self->voteid);
580    my $res = $sth->fetchrow_hashref;
581    $sth->finish;
582    $res->{count}
583}
584
585sub voting_info_id {
586    my ($self, $mail) = @_;
587
588    my $sth = $self->db->prepare_cached(
589        q{
590        select * from voting where mail = ? and poll = ?
591        }
592    );
593    $sth->execute($mail, $self->voteid);
594    my $res = $sth->fetchrow_hashref();
595    $sth->finish;
596    $res
597}
598
599sub auth_voting {
600    my ($self, $mail, $password) = @_;
601    my $userinfo = $self->voting_info_id($mail) or return;
602
603    $userinfo->{passwd} or return;
604    if (crypt($password, $userinfo->{passwd} || '') eq $userinfo->{passwd}) {
605        return 1;
606    } else {
607        return 0;
608    }
609}
610
611sub auth_poll {
612    my ($self, $passwd) = @_;
613
614    my $vinfo = $self->info or return;
615
616    $vinfo->{password} or return;
617    $passwd or return;
618    if (crypt($passwd, $vinfo->{password} || '') eq $vinfo->{password}) {
619        return 1;
620    } else {
621        return 0;
622    }
623}
624
625sub voting_has_sign {
626    my ($self, $user) = @_;
627    $self->voting_from_mail($user)->has_sign;
628}
629
630# Requete de decompte des voix:
631
632sub can_show_result {
633    my ($self) = @_;
634
635    # If ballot are encrypted, no
636    if ($self->list_ballot_need_dec) {
637        return;
638    }
639
640    return 1;
641}
642
643sub ballot {
644    my ($self, $id) = @_;
645
646    my $sth = $self->db->prepare_cached(
647        q{
648        select id from ballot where poll = ? and id = ?
649        }
650    );
651
652    $sth->execute($self->voteid, $id);
653    my $res = $sth->fetchrow_hashref;
654    $sth->finish;
655    return $res ? Vote::DB::Ballot->new($self->{dbstring}, $id) : undef;
656}
657
658# TODO kill this:
659sub list_ballot {
660    ballot_keys(@_);
661}
662
663sub ballot_keys {
664    my ($self) = @_;
665
666    my $sth = $self->db->prepare_cached(
667        q{
668        select id from ballot where poll = ?
669        order by id
670        }
671    );
672    $sth->execute($self->voteid);
673    my @ids;
674    while (my $res = $sth->fetchrow_hashref) {
675        push(@ids, $res->{id});
676    }
677    @ids
678}
679
680sub list_ballot_enc {
681    my ($self) = @_;
682
683    my $sth = $self->db->prepare_cached(
684        q{
685        select id from ballot_enc where poll = ?
686        order by id
687        }
688    );
689    $sth->execute($self->voteid);
690    my @ids;
691    while (my $res = $sth->fetchrow_hashref) {
692        push(@ids, $res->{id});
693    }
694    @ids
695}
696
697sub list_ballot_need_dec {
698    my ($self) = @_;
699
700    my $sth = $self->db->prepare_cached(
701        q{
702        select id from ballot_enc where poll = ? and decrypted = 'false'
703        order by id
704        }
705    );
706    $sth->execute($self->voteid);
707    my @ids;
708    while (my $res = $sth->fetchrow_hashref) {
709        push(@ids, $res->{id});
710    }
711    @ids
712}
713
714sub list_ballot_needvalid {
715    my ($self) = @_;
716
717    my $sth = $self->db->prepare_cached(
718        q{
719        select id from ballot where poll = ?
720        and invalid is null order by id
721        }
722    );
723    $sth->execute($self->voteid);
724    my @ids;
725    while (my $res = $sth->fetchrow_hashref) {
726        push(@ids, $res->{id});
727    }
728    @ids
729}
730
731sub ballot_untrusted_values {
732    my ($self) = @_;
733
734    my $getval = $self->db->prepare_cached(
735        q{
736        select value from ballot join ballot_item
737        on ballot.id = ballot_item.id
738        where poll = ? and fromlist = false and corrected is null
739        group by value order by value
740        }
741    );
742    $getval->execute($self->voteid);
743    my @vals;
744    while (my $res = $getval->fetchrow_hashref) {
745        push(@vals, $res->{value});
746    }
747    @vals
748}
749
750sub ballot_values {
751    my ($self) = @_;
752
753    my $getval = $self->db->prepare_cached(
754        q{
755        select coalesce(corrected, value) as value from ballot join ballot_item
756        on ballot.id = ballot_item.id
757        where poll = ?
758        group by coalesce(corrected, value) order by coalesce(corrected, value)
759        }
760    );
761    $getval->execute($self->voteid);
762    my @vals;
763    while (my $res = $getval->fetchrow_hashref) {
764        push(@vals, $res->{value});
765    }
766    @vals
767}
768
769sub map_value {
770    my ($self, $from, $to) = @_;
771
772    my $sth = $self->db->prepare_cached(
773        q{
774        insert in ballot_map (poll, "from", to) values (?,?,?)
775        }
776    );
777
778    $sth->execute($self->voteid, $from, $to) or $self->rollback;
779    $self->commit;
780}
781
782sub addupd_voting {
783    my ($self, $mail, $id) = @_;
784
785    $mail =~ s/\s*$//;
786    $mail =~ s/^\s*//;
787    $mail = lc($mail);
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.