From 50e7063dc9a19b00bebffae815b7e7317f4606e7 Mon Sep 17 00:00:00 2001 From: Ben Adida <ben@adida.net> Date: Mon, 17 Jan 2011 18:29:20 -0800 Subject: [PATCH] made legacy data work with new modular code. Specifically, fields are now serialized just in time for DB saving using the new modular serialization, and deserialized into proper core objects (algorithms or workflow objects) when loaded from DB --- helios/crypto/elgamal.py | 2 +- helios/datatypes/__init__.py | 80 ++++++++++++++++---------------- helios/datatypes/djangofield.py | 81 +++++++++++++++++++++++++++++++++ helios/datatypes/legacy.py | 44 +++++++++--------- helios/fixtures/election.json | 2 +- helios/models.py | 75 +++++++++++++----------------- helios/tests.py | 8 ++++ helios/views.py | 6 +-- 8 files changed, 186 insertions(+), 112 deletions(-) create mode 100644 helios/datatypes/djangofield.py diff --git a/helios/crypto/elgamal.py b/helios/crypto/elgamal.py index 0772f18..95e8e28 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 6c2195d..d3b5271 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 0000000..670eb59 --- /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 e1c0dd8..8084560 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 9833051..b219c0d 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 39d97ed..54aa59e 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 88235ac..d4e3f03 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 6fe1447..c71bf3c 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] -- GitLab