Skip to content
Snippets Groups Projects
Commit 18163e3a authored by inso's avatar inso
Browse files

New documents services

parent 3455cec8
No related branches found
No related tags found
No related merge requests found
......@@ -338,261 +338,6 @@ class Account(QObject):
value += val
return value
async def check_registered(self, community):
"""
Checks for the pubkey and the uid of an account in a community
:param sakia.core.Community community: The community we check for registration
:return: (True if found, local value, network value)
"""
def _parse_uid_certifiers(data):
return self.name == data['uid'], self.name, data['uid']
def _parse_uid_lookup(data):
timestamp = BlockUID.empty()
found_uid = ""
for result in data['results']:
if result["pubkey"] == self.pubkey:
uids = result['uids']
for uid_data in uids:
if BlockUID.from_str(uid_data["meta"]["timestamp"]) >= timestamp:
timestamp = uid_data["meta"]["timestamp"]
found_uid = uid_data["uid"]
return self.name == found_uid, self.name, found_uid
def _parse_pubkey_certifiers(data):
return self.pubkey == data['pubkey'], self.pubkey, data['pubkey']
def _parse_pubkey_lookup(data):
timestamp = BlockUID.empty()
found_uid = ""
found_result = ["", ""]
for result in data['results']:
uids = result['uids']
for uid_data in uids:
if BlockUID.from_str(uid_data["meta"]["timestamp"]) >= timestamp:
timestamp = BlockUID.from_str(uid_data["meta"]["timestamp"])
found_uid = uid_data["uid"]
if found_uid == self.name:
found_result = result['pubkey'], found_uid
if found_result[1] == self.name:
return self.pubkey == found_result[0], self.pubkey, found_result[0]
else:
return False, self.pubkey, None
async def execute_requests(parsers, search):
tries = 0
request = bma.wot.CertifiersOf
nonlocal registered
#TODO: The algorithm is quite dirty
#Multiplying the tries without any reason...
while tries < 3 and not registered[0] and not registered[2]:
try:
data = await community.bma_access.simple_request(request,
req_args={'search': search})
if data:
registered = parsers[request](data)
tries += 1
except errors.DuniterError as e:
if e.ucode in (errors.NO_MEMBER_MATCHING_PUB_OR_UID,
e.ucode == errors.NO_MATCHING_IDENTITY):
if request == bma.wot.CertifiersOf:
request = bma.wot.Lookup
tries = 0
else:
tries += 1
else:
tries += 1
except asyncio.TimeoutError:
tries += 1
except ClientError:
tries += 1
registered = (False, self.name, None)
# We execute search based on pubkey
# And look for account UID
uid_parsers = {
bma.wot.CertifiersOf: _parse_uid_certifiers,
bma.wot.Lookup: _parse_uid_lookup
}
await execute_requests(uid_parsers, self.pubkey)
# If the uid wasn't found when looking for the pubkey
# We look for the uid and check for the pubkey
if not registered[0] and not registered[2]:
pubkey_parsers = {
bma.wot.CertifiersOf: _parse_pubkey_certifiers,
bma.wot.Lookup: _parse_pubkey_lookup
}
await execute_requests(pubkey_parsers, self.name)
return registered
async def send_selfcert(self, password, community):
"""
Send our self certification to a target community
:param str password: The account SigningKey password
:param community: The community target of the self certification
"""
try:
block_data = await community.bma_access.simple_request(bma.blockchain.Current)
signed_raw = "{0}{1}\n".format(block_data['raw'], block_data['signature'])
block_uid = Block.from_signed_raw(signed_raw).blockUID
except errors.DuniterError as e:
if e.ucode == errors.NO_CURRENT_BLOCK:
block_uid = BlockUID.empty()
else:
raise
selfcert = SelfCertification(PROTOCOL_VERSION,
community.currency,
self.pubkey,
self.name,
block_uid,
None)
key = SigningKey(self.salt, password)
selfcert.sign([key])
logging.debug("Key publish : {0}".format(selfcert.signed_raw()))
responses = await community.bma_access.broadcast(bma.wot.Add, {}, {'identity': selfcert.signed_raw()})
result = (False, "")
for r in responses:
if r.status == 200:
result = (True, (await r.json()))
elif not result[0]:
result = (False, (await r.text()))
else:
await r.release()
if result[0]:
(await self.identity(community)).sigdate = block_uid
return result
async def send_membership(self, password, community, mstype):
"""
Send a membership document to a target community.
Signal "document_broadcasted" is emitted at the end.
:param str password: The account SigningKey password
:param community: The community target of the membership document
:param str mstype: The type of membership demand. "IN" to join, "OUT" to leave
"""
logging.debug("Send membership")
blockUID = community.network.current_blockUID
self_identity = await self._identities_registry.future_find(self.pubkey, community)
selfcert = await self_identity.selfcert(community)
membership = Membership(PROTOCOL_VERSION, community.currency,
selfcert.pubkey, blockUID, mstype, selfcert.uid,
selfcert.timestamp, None)
key = SigningKey(self.salt, password)
membership.sign([key])
logging.debug("Membership : {0}".format(membership.signed_raw()))
responses = await community.bma_access.broadcast(bma.blockchain.Membership, {},
{'membership': membership.signed_raw()})
result = (False, "")
for r in responses:
if r.status == 200:
result = (True, (await r.json()))
elif not result[0]:
result = (False, (await r.text()))
else:
await r.release()
return result
async def certify(self, password, community, pubkey):
"""
Certify an other identity
:param str password: The account SigningKey password
:param sakia.core.community.Community community: The community target of the certification
:param str pubkey: The certified identity pubkey
"""
logging.debug("Certdata")
blockUID = community.network.current_blockUID
try:
identity = await self._identities_registry.future_find(pubkey, community)
selfcert = await identity.selfcert(community)
except LookupFailureError as e:
return False, str(e)
if selfcert:
certification = Certification(PROTOCOL_VERSION, community.currency,
self.pubkey, pubkey, blockUID, None)
key = SigningKey(self.salt, password)
certification.sign(selfcert, [key])
signed_cert = certification.signed_raw(selfcert)
logging.debug("Certification : {0}".format(signed_cert))
data = {'cert': certification.signed_raw(selfcert)}
logging.debug("Posted data : {0}".format(data))
responses = await community.bma_access.broadcast(bma.wot.Certify, {}, data)
result = (False, "")
for r in responses:
if r.status == 200:
result = (True, (await r.json()))
# signal certification to all listeners
self.certification_accepted.emit()
elif not result[0]:
result = (False, (await r.text()))
else:
await r.release()
return result
else:
return False, self.tr("Could not find user self certification.")
async def revoke(self, password, community):
"""
Revoke self-identity on server, not in blockchain
:param str password: The account SigningKey password
:param sakia.core.community.Community community: The community target of the revokation
"""
revoked = await self._identities_registry.future_find(self.pubkey, community)
revokation = Revocation(PROTOCOL_VERSION, community.currency, None)
selfcert = await revoked.selfcert(community)
key = SigningKey(self.salt, password)
revokation.sign(selfcert, [key])
logging.debug("Self-Revokation Document : \n{0}".format(revokation.raw(selfcert)))
logging.debug("Signature : \n{0}".format(revokation.signatures[0]))
data = {
'pubkey': revoked.pubkey,
'self_': selfcert.signed_raw(),
'sig': revokation.signatures[0]
}
logging.debug("Posted data : {0}".format(data))
responses = await community.bma_access.broadcast(bma.wot.Revoke, {}, data)
result = (False, "")
for r in responses:
if r.status == 200:
result = (True, (await r.json()))
elif not result[0]:
result = (False, (await r.text()))
else:
await r.release()
return result
async def generate_revokation(self, community, password):
"""
Generate account revokation document for given community
:param sakia.core.Community community: the community
:param str password: the password
:return: the revokation document
:rtype: duniterpy.documents.certification.Revocation
"""
document = Revocation(PROTOCOL_VERSION, community.currency, self.pubkey, "")
identity = await self.identity(community)
selfcert = await identity.selfcert(community)
key = SigningKey(self.salt, password)
document.sign(selfcert, [key])
return document.signed_raw(selfcert)
def start_coroutines(self):
for c in self.communities:
c.start_coroutines()
......
......@@ -40,6 +40,7 @@ class BmaConnector:
"""
Start a request to the network but don't cache its result.
:param str currency: the currency requested
:param class request: A bma request class calling for data
:param dict req_args: Arguments to pass to the request constructor
:param dict get_args: Arguments to pass to the request __get__ method
......@@ -48,7 +49,6 @@ class BmaConnector:
nodes = self.filter_nodes(request, self._nodes_processor.synced_nodes(currency))
if len(nodes) > 0:
tries = 0
json_data = None
while tries < 3:
node = random.choice(nodes)
nodes.pop(node)
......@@ -60,15 +60,15 @@ class BmaConnector:
asyncio.TimeoutError, ValueError, jsonschema.ValidationError) as e:
logging.debug(str(e))
tries += 1
if len(nodes) == 0 or not json_data:
if len(nodes) == 0:
raise NoPeerAvailable("", len(nodes))
return json_data
async def broadcast(self, currency, request, req_args={}, post_args={}):
"""
Broadcast data to a network.
Sends the data to all knew nodes.
:param str currency: the currency target
:param request: A duniterpy bma request class
:param req_args: Arguments to pass to the request constructor
:param post_args: Arguments to pass to the request __post__ method
......
import attr
from duniterpy.documents import block_uid, BlockUID
from duniterpy.documents import block_uid, BlockUID, SelfCertification
from duniterpy import PROTOCOL_VERSION
@attr.s()
......@@ -17,3 +18,13 @@ class Identity:
membership_timestamp = attr.ib(convert=int, default=0, cmp=False, hash=False)
membership_type = attr.ib(convert=str, default='', validator=lambda s, a, t: t in ('', 'IN', 'OUT'), cmp=False, hash=False)
membership_written_on = attr.ib(convert=block_uid, default=BlockUID.empty(), cmp=False, hash=False)
def self_certification(self):
"""
Creates a self cert document for a given identity
:param sakia.data.entities.Identity identity:
:return: the document
:rtype: duniterpy.documents.SelfCertification
"""
return SelfCertification(PROTOCOL_VERSION, self.currency, self.pubkey,
self.uid, self.blockstamp, self.signature)
......@@ -4,6 +4,8 @@ from duniterpy.api import bma, errors
import asyncio
from aiohttp.errors import ClientError
from sakia.errors import NoPeerAvailable
from duniterpy.documents import SelfCertification
from duniterpy import PROTOCOL_VERSION
@attr.s
......@@ -55,7 +57,6 @@ class IdentitiesProcessor:
def update_identity(self, identity):
"""
Saves an identity state in the db
:param identity:
:return:
:param sakia.data.entities.Identity identity: the identity updated
"""
self._identities_repo.update(identity)
......@@ -3,7 +3,7 @@ import logging
from PyQt5.QtGui import QCursor
from PyQt5.QtWidgets import QDialog, QApplication, QMenu
from aiohttp.errors import DisconnectedError, ClientError, TimeoutError
from sakia.tools.exceptions import NoPeerAvailable
from sakia.errors import NoPeerAvailable
from duniterpy.documents import MalformedDocumentError
from sakia.decorators import asyncify
......
......@@ -10,7 +10,7 @@ import asyncio
from PyQt5.QtCore import QEvent
from PyQt5.QtWidgets import QDialog, QMessageBox
from ..gen_resources.password_asker_uic import Ui_PasswordAskerDialog
from .password_asker_uic import Ui_PasswordAskerDialog
class PasswordAskerDialog(QDialog, Ui_PasswordAskerDialog):
......
from .network import NetworkService
from .identities import IdentitiesService
from .blockchain import BlockchainService
from .documents import DocumentsService
import asyncio
import attr
import logging
import jsonschema
from collections import Counter
from duniterpy.key import SigningKey
from duniterpy import PROTOCOL_VERSION
from duniterpy.documents import BlockUID, Block, SelfCertification, Certification, Membership, Revocation
from duniterpy.api import bma, errors
from sakia.data.entities import Node
from aiohttp.errors import ClientError, DisconnectedError
@attr.s()
class DocumentsService:
"""
A service to forge and broadcast documents
to the network
"""
_bma_connector = attr.ib() # :type: sakia.data.connectors.BmaConnector
_blockchain_processor = attr.ib() # :type: sakia.data.processors.BlockchainProcessor
_identities_processor = attr.ib() # :type: sakia.data.processors.IdentitiesProcessor
_logger = attr.ib(default=lambda: logging.getLogger('sakia'))
async def check_registered(self, currency):
"""
Checks for the pubkey and the uid of an account in a community
:param str currency: The currency we check for registration
:return: (True if found, local value, network value)
"""
def _parse_uid_certifiers(data):
return self.name == data['uid'], self.name, data['uid']
def _parse_uid_lookup(data):
timestamp = BlockUID.empty()
found_uid = ""
for result in data['results']:
if result["pubkey"] == self.pubkey:
uids = result['uids']
for uid_data in uids:
if BlockUID.from_str(uid_data["meta"]["timestamp"]) >= timestamp:
timestamp = uid_data["meta"]["timestamp"]
found_uid = uid_data["uid"]
return self.name == found_uid, self.name, found_uid
def _parse_pubkey_certifiers(data):
return self.pubkey == data['pubkey'], self.pubkey, data['pubkey']
def _parse_pubkey_lookup(data):
timestamp = BlockUID.empty()
found_uid = ""
found_result = ["", ""]
for result in data['results']:
uids = result['uids']
for uid_data in uids:
if BlockUID.from_str(uid_data["meta"]["timestamp"]) >= timestamp:
timestamp = BlockUID.from_str(uid_data["meta"]["timestamp"])
found_uid = uid_data["uid"]
if found_uid == self.name:
found_result = result['pubkey'], found_uid
if found_result[1] == self.name:
return self.pubkey == found_result[0], self.pubkey, found_result[0]
else:
return False, self.pubkey, None
async def execute_requests(parsers, search):
tries = 0
request = bma.wot.CertifiersOf
nonlocal registered
#TODO: The algorithm is quite dirty
#Multiplying the tries without any reason...
while tries < 3 and not registered[0] and not registered[2]:
try:
data = await self._bma_connector.get(currency, request, req_args={'search': search})
if data:
registered = parsers[request](data)
tries += 1
except errors.DuniterError as e:
if e.ucode in (errors.NO_MEMBER_MATCHING_PUB_OR_UID,
e.ucode == errors.NO_MATCHING_IDENTITY):
if request == bma.wot.CertifiersOf:
request = bma.wot.Lookup
tries = 0
else:
tries += 1
else:
tries += 1
except asyncio.TimeoutError:
tries += 1
except (ClientError, TimeoutError, ConnectionRefusedError, DisconnectedError, ValueError) as e:
self._logger.debug("{0} : {1}".format(str(e), self.node.pubkey[:5]))
self.node.state = Node.OFFLINE
except jsonschema.ValidationError as e:
self._logger.debug(str(e))
self._logger.debug("Validation error : {0}".format(self.node.pubkey[:5]))
self.node.state = Node.CORRUPTED
registered = (False, self.name, None)
# We execute search based on pubkey
# And look for account UID
uid_parsers = {
bma.wot.CertifiersOf: _parse_uid_certifiers,
bma.wot.Lookup: _parse_uid_lookup
}
await execute_requests(uid_parsers, self.pubkey)
# If the uid wasn't found when looking for the pubkey
# We look for the uid and check for the pubkey
if not registered[0] and not registered[2]:
pubkey_parsers = {
bma.wot.CertifiersOf: _parse_pubkey_certifiers,
bma.wot.Lookup: _parse_pubkey_lookup
}
await execute_requests(pubkey_parsers, self.name)
return registered
async def send_selfcert(self, currency, salt, password):
"""
Send our self certification to a target community
:param str currency: The currency of the identity
:param sakia.data.entities.Identity identity: The certified identity
:param str salt: The account SigningKey salt
:param str password: The account SigningKey password
"""
try:
block_data = await self._bma_connector.get(currency, bma.blockchain.Current)
signed_raw = "{0}{1}\n".format(block_data['raw'], block_data['signature'])
block_uid = Block.from_signed_raw(signed_raw).blockUID
except errors.DuniterError as e:
if e.ucode == errors.NO_CURRENT_BLOCK:
block_uid = BlockUID.empty()
else:
raise
selfcert = SelfCertification(PROTOCOL_VERSION,
currency,
self.pubkey,
self.name,
block_uid,
None)
key = SigningKey(self.salt, password)
selfcert.sign([key])
self._logger.debug("Key publish : {0}".format(selfcert.signed_raw()))
responses = await self._bma_connector.broadcast(currency, bma.wot.Add, {}, {'identity': selfcert.signed_raw()})
result = (False, "")
for r in responses:
if r.status == 200:
result = (True, (await r.json()))
elif not result[0]:
result = (False, (await r.text()))
else:
await r.release()
return result
async def send_membership(self, currency, identity, password, mstype):
"""
Send a membership document to a target community.
Signal "document_broadcasted" is emitted at the end.
:param str currency: the currency target
:param sakia.data.entities.Identity identity: the identitiy data
:param str password: The account SigningKey password
:param str mstype: The type of membership demand. "IN" to join, "OUT" to leave
"""
self._logger.debug("Send membership")
blockUID = self._blockchain_processor.current_buid(currency)
membership = Membership(PROTOCOL_VERSION, currency,
identity.pubkey, blockUID, mstype, identity.uid,
identity.timestamp, None)
key = SigningKey(self.salt, password)
membership.sign([key])
self._logger.debug("Membership : {0}".format(membership.signed_raw()))
responses = await self._bma_connector.broadcast(currency, bma.blockchain.Membership, {},
{'membership': membership.signed_raw()})
result = (False, "")
for r in responses:
if r.status == 200:
result = (True, (await r.json()))
elif not result[0]:
result = (False, (await r.text()))
else:
await r.release()
return result
async def certify(self, currency, identity, salt, password):
"""
Certify an other identity
:param str currency: The currency of the identity
:param sakia.data.entities.Identity identity: The certified identity
:param str salt: The account SigningKey salt
:param str password: The account SigningKey password
"""
self._logger.debug("Certdata")
blockUID = self._blockchain_processor.current_buid(currency)
certification = Certification(PROTOCOL_VERSION, currency,
self.pubkey, identity.pubkey, blockUID, None)
key = SigningKey(salt, password)
certification.sign(identity.self_certification(), [key])
signed_cert = certification.signed_raw(identity.self_certification())
self._logger.debug("Certification : {0}".format(signed_cert))
responses = await self._bma_connector.bma_access.broadcast(currency, bma.wot.Certify, {},
{'cert': signed_cert})
result = (False, "")
for r in responses:
if r.status == 200:
result = (True, (await r.json()))
# signal certification to all listeners
self.certification_accepted.emit()
elif not result[0]:
result = (False, (await r.text()))
else:
await r.release()
return result
async def revoke(self, currency, identity, salt, password):
"""
Revoke self-identity on server, not in blockchain
:param str currency: The currency of the identity
:param sakia.data.entities.Identity identity: The certified identity
:param str salt: The account SigningKey salt
:param str password: The account SigningKey password
"""
revocation = Revocation(PROTOCOL_VERSION, currency, None)
self_cert = identity.self_certification()
key = SigningKey(salt, password)
revocation.sign(self_cert, [key])
self._logger.debug("Self-Revokation Document : \n{0}".format(revocation.raw(self_cert)))
self._logger.debug("Signature : \n{0}".format(revocation.signatures[0]))
data = {
'pubkey': identity.pubkey,
'self_': self_cert.signed_raw(),
'sig': revocation.signatures[0]
}
self._logger.debug("Posted data : {0}".format(data))
responses = await self._bma_connector.broadcast(currency, bma.wot.Revoke, {}, data)
result = (False, "")
for r in responses:
if r.status == 200:
result = (True, (await r.json()))
elif not result[0]:
result = (False, (await r.text()))
else:
await r.release()
return result
async def generate_revokation(self, currency, identity, salt, password):
"""
Generate account revokation document for given community
:param str currency: The currency of the identity
:param sakia.data.entities.Identity identity: The certified identity
:param str salt: The account SigningKey salt
:param str password: The account SigningKey password
"""
document = Revocation(PROTOCOL_VERSION, currency, identity.pubkey, "")
self_cert = identity.self_certification()
key = SigningKey(salt, password)
document.sign(self_cert, [key])
return document.signed_raw(self_cert)
import asyncio
import unittest
import sqlite3
import aiohttp
from duniterpy.documents import BlockUID, Peer
from sakia.tests import QuamashTest
from sakia.services import DocumentsService
from sakia.data.connectors import NodeConnector, BmaConnector
from sakia.data.repositories import NodesRepo, MetaDatabase, BlockchainsRepo, IdentitiesRepo
from sakia.data.processors import NodesProcessor, BlockchainProcessor, IdentitiesProcessor
class TestDocumentsService(unittest.TestCase, QuamashTest):
def setUp(self):
self.setUpQuamash()
sqlite3.register_adapter(BlockUID, str)
sqlite3.register_adapter(bool, int)
sqlite3.register_adapter(list, lambda ls: '\n'.join([str(v) for v in ls]))
sqlite3.register_adapter(tuple, lambda ls: '\n'.join([str(v) for v in ls]))
sqlite3.register_converter("BOOLEAN", lambda v: bool(int(v)))
self.con = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES)
def tearDown(self):
self.tearDownQuamash()
def test_certify(self):
meta_repo = MetaDatabase(self.con)
meta_repo.prepare()
meta_repo.upgrade_database()
nodes_repo = NodesRepo(self.con)
nodes_processor = NodesProcessor(nodes_repo)
bma_connector = BmaConnector(nodes_processor)
blockchain_repo = BlockchainsRepo(self.con)
identities_repo = IdentitiesRepo(self.con)
blockchain_processor = BlockchainProcessor(blockchain_repo, bma_connector)
identities_processor = IdentitiesProcessor(identities_repo, bma_connector)
documents_service = DocumentsService(bma_connector, blockchain_processor, identities_processor)
#TODO: Build a framework to test documents broadcasting
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment