diff --git a/helios/crypto/elgamal.py b/helios/crypto/elgamal.py index 0772f18515718e388f8fbe8a3b90905f7e9df6c5..95e8e28de3d596a859f3c576880b6f341c5acf94 100644 --- a/helios/crypto/elgamal.py +++ b/helios/crypto/elgamal.py @@ -510,7 +510,7 @@ class ZKDisjunctiveProof: self.proofs = proofs class DLogProof(object): - def __init__(self, commitment, challenge, response): + def __init__(self, commitment=None, challenge=None, response=None): self.commitment = commitment self.challenge = challenge self.response = response diff --git a/helios/datatypes/__init__.py b/helios/datatypes/__init__.py index 6c2195da64456f51c7348346c612ec9673624821..d3b5271ef918e7baefc4758b1617babd41964742 100644 --- a/helios/datatypes/__init__.py +++ b/helios/datatypes/__init__.py @@ -33,7 +33,7 @@ from helios.crypto import utils as cryptoutils ## utility function ## def recursiveToDict(obj): - if not obj: + if obj == None: return None if type(obj) == list: @@ -118,6 +118,7 @@ class LDObject(object): @classmethod def instantiate(cls, obj, datatype=None): + "FIXME: should datatype override the object's internal datatype? probably not" if isinstance(obj, LDObject): return obj @@ -128,47 +129,30 @@ class LDObject(object): raise Exception("no datatype found") # nulls - if not obj: + if obj == None: return None # the class dynamic_cls = get_class(datatype) - # instantiate it + # instantiate it and load data return_obj = dynamic_cls(obj) - - # go through the subfields and instantiate them too - for subfield_name, subfield_type in dynamic_cls.STRUCTURED_FIELDS.iteritems(): - try: - return_obj.structured_fields[subfield_name] = cls.instantiate(getattr(return_obj.wrapped_obj, subfield_name), datatype = subfield_type) - except: - import pdb; pdb.set_trace() + return_obj.loadData() return return_obj - def set_from_args(self, **kwargs): - for f in self.FIELDS: - if kwargs.has_key(f): - new_val = self.process_value_in(f, kwargs[f]) - setattr(self.wrapped_obj, f, new_val) - else: - setattr(self.wrapped_obj, f, None) - - def serialize(self): - return utils.to_json(self.toDict()) - - def toDict(self, alternate_fields=None): - val = {} - for f in (alternate_fields or self.FIELDS): - # is it a structured subfield? - if self.structured_fields.has_key(f): - val[f] = recursiveToDict(self.structured_fields[f]) - else: - val[f] = self.process_value_out(f, getattr(self.wrapped_obj, f)) - return val + def _getattr_wrapped(self, attr): + return getattr(self.wrapped_obj, attr) - toJSONDict = toDict + def _setattr_wrapped(self, attr, val): + setattr(self.wrapped_obj, attr, val) + def loadData(self): + "load data using from the wrapped object" + # go through the subfields and instantiate them too + for subfield_name, subfield_type in self.STRUCTURED_FIELDS.iteritems(): + self.structured_fields[subfield_name] = self.instantiate(self._getattr_wrapped(subfield_name), datatype = subfield_type) + def loadDataFromDict(self, d): """ load data from a dictionary @@ -182,23 +166,31 @@ class LDObject(object): for f in self.FIELDS: if f in structured_fields: # a structured ld field, recur - try: - sub_ld_object = self.fromDict(d[f], type_hint = self.STRUCTURED_FIELDS[f]) - except KeyError: - import pdb; pdb.set_trace() - + sub_ld_object = self.fromDict(d[f], type_hint = self.STRUCTURED_FIELDS[f]) self.structured_fields[f] = sub_ld_object # set the field on the wrapped object too - try: - setattr(self.wrapped_obj, f, sub_ld_object.wrapped_obj) - except AttributeError: - import pdb; pdb.set_trace() + self._setattr_wrapped(f, sub_ld_object.wrapped_obj) else: # a simple type new_val = self.process_value_in(f, d[f]) - setattr(self.wrapped_obj, f, new_val) + self._setattr_wrapped(f, new_val) + def serialize(self): + return utils.to_json(self.toDict()) + + def toDict(self, alternate_fields=None): + val = {} + for f in (alternate_fields or self.FIELDS): + # is it a structured subfield? + if self.structured_fields.has_key(f): + val[f] = recursiveToDict(self.structured_fields[f]) + else: + val[f] = self.process_value_out(f, self._getattr_wrapped(f)) + return val + + toJSONDict = toDict + @classmethod def fromDict(cls, d, type_hint=None): # the LD type is either in d or in type_hint @@ -275,16 +267,20 @@ class BaseArrayOfObjects(LDObject): WRAPPED_OBJ_CLASS = list def __init__(self, wrapped_obj): - self.items = [] super(BaseArrayOfObjects, self).__init__(wrapped_obj) def toDict(self): return [item.toDict() for item in self.items] + def loadData(self): + "go through each item and LD instantiate it, as if it were a structured field" + self.items = [self.instantiate(element, datatype= self.ELEMENT_TYPE) for element in self.wrapped_obj] + def loadDataFromDict(self, d): "assumes that d is a list" # TODO: should we be using ELEMENT_TYPE_CLASS here instead of LDObject? self.items = [LDObject.fromDict(element, type_hint = self.ELEMENT_TYPE) for element in d] + self.wrapped_obj = [item.wrapped_obj for item in self.items] def arrayOf(element_type): diff --git a/helios/datatypes/djangofield.py b/helios/datatypes/djangofield.py new file mode 100644 index 0000000000000000000000000000000000000000..670eb59c345c01d73f67a0184c506ad7c1cc9e4b --- /dev/null +++ b/helios/datatypes/djangofield.py @@ -0,0 +1,81 @@ +""" +taken from + +http://www.djangosnippets.org/snippets/377/ + +and adapted to LDObject +""" + +import datetime +from django.db import models +from django.db.models import signals +from django.conf import settings +from django.utils import simplejson as json +from django.core.serializers.json import DjangoJSONEncoder + +from . import LDObject + +class LDObjectField(models.TextField): + """ + LDObject is a generic textfield that neatly serializes/unserializes + JSON objects seamlessly. + + deserialization_params added on 2011-01-09 to provide additional hints at deserialization time + """ + + # Used so to_python() is called + __metaclass__ = models.SubfieldBase + + def __init__(self, type_hint=None, **kwargs): + self.type_hint = type_hint + super(LDObjectField, self).__init__(**kwargs) + + def to_python(self, value): + """Convert our string value to LDObject after we load it from the DB""" + + # did we already convert this? + if not isinstance(value, basestring): + return value + + if value == None: + return None + + # in some cases, we're loading an existing array or dict, + # we skip this part but instantiate the LD object + if isinstance(value, basestring): + try: + parsed_value = json.loads(value) + except: + raise Exception("value is not JSON parseable, that's bad news") + else: + parsed_value = value + + if parsed_value != None: + "we give the wrapped object back because we're not dealing with serialization types" + return_val = LDObject.fromDict(parsed_value, type_hint = self.type_hint).wrapped_obj + return return_val + else: + return None + + def get_prep_value(self, value): + """Convert our JSON object to a string before we save""" + if isinstance(value, basestring): + return value + + if value == None: + return None + + # instantiate the proper LDObject to dump it appropriately + ld_object = LDObject.instantiate(value, datatype=self.type_hint) + return ld_object.serialize() + + def value_to_string(self, obj): + value = self._get_val_from_obj(obj) + return self.get_db_prep_value(value) + +## +## for schema migration, we have to tell South about JSONField +## basically that it's the same as its parent class +## +from south.modelsinspector import add_introspection_rules +add_introspection_rules([], ["^helios\.datatypes\.djangofield.LDObjectField"]) diff --git a/helios/datatypes/legacy.py b/helios/datatypes/legacy.py index e1c0dd89e118835790b949c109f0f349416eb956..8084560e1bde7eb78bc3f613c61270ea0c081094 100644 --- a/helios/datatypes/legacy.py +++ b/helios/datatypes/legacy.py @@ -6,18 +6,6 @@ from helios.datatypes import LDObject, arrayOf from helios.crypto import elgamal as crypto_elgamal from helios.workflows import homomorphic -## -## utilities - -class DictObject(object): - def __init__(self, d=None): - self.d = d - if not self.d: - self.d = {} - - def __getattr__(self, k): - return self.d[k] - ## ## @@ -25,7 +13,16 @@ class LegacyObject(LDObject): WRAPPED_OBJ_CLASS = dict USE_JSON_LD = False +class DictObject(object): + "when the wrapped object is actually dictionary" + def _getattr_wrapped(self, attr): + return self.wrapped_obj[attr] + + def _setattr_wrapped(self, attr, val): + self.wrapped_obj[attr] = val + class Election(LegacyObject): + WRAPPED_OBJ_CLASS = homomorphic.Election FIELDS = ['uuid', 'questions', 'name', 'short_name', 'description', 'voters_hash', 'openreg', 'frozen_at', 'public_key', 'cast_url', 'use_voter_aliases', 'voting_starts_at', 'voting_ends_at'] @@ -96,7 +93,7 @@ class Trustee(LegacyObject): 'public_key' : 'legacy/EGPublicKey', 'pok': 'legacy/DLogProof', 'decryption_factors': arrayOf(arrayOf('core/BigInteger')), - 'decryption_proofs' : arrayOf(arrayOf('legacy/DLogProof'))} + 'decryption_proofs' : arrayOf(arrayOf('legacy/EGZKProof'))} class EGPublicKey(LegacyObject): WRAPPED_OBJ_CLASS = crypto_elgamal.PublicKey @@ -121,14 +118,12 @@ class EGCiphertext(LegacyObject): 'alpha': 'core/BigInteger', 'beta' : 'core/BigInteger'} -class EGZKProofCommitment(LegacyObject): +class EGZKProofCommitment(DictObject, LegacyObject): FIELDS = ['A', 'B'] STRUCTURED_FIELDS = { 'A' : 'core/BigInteger', 'B' : 'core/BigInteger'} - def __init__(self, wrapped_obj): - super(EGZKProofCommitment, self).__init__(DictObject(wrapped_obj)) class EGZKProof(LegacyObject): WRAPPED_OBJ_CLASS = crypto_elgamal.ZKProof @@ -153,6 +148,7 @@ class EGZKDisjunctiveProof(LegacyObject): return super(EGZKDisjunctiveProof, self).toDict()['proofs'] class DLogProof(LegacyObject): + WRAPPED_OBJ_CLASS = crypto_elgamal.DLogProof FIELDS = ['commitment', 'challenge', 'response'] STRUCTURED_FIELDS = { 'commitment' : 'core/BigInteger', @@ -160,10 +156,10 @@ class DLogProof(LegacyObject): 'response' : 'core/BigInteger'} def __init__(self, wrapped_obj): - if type(wrapped_obj) == dict: - super(DLogProof, self).__init__(DictObject(wrapped_obj)) - else: - super(DLogProof, self).__init__(wrapped_obj) + if isinstance(wrapped_obj, dict): + import pdb; pdb.set_trace() + + super(DLogProof,self).__init__(wrapped_obj) class Result(LegacyObject): pass @@ -171,8 +167,12 @@ class Result(LegacyObject): class Questions(LegacyObject): WRAPPED_OBJ = list - def __len__(self): - return len(self.wrapped_obj) + def loadDataFromDict(self, d): + self.wrapped_obj = d + + def toDict(self): + return self.wrapped_obj + class Tally(LegacyObject): pass diff --git a/helios/fixtures/election.json b/helios/fixtures/election.json index 98330512f56a3aa8a30c298a479e8a0142032161..b219c0d4b7014f779db596e008cfbdf432ff583f 100644 --- a/helios/fixtures/election.json +++ b/helios/fixtures/election.json @@ -9,7 +9,7 @@ "election_type" : "election", "use_advanced_audit_features" : true, "private_p" : false, - "description" : "test descriptoin", + "description" : "test description", "public_key" : null, "private_key" : null, "questions" : [], diff --git a/helios/models.py b/helios/models.py index 39d97ed58c79f0e0ae5bbb066a312c4799e2cbf3..54aa59e9ba8cbf4b410ba3fdb9f001fd9bf3085e 100644 --- a/helios/models.py +++ b/helios/models.py @@ -22,6 +22,7 @@ from helios import datatypes # useful stuff in auth from auth.models import User, AUTH_SYSTEMS from auth.jsonfield import JSONField +from helios.datatypes.djangofield import LDObjectField import csv, copy @@ -57,22 +58,18 @@ class Election(HeliosModel): private_p = models.BooleanField(default=False, null=False) description = models.TextField() - public_key = JSONField(datatypes.LDObject, - deserialization_params = {'type_hint' : 'legacy/EGPublicKey'}, - null=True) - private_key = JSONField(datatypes.LDObject, - deserialization_params = {'type_hint' : 'legacy/EGSecretKey'}, - null=True) + public_key = LDObjectField(type_hint = 'legacy/EGPublicKey', + null=True) + private_key = LDObjectField(type_hint = 'legacy/EGSecretKey', + null=True) - questions = JSONField(datatypes.LDObject, - deserialization_params = {'type_hint' : 'legacy/Questions'}, - null=True) + questions = LDObjectField(type_hint = 'legacy/Questions', + null=True) # eligibility is a JSON field, which lists auth_systems and eligibility details for that auth_system, e.g. # [{'auth_system': 'cas', 'constraint': [{'year': 'u12'}, {'year':'u13'}]}, {'auth_system' : 'password'}, {'auth_system' : 'openid', 'constraint': [{'host':'http://myopenid.com'}]}] - eligibility = JSONField(datatypes.LDObject, - deserialization_params = {'type_hint' : 'legacy/Eligibility'}, - null=True) + eligibility = LDObjectField(type_hint = 'legacy/Eligibility', + null=True) # open registration? # this is now used to indicate the state of registration, @@ -124,14 +121,12 @@ class Election(HeliosModel): # encrypted tally, each a JSON string # used only for homomorphic tallies - encrypted_tally = JSONField(datatypes.LDObject, - deserialization_params={'type_hint': 'legacy/Tally'}, - null=True) + encrypted_tally = LDObjectField(type_hint = 'legacy/Tally', + null=True) # results of the election - result = JSONField(datatypes.LDObject, - deserialization_params = {'type_hint' : 'legacy/Result'}, - null=True) + result = LDObjectField(type_hint = 'legacy/Result', + null=True) # decryption proof, a JSON object # no longer needed since it's all trustees @@ -409,12 +404,13 @@ class Election(HeliosModel): trustee.uuid = str(uuid.uuid4()) trustee.name = settings.DEFAULT_FROM_NAME trustee.email = settings.DEFAULT_FROM_EMAIL - trustee.public_key = datatypes.LDObject.instantiate(keypair.pk, 'pkc/elgamal/PublicKey') - trustee.secret_key = datatypes.LDObject.instantiate(keypair.sk, 'pkc/elgamal/SecretKey') + trustee.public_key = keypair.pk + trustee.secret_key = keypair.sk - # FIXME: compute it - trustee.public_key_hash = utils.hash_b64(trustee.public_key.serialize()) - trustee.pok = datatypes.LDObject.instantiate(trustee.secret_key.wrapped_obj.prove_sk(algs.DLog_challenge_generator), 'pkc/elgamal/DLogProof') + # FIXME: is this at the right level of abstraction? + trustee.public_key_hash = datatypes.LDObject.instantiate(trustee.public_key, datatype='legacy/EGPublicKey').hash + + trustee.pok = trustee.secret_key.prove_sk(algs.DLog_challenge_generator) trustee.save() @@ -599,9 +595,8 @@ class Voter(HeliosModel): alias = models.CharField(max_length = 100, null=True) # we keep a copy here for easy tallying - vote = JSONField(datatypes.LDObject, - deserialization_params = {'type_hint': 'legacy/EncryptedVote'}, - null=True) + vote = LDObjectField(type_hint = 'legacy/EncryptedVote', + null=True) vote_hash = models.CharField(max_length = 100, null=True) cast_at = models.DateTimeField(auto_now_add=False, null=True) @@ -762,8 +757,7 @@ class CastVote(HeliosModel): voter = models.ForeignKey(Voter) # the actual encrypted vote - vote = JSONField(datatypes.LDObject, - deserialization_params = {'type_hint' : 'legacy/EncryptedVote'}) + vote = LDObjectField(type_hint = 'legacy/EncryptedVote') # cache the hash of the vote vote_hash = models.CharField(max_length=100) @@ -883,31 +877,26 @@ class Trustee(HeliosModel): secret = models.CharField(max_length=100) # public key - public_key = JSONField(datatypes.LDObject, - deserialization_params = {'type_hint': 'legacy/EGPublicKey'}, - null=True) + public_key = LDObjectField(type_hint = 'legacy/EGPublicKey', + null=True) public_key_hash = models.CharField(max_length=100) # secret key # if the secret key is present, this means # Helios is playing the role of the trustee. - secret_key = JSONField(datatypes.LDObject, - deserialization_params = {'type_hint': 'legacy/EGSecretKey'}, - null=True) + secret_key = LDObjectField(type_hint = 'legacy/EGSecretKey', + null=True) # proof of knowledge of secret key - pok = JSONField(datatypes.LDObject, - deserialization_params = {'type_hint': 'legacy/DLogProof'}, - null=True) + pok = LDObjectField(type_hint = 'legacy/DLogProof', + null=True) # decryption factors - decryption_factors = JSONField(datatypes.LDObject, - deserialization_params = {'type_hint' : datatypes.arrayOf(datatypes.arrayOf('core/BigInteger'))}, - null=True) + decryption_factors = LDObjectField(type_hint = datatypes.arrayOf(datatypes.arrayOf('core/BigInteger')), + null=True) - decryption_proofs = JSONField(datatypes.LDObject, - deserialization_params = {'type_hint' : datatypes.arrayOf(datatypes.arrayOf('legacy/EGZKProof'))}, - null=True) + decryption_proofs = LDObjectField(type_hint = datatypes.arrayOf(datatypes.arrayOf('legacy/EGZKProof')), + null=True) def save(self, *args, **kwargs): """ diff --git a/helios/tests.py b/helios/tests.py index 88235accac67f558e9b74aebac5b23ab102eb419..d4e3f03a3acd499742b00278c7dc58da04a64ce2 100644 --- a/helios/tests.py +++ b/helios/tests.py @@ -233,6 +233,14 @@ class DatatypeTests(TestCase): 'p' : '23434', 'g' : '2343243242', 'q' : '2343242343434'}, type_hint = 'pkc/elgamal/PublicKey') + + def test_dictobject_from_dict(self): + original_dict = { + 'A' : '35423432', + 'B' : '234324243'} + ld_obj = datatypes.LDObject.fromDict(original_dict, type_hint = 'legacy/EGZKProofCommitment') + + self.assertEquals(original_dict, ld_obj.toDict()) diff --git a/helios/views.py b/helios/views.py index 6fe14473b75b0da19d069051260a8380ff749d4d..c71bf3c425feff89db00003ad0b21588b95974d9 100644 --- a/helios/views.py +++ b/helios/views.py @@ -319,7 +319,7 @@ def socialbuttons(request): @election_view() def list_trustees(request, election): trustees = Trustee.get_by_election(election) - return [t.toJSONDict() for t in trustees] + return [datatypes.LDObject.instantiate(t, 'legacy/Trustee').toDict() for t in trustees] @election_view() def list_trustees_view(request, election): @@ -802,7 +802,7 @@ def one_election_register(request, election): def one_election_save_questions(request, election): check_csrf(request) - election.questions = datatypes.LDObject.instantiate(utils.from_json(request.POST['questions_json']), 'legacy/Questions'); + election.questions = utils.from_json(request.POST['questions_json']) election.save() # always a machine API @@ -1172,7 +1172,7 @@ def ballot_list(request, election): after = datetime.datetime.strptime(request.GET['after'], '%Y-%m-%d %H:%M:%S') voters = Voter.get_by_election(election, cast=True, order_by='cast_at', limit=limit, after=after) - return [v.last_cast_vote().toJSONDict() for v in voters] + return [datatypes.LDObject.instantiate(v.last_cast_vote(), 'legacy/ShortCastVote').toDict() for v in voters]