class DBMessage(db.Entity): type = orm.Discriminator(MSG_TYPE_TYPE) date = orm.Required(datetime, sql_default='CURRENT_TIMESTAMP') sequence_no = orm.Required(SEQUENCE_TYPE) node = orm.Optional(NODE_TYPE) value = orm.Optional(VALUE_TYPE) leader = orm.Optional(NODE_TYPE) sender = orm.Optional(NODE_TYPE) raw = orm.Optional(orm.Json) _discriminator_ = UNSET def from_db(self): clazz = MSG_TYPE_CLASS_MAP[self.type] assert issubclass(clazz, messages.Message) return clazz.from_dict(self.as_dict()) # end def @classmethod def to_db(cls, msg): return cls(**msg.to_dict())
class Parent(db.Entity): _discriminator_ = 1 type_field = orm.Discriminator(int) field = orm.Required(str) orm.composite_index(type_field, field)
class ChannelNode(db.Entity): """ This is the base class of our ORM bindings. It implements methods for signing and serialization of ORM objects. All other GigaChannel-related ORM classes are derived from it. It is not intended for direct use. Instead, other classes should derive from it. """ _discriminator_ = CHANNEL_NODE rowid = orm.PrimaryKey(int, size=64, auto=True) # Serializable metadata_type = orm.Discriminator(int, size=16, index=True) reserved_flags = orm.Optional(int, size=16, default=0) origin_id = orm.Optional(int, size=64, default=0, index=True) public_key = orm.Required(database_blob, index=True) id_ = orm.Required(int, size=64) orm.composite_key(public_key, id_) orm.composite_index(public_key, origin_id) timestamp = orm.Required(int, size=64, default=0) # Signature is nullable. This means that "None" entries are stored in DB as NULLs instead of empty strings. # NULLs are not checked for uniqueness and not indexed. # This is necessary to store unsigned signatures without violating the uniqueness constraints. signature = orm.Optional(database_blob, unique=True, nullable=True, default=None) # Local added_on = orm.Optional(datetime, default=datetime.utcnow) status = orm.Optional(int, default=COMMITTED, index=True) # Special class-level properties _payload_class = ChannelNodePayload _my_key = key _logger = logger # This attribute holds the names of the class attributes that are used by the serializer for the # corresponding payload type. We only initialize it once on class creation as an optimization. payload_arguments = _payload_class.__init__.__code__.co_varnames[: _payload_class . __init__ . __code__ . co_argcount][ 1:] # A non - personal attribute of an entry is an attribute that would have the same value regardless of where, # when and who created the entry. # In other words, it does not depend on the Tribler instance that created it. # ACHTUNG! On object creation, Pony does not check if discriminator is wrong for the created ORM type! nonpersonal_attributes = ('metadata_type', ) def __init__(self, *args, **kwargs): """ Initialize a metadata object. All this dance is required to ensure that the signature is there and it is correct. """ skip_key_check = False # FIXME: refactor this method by moving different ways to create an entry into separate methods # Process special keyworded arguments # "sign_with" argument given, sign with it private_key_override = None if "sign_with" in kwargs: kwargs["public_key"] = database_blob( kwargs["sign_with"].pub().key_to_bin()[10:]) private_key_override = kwargs.pop("sign_with") # Free-for-all entries require special treatment if "public_key" in kwargs and kwargs["public_key"] == b"": # We have to give the entry an unique sig to honor the DB constraints. We use the entry's id_ # as the sig to keep it unique and short. The uniqueness is guaranteed by DB as it already # imposes uniqueness constraints on the id_+public_key combination. if "id_" in kwargs: kwargs["signature"] = None skip_key_check = True else: # Trying to create an FFA entry without specifying the id_ should be considered an error, # because assigning id_ automatically by clock breaks anonymity. # FFA entries should be "timeless" and anonymous. raise InvalidChannelNodeException( "Attempted to create %s free-for-all (unsigned) object without specifying id_ : " % str(self.__class__.__name__)) # For putting legacy/test stuff in skip_key_check = kwargs.pop("skip_key_check", skip_key_check) if "timestamp" not in kwargs: kwargs["timestamp"] = clock.tick() if "id_" not in kwargs: kwargs["id_"] = int(random.getrandbits(63)) if not private_key_override and not skip_key_check: # No key/signature given, sign with our own key. if ("signature" not in kwargs) and ( ("public_key" not in kwargs) or (kwargs["public_key"] == database_blob( self._my_key.pub().key_to_bin()[10:]))): private_key_override = self._my_key # Key/signature given, check them for correctness elif ("public_key" in kwargs) and ("signature" in kwargs): try: self._payload_class(**kwargs) except InvalidSignatureException: raise InvalidSignatureException( f"Attempted to create {str(self.__class__.__name__)} object with invalid signature/PK: " + (hexlify(kwargs["signature"]) if "signature" in kwargs else "empty signature ") + " / " + (hexlify(kwargs["public_key"]) if "public_key" in kwargs else " empty PK")) if private_key_override: # Get default values for Pony class attributes. We have to do it manually because we need # to know the payload signature *before* creating the object. kwargs = generate_dict_from_pony_args( self.__class__, skip_list=["signature", "public_key"], **kwargs) payload = self._payload_class(**dict( kwargs, public_key=private_key_override.pub().key_to_bin()[10:], key=private_key_override, metadata_type=self.metadata_type, )) kwargs["public_key"] = payload.public_key kwargs["signature"] = payload.signature super().__init__(*args, **kwargs) def _serialized(self, key=None): """ Serializes the object and returns the result with added signature (tuple output) :param key: private key to sign object with :return: (serialized_data, signature) tuple """ return self._payload_class(key=key, unsigned=(self.signature is None), **self.to_dict())._serialized() def serialized(self, key=None): """ Serializes the object and returns the result with added signature (blob output) :param key: private key to sign object with :return: serialized_data+signature binary string """ return b''.join(self._serialized(key)) def _serialized_delete(self): """ Create a special command to delete this metadata and encode it for transfer (tuple output). :return: (serialized_data, signature) tuple """ my_dict = ChannelNode.to_dict(self) my_dict.update({ "metadata_type": DELETED, "delete_signature": self.signature }) return DeletedMetadataPayload(key=self._my_key, **my_dict)._serialized() def serialized_delete(self): """ Create a special command to delete this metadata and encode it for transfer (blob output). :return: serialized_data+signature binary string """ return b''.join(self._serialized_delete()) def to_file(self, filename, key=None): with open(str_path(filename), 'wb') as output_file: output_file.write(self.serialized(key)) def to_delete_file(self, filename): with open(str_path(filename), 'wb') as output_file: output_file.write(self.serialized_delete()) def sign(self, key=None): if not key: key = self._my_key self.public_key = database_blob(key.pub().key_to_bin()[10:]) _, self.signature = self._serialized(key) def has_valid_signature(self): crypto = default_eccrypto signature_correct = False key_correct = crypto.is_valid_public_bin(b"LibNaCLPK:" + bytes(self.public_key)) if key_correct: try: self._payload_class(**self.to_dict()) except InvalidSignatureException: signature_correct = False else: signature_correct = True return key_correct and signature_correct @classmethod def from_payload(cls, payload): return cls(**payload.to_dict()) @classmethod def from_dict(cls, dct): return cls(**dct) @property @db_session def is_personal(self): # TODO: optimize this by stopping doing blob comparisons on each call, and instead remember rowid? return database_blob( self._my_key.pub().key_to_bin()[10:]) == database_blob( self.public_key) @db_session def soft_delete(self): if self.status == NEW: # Uncommited metadata. Delete immediately self.delete() else: self.status = TODELETE def update_properties(self, update_dict): signed_attribute_changed = False for k, value in update_dict.items(): if getattr(self, k) != value: setattr(self, k, value) signed_attribute_changed = signed_attribute_changed or ( k in self.payload_arguments) if signed_attribute_changed: if self.status != NEW: self.status = UPDATED self.timestamp = clock.tick() self.sign() return self def get_parents_ids(self, max_recursion_depth=15): if max_recursion_depth == 0: return tuple() if self.origin_id == 0: return (0, ) parent = db.CollectionNode.get(public_key=self.public_key, id_=self.origin_id) if parent: return parent.get_parents_ids( max_recursion_depth=max_recursion_depth - 1) + (parent.id_, ) else: return tuple() def make_copy(self, tgt_parent_id, attributes_override=None): dst_dict = attributes_override or {} for k in self.nonpersonal_attributes: dst_dict[k] = getattr(self, k) dst_dict.update({"origin_id": tgt_parent_id, "status": NEW}) return self.__class__(**dst_dict)
class ChannelNode(db.Entity): _discriminator_ = CHANNEL_NODE rowid = orm.PrimaryKey(int, size=64, auto=True) # Serializable metadata_type = orm.Discriminator(int, size=16) reserved_flags = orm.Optional(int, size=16, default=0) origin_id = orm.Optional(int, size=64, default=0) public_key = orm.Required(database_blob) id_ = orm.Required(int, size=64) orm.composite_key(public_key, id_) timestamp = orm.Required(int, size=64, default=0) signature = orm.Required(database_blob, unique=True) # Local added_on = orm.Optional(datetime, default=datetime.utcnow) status = orm.Optional(int, default=COMMITTED) # Special properties _payload_class = ChannelNodePayload _my_key = key _logger = logger _clock = clock def __init__(self, *args, **kwargs): """ Initialize a metadata object. All this dance is required to ensure that the signature is there and it is correct. """ # Process special keyworded arguments # "sign_with" argument given, sign with it private_key_override = None if "sign_with" in kwargs: kwargs["public_key"] = database_blob( kwargs["sign_with"].pub().key_to_bin()[10:]) private_key_override = kwargs["sign_with"] kwargs.pop("sign_with") # For putting legacy/test stuff in skip_key_check = False if "skip_key_check" in kwargs and kwargs["skip_key_check"]: skip_key_check = True kwargs.pop("skip_key_check") if "id_" not in kwargs: kwargs["id_"] = self._clock.tick() if "timestamp" not in kwargs: kwargs["timestamp"] = kwargs["id_"] if not private_key_override and not skip_key_check: # No key/signature given, sign with our own key. if ("signature" not in kwargs) and \ (("public_key" not in kwargs) or ( kwargs["public_key"] == database_blob(self._my_key.pub().key_to_bin()[10:]))): private_key_override = self._my_key # Key/signature given, check them for correctness elif ("public_key" in kwargs) and ("signature" in kwargs): try: self._payload_class(**kwargs) except InvalidSignatureException: raise InvalidSignatureException( ("Attempted to create %s object with invalid signature/PK: " % str(self.__class__.__name__)) + (hexlify(kwargs["signature"]) if "signature" in kwargs else "empty signature ") + " / " + (hexlify(kwargs["public_key"]) if "public_key" in kwargs else " empty PK")) if private_key_override: # Get default values for Pony class attributes. We have to do it manually because we need # to know the payload signature *before* creating the object. kwargs = generate_dict_from_pony_args( self.__class__, skip_list=["signature", "public_key"], **kwargs) payload = self._payload_class( **dict(kwargs, public_key=str( private_key_override.pub().key_to_bin()[10:]), key=private_key_override, metadata_type=self.metadata_type)) kwargs["public_key"] = payload.public_key kwargs["signature"] = payload.signature super(ChannelNode, self).__init__(*args, **kwargs) def _serialized(self, key=None): """ Serializes the object and returns the result with added signature (tuple output) :param key: private key to sign object with :return: (serialized_data, signature) tuple """ return self._payload_class(key=key, **self.to_dict())._serialized() def serialized(self, key=None): """ Serializes the object and returns the result with added signature (blob output) :param key: private key to sign object with :return: serialized_data+signature binary string """ return ''.join(self._serialized(key)) def _serialized_delete(self): """ Create a special command to delete this metadata and encode it for transfer (tuple output). :return: (serialized_data, signature) tuple """ my_dict = ChannelNode.to_dict(self) my_dict.update({ "metadata_type": DELETED, "delete_signature": self.signature }) return DeletedMetadataPayload(key=self._my_key, **my_dict)._serialized() def serialized_delete(self): """ Create a special command to delete this metadata and encode it for transfer (blob output). :return: serialized_data+signature binary string """ return ''.join(self._serialized_delete()) def to_file(self, filename, key=None): with open(filename, 'wb') as output_file: output_file.write(self.serialized(key)) def to_delete_file(self, filename): with open(filename, 'wb') as output_file: output_file.write(self.serialized_delete()) def sign(self, key=None): if not key: key = self._my_key self.public_key = database_blob(key.pub().key_to_bin()[10:]) _, self.signature = self._serialized(key) def has_valid_signature(self): crypto = default_eccrypto signature_correct = False key_correct = crypto.is_valid_public_bin(b"LibNaCLPK:" + str(self.public_key)) if key_correct: try: self._payload_class(**self.to_dict()) except InvalidSignatureException: signature_correct = False else: signature_correct = True return key_correct and signature_correct @classmethod def from_payload(cls, payload): return cls(**payload.to_dict()) @classmethod def from_dict(cls, dct): return cls(**dct)
class Metadata(db.Entity): rowid = orm.PrimaryKey(int, auto=True) metadata_type = orm.Discriminator(int) _discriminator_ = TYPELESS # We want to make signature unique=True for safety, but can't do it in Python2 because of Pony bug #390 signature = orm.Optional(buffer) timestamp = orm.Optional(datetime, default=datetime.utcnow) tc_pointer = orm.Optional(int, size=64, default=0) public_key = orm.Optional(buffer, default='\x00' * 74) addition_timestamp = orm.Optional(datetime, default=datetime.utcnow) deleted = orm.Optional(bool, default=False) _payload_class = MetadataPayload _my_key = None _logger = None def __init__(self, *args, **kwargs): super(Metadata, self).__init__(*args, **kwargs) # If no key/signature given, sign with our own key. if "public_key" not in kwargs or (kwargs["public_key"] == self._my_key and "signature" not in kwargs): self.sign(self._my_key) def _serialized(self, key=None): """ Serializes the object and returns the result with added signature (tuple output) :param key: private key to sign object with :return: (serialized_data, signature) tuple """ return self._payload_class(**self.to_dict())._serialized(key) def serialized(self, key=None): """ Serializes the object and returns the result with added signature (blob output) :param key: private key to sign object with :return: serialized_data+signature binary string """ return ''.join(self._serialized(key)) def _serialized_delete(self): """ Create a special command to delete this metadata and encode it for transfer (tuple output). :return: (serialized_data, signature) tuple """ my_dict = Metadata.to_dict(self) my_dict.update({ "metadata_type": DELETED, "delete_signature": self.signature }) return DeletedMetadataPayload(**my_dict)._serialized(self._my_key) def serialized_delete(self): """ Create a special command to delete this metadata and encode it for transfer (blob output). :return: serialized_data+signature binary string """ return ''.join(self._serialized_delete()) def to_file(self, filename, key=None): with open(filename, 'wb') as output_file: output_file.write(self.serialized(key)) def to_delete_file(self, filename): with open(filename, 'wb') as output_file: output_file.write(self.serialized_delete()) def sign(self, key=None): if not key: key = self._my_key self.public_key = buffer(key.pub().key_to_bin()) _, self.signature = self._serialized(key) def has_valid_signature(self): crypto = default_eccrypto return crypto.is_valid_public_bin(str(self.public_key)) \ and self._payload_class(**self.to_dict()).has_valid_signature() @classmethod def from_payload(cls, payload): return cls(**payload.to_dict()) @classmethod def from_dict(cls, dct): return cls(**dct)