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

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