diff --git a/src/sakia/data/connectors/bma.py b/src/sakia/data/connectors/bma.py index 0f53e826c0203ab4740c612bd3ef2aa9b2986f50..b2c0029e8594adbcb29087f3e70354b96f89f6a0 100644 --- a/src/sakia/data/connectors/bma.py +++ b/src/sakia/data/connectors/bma.py @@ -41,7 +41,7 @@ class BmaConnector: endpoints += [e for e in n.endpoints if type(e) in (BMAEndpoint, SecuredBMAEndpoint)] return endpoints - async def get(self, currency, request, req_args={}, get_args={}): + async def get(self, currency, request, req_args={}): """ Start a request to the network but don't cache its result. @@ -58,7 +58,7 @@ class BmaConnector: endpoint = random.choice(endpoints) try: self._logger.debug("Requesting {0} on endpoint {1}".format(str(request.__name__), str(endpoint))) - with aiohttp.ClientSession() as session: + async with aiohttp.ClientSession() as session: json_data = await request(endpoint.conn_handler(session), **req_args) return json_data except (ClientError, ServerDisconnectedError, gaierror, @@ -67,7 +67,7 @@ class BmaConnector: tries += 1 raise NoPeerAvailable("", len(endpoints)) - async def broadcast(self, currency, request, req_args={}, post_args={}): + async def broadcast(self, currency, request, req_args={}): """ Broadcast data to a network. Sends the data to all knew nodes. @@ -75,7 +75,6 @@ class BmaConnector: :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 :return: All nodes replies :rtype: tuple of aiohttp replies diff --git a/src/sakia/data/entities/identity.py b/src/sakia/data/entities/identity.py index 445d8f5ecd1e6e019f75ff8a0c81d83604b564bd..4e1930efb0dc95be0e3abdf492b267bfa859abff 100644 --- a/src/sakia/data/entities/identity.py +++ b/src/sakia/data/entities/identity.py @@ -1,5 +1,6 @@ import attr -from duniterpy.documents import block_uid, BlockUID, Identity +from duniterpy.documents import block_uid, BlockUID +from duniterpy.documents import Identity as IdentityDoc from duniterpy import PROTOCOL_VERSION @@ -27,5 +28,5 @@ class Identity: :return: the document :rtype: duniterpy.documents.Identity """ - return Identity(PROTOCOL_VERSION, self.currency, self.pubkey, + return IdentityDoc(3, self.currency, self.pubkey, self.uid, self.blockstamp, self.signature) diff --git a/src/sakia/data/processors/identities.py b/src/sakia/data/processors/identities.py index da208f028a1187f219604b3ba6a7682c89b22f7b..4a78f8536afc8a4990d4cc215aaf6d645436c70c 100644 --- a/src/sakia/data/processors/identities.py +++ b/src/sakia/data/processors/identities.py @@ -41,8 +41,7 @@ class IdentitiesProcessor: tries = 0 while tries < 3: try: - data = await self._bma_connector.get(bma.wot.Lookup, - req_args={'search': pubkey}) + data = await self._bma_connector.get(currency, bma.wot.lookup, req_args={'search': pubkey}) for result in data['results']: if result["pubkey"] == pubkey: uids = result['uids'] @@ -174,13 +173,3 @@ class IdentitiesProcessor: pass else: raise - - async def publish_selfcert(self, currency, identity, salt, password, scrypt_params): - """ - Send our self certification to a target community - :param sakia.data.entities.Identity identity: The identity broadcasted - :param str salt: The account SigningKey salt - :param str password: The account SigningKey password - :param str currency: The currency target of the self certification - :param duniterpy.key.ScryptParams scrypt_params: The scrypt parameters of the key - """ \ No newline at end of file diff --git a/src/sakia/gui/dialogs/certification/certification.ui b/src/sakia/gui/dialogs/certification/certification.ui index f97b51dd02449ba2d77fc2c3443aea19e3ce0207..db5a007ba124a328dfd257f63558b97b13395146 100644 --- a/src/sakia/gui/dialogs/certification/certification.ui +++ b/src/sakia/gui/dialogs/certification/certification.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>517</width> - <height>378</height> + <height>338</height> </rect> </property> <property name="windowTitle"> @@ -62,111 +62,18 @@ </widget> </item> <item> - <widget class="QGroupBox" name="groupBox"> + <widget class="QGroupBox" name="groupbox_certified"> <property name="title"> <string>Certify user</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout_5"> - <item> - <layout class="QVBoxLayout" name="layout_target_choice"> - <property name="topMargin"> - <number>6</number> - </property> - <item> - <layout class="QHBoxLayout" name="layout_mode_pubkey"> - <item> - <widget class="QRadioButton" name="radio_pubkey"> - <property name="text"> - <string>&User public key</string> - </property> - <property name="checked"> - <bool>false</bool> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Maximum</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QLineEdit" name="edit_pubkey"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="inputMask"> - <string/> - </property> - <property name="text"> - <string/> - </property> - <property name="placeholderText"> - <string>Key</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="layout_mode_search"> - <property name="topMargin"> - <number>6</number> - </property> - <item> - <widget class="QRadioButton" name="radio_search"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>Sea&rch user</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Maximum</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - </layout> - </item> - </layout> + <layout class="QVBoxLayout" name="verticalLayout_3"/> </widget> </item> <item> <widget class="QDialogButtonBox" name="button_box"> + <property name="enabled"> + <bool>true</bool> + </property> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> diff --git a/src/sakia/gui/dialogs/certification/controller.py b/src/sakia/gui/dialogs/certification/controller.py index c2b6bfc326d8fd1d1a6bfa59d7ab8b8f9325f2dd..2e408256096970c98ab1c3ff7b04acf0ae81b29a 100644 --- a/src/sakia/gui/dialogs/certification/controller.py +++ b/src/sakia/gui/dialogs/certification/controller.py @@ -84,8 +84,6 @@ class CertificationController(QObject): """ dialog = cls.create(parent, app) dialog.view.combo_community.setCurrentText(connection.currency) - dialog.view.edit_pubkey.setText(identity.pubkey) - dialog.view.radio_pubkey.setChecked(True) dialog.refresh() return await dialog.async_exec() @@ -107,6 +105,7 @@ class CertificationController(QObject): """ self.user_information = user_information self.view.set_user_information(user_information.view) + self.user_information.identity_loaded.connect(self.refresh) @asyncify async def accept(self): @@ -119,17 +118,14 @@ class CertificationController(QObject): self.view.button_box.setEnabled(True) return QApplication.setOverrideCursor(Qt.WaitCursor) - if self.view.radio_pubkey.isChecked(): - result = await self.model.certify_pubkey(password, self.view.edit_pubkey.text()) - else: - result = await self.model.certify_identity(password, self.user_information.model.identity) + result = await self.model.certify_identity(password, self.user_information.model.identity) if result[0]: QApplication.restoreOverrideCursor() - await self.view.show_success() + await self.view.show_success(self.model.notification()) self.view.accept() else: - await self.view.show_error(result[1]) + await self.view.show_error(self.model.notification(), result[1]) QApplication.restoreOverrideCursor() self.view.button_box.setEnabled(True) @@ -144,7 +140,9 @@ class CertificationController(QObject): if self.model.could_certify(): if written < stock or stock == 0: - if days+hours+minutes > 0: + if not self.user_information.model.identity: + self.view.set_button_box(CertificationView.ButtonBoxState.SELECT_IDENTITY) + elif days+hours+minutes > 0: if days > 0: remaining_localized = self.tr("{days} days").format(days=days) else: @@ -162,11 +160,7 @@ class CertificationController(QObject): """ Refresh user information """ - pubkey = self.selected_pubkey() - if self.search_user.identity_selected: - self.user_information.search_identity(self.search_user.model.identity()) - else: - self.user_information.search_identity(Identity(self.model.connection.currency, pubkey)) + self.user_information.search_identity(self.search_user.model.identity()) def change_currency(self, index): currency = self.model.available_currencies()[index] diff --git a/src/sakia/gui/dialogs/certification/model.py b/src/sakia/gui/dialogs/certification/model.py index 24ed41a4f93bbae808e98184d5cd6581bd542edd..3c3cbfaffc1eb97be80f5e942ec121038d68e62f 100644 --- a/src/sakia/gui/dialogs/certification/model.py +++ b/src/sakia/gui/dialogs/certification/model.py @@ -91,8 +91,8 @@ class CertificationModel(QObject): connections = self._connections_processor.connections(currency=currency) self.connection = connections[index] - def certify_pubkey(self, password, pubkey): - self._documents_service.certify(self.connection, password, pubkey) + def notification(self): + return self.app.parameters.notifications - def certify_identity(self, password, pubkey): - self._certifications_processor.certify(self.connection, password, pubkey) \ No newline at end of file + async def certify_identity(self, password, identity): + return await self.app.documents_service.certify(self.connection, password, identity) \ No newline at end of file diff --git a/src/sakia/gui/dialogs/certification/view.py b/src/sakia/gui/dialogs/certification/view.py index f8ed6042b0c622641accb321a5b896b8607b6b5d..e6107f74c7ad6a48d8e4f2b492abf0ae0d4263af 100644 --- a/src/sakia/gui/dialogs/certification/view.py +++ b/src/sakia/gui/dialogs/certification/view.py @@ -16,23 +16,19 @@ class CertificationView(QDialog, Ui_CertificationDialog): NOT_A_MEMBER = 1 REMAINING_TIME_BEFORE_VALIDATION = 2 OK = 3 - - class RecipientMode(Enum): - PUBKEY = 0 - SEARCH = 1 + SELECT_IDENTITY = 4 _button_box_values = { ButtonBoxState.NO_MORE_CERTIFICATION: (False, QT_TRANSLATE_NOOP("CertificationView", "No more certifications")), ButtonBoxState.NOT_A_MEMBER: (False, QT_TRANSLATE_NOOP("CertificationView", "Not a member")), + ButtonBoxState.SELECT_IDENTITY: (False, QT_TRANSLATE_NOOP("CertificationView", "Please select an identity")), ButtonBoxState.REMAINING_TIME_BEFORE_VALIDATION: (True, QT_TRANSLATE_NOOP("CertificationView", "&Ok (Not validated before {remaining})")), ButtonBoxState.OK: (True, QT_TRANSLATE_NOOP("CertificationView", "&Ok")) } - pubkey_changed = pyqtSignal() - def __init__(self, parent, search_user_view, user_information_view): """ @@ -44,18 +40,9 @@ class CertificationView(QDialog, Ui_CertificationDialog): super().__init__(parent) self.setupUi(self) - self.radio_pubkey.toggled.connect(lambda c, radio=CertificationView.RecipientMode.PUBKEY: - self.recipient_mode_changed(radio)) - self.radio_search.toggled.connect(lambda c, radio=CertificationView.RecipientMode.SEARCH: - self.recipient_mode_changed(radio)) - self.search_user = search_user_view self.user_information_view = user_information_view - self.edit_pubkey.textChanged.connect(self.pubkey_changed) - self.radio_search.toggled.connect(self.pubkey_changed) - self.radio_pubkey.toggled.connect(self.pubkey_changed) - def set_keys(self, connections): self.combo_pubkey.clear() for c in connections: @@ -79,33 +66,27 @@ class CertificationView(QDialog, Ui_CertificationDialog): :return: """ self.search_user = search_user_view - self.layout_mode_search.addWidget(search_user_view) + self.groupbox_certified.layout().addWidget(search_user_view) self.search_user.button_reset.hide() def set_user_information(self, user_information_view): self.user_information_view = user_information_view - self.layout_target_choice.addWidget(user_information_view) - - def recipient_mode(self): - if self.radio_search.isChecked(): - return CertificationView.RecipientMode.SEARCH - else: - return CertificationView.RecipientMode.PUBKEY + self.groupbox_certified.layout().addWidget(user_information_view) def pubkey_value(self): return self.edit_pubkey.text() - async def show_success(self): - if self.app.preferences['notifications']: + async def show_success(self, notification): + if notification: toast.display(self.tr("Certification"), self.tr("Success sending certification")) else: await QAsyncMessageBox.information(self.widget, self.tr("Certification"), self.tr("Success sending certification")) - async def show_error(self, error_txt): + async def show_error(self, notification, error_txt): - if self.app.preferences['notifications']: + if notification: toast.display(self.tr("Certification"), self.tr("Could not broadcast certification : {0}" .format(error_txt))) else: @@ -149,10 +130,3 @@ class CertificationView(QDialog, Ui_CertificationDialog): button_box_state = CertificationView._button_box_values[state] self.button_box.button(QDialogButtonBox.Ok).setEnabled(button_box_state[0]) self.button_box.button(QDialogButtonBox.Ok).setText(button_box_state[1].format(**kwargs)) - - def recipient_mode_changed(self, radio): - """ - :param str radio: - """ - self.edit_pubkey.setEnabled(radio == CertificationView.RecipientMode.PUBKEY) - self.search_user.setEnabled(radio == CertificationView.RecipientMode.SEARCH) diff --git a/src/sakia/gui/sub/user_information/controller.py b/src/sakia/gui/sub/user_information/controller.py index 5cd642dde28ac1eccdb8d1dacdca75b1ac238e74..b1917405e91f986fe3d7c62701dd7257c85a539e 100644 --- a/src/sakia/gui/sub/user_information/controller.py +++ b/src/sakia/gui/sub/user_information/controller.py @@ -1,5 +1,5 @@ from PyQt5.QtWidgets import QDialog -from PyQt5.QtCore import QObject +from PyQt5.QtCore import QObject, pyqtSignal from sakia.decorators import asyncify from .model import UserInformationModel from .view import UserInformationView @@ -9,6 +9,7 @@ class UserInformationController(QObject): """ The homescreen view """ + identity_loaded = pyqtSignal() def __init__(self, parent, view, model): """ @@ -46,6 +47,7 @@ class UserInformationController(QObject): self.view.display_identity_timestamps(self.model.identity.pubkey, self.model.identity.timestamp, self.model.identity.membership_timestamp) self.view.hide_busy() + self.identity_loaded.emit() @asyncify async def search_identity(self, identity): diff --git a/src/sakia/services/documents.py b/src/sakia/services/documents.py index 73222ae4217321f7b110fee930051a7fc2e91a3a..85453e40358d4159600026e5413c6c171d03807e 100644 --- a/src/sakia/services/documents.py +++ b/src/sakia/services/documents.py @@ -41,6 +41,7 @@ class DocumentsService: Send our self certification to a target community :param sakia.data.entities.Connection connection: the connection published + :param str password: the private key password """ block_uid = self._blockchain_processor.current_buid(connection.currency) timestamp = self._blockchain_processor.time(connection.currency) @@ -107,34 +108,30 @@ class DocumentsService: await r.release() return result - async def certify(self, currency, identity, salt, password): + async def certify(self, connection, password, identity): """ Certify an other identity - :param str currency: The currency of the identity - :param sakia.data.entities.IdentityDoc identity: The certified identity - :param str salt: The account SigningKey salt - :param str password: The account SigningKey password + :param sakia.data.entities.Connection connection: the connection published + :param str password: the private key password + :param sakia.data.entities.Identity identity: the identity certified """ self._logger.debug("Certdata") - blockUID = self._blockchain_processor.current_buid(currency) + blockUID = self._blockchain_processor.current_buid(connection.currency) - certification = Certification(PROTOCOL_VERSION, currency, - self.pubkey, identity.pubkey, blockUID, None) + certification = Certification(6, connection.currency, + connection.pubkey, identity.pubkey, blockUID, None) - key = SigningKey(salt, password) + key = SigningKey(connection.salt, connection.password, connection.scrypt_params) certification.sign(identity.document(), [key]) signed_cert = certification.signed_raw(identity.document()) self._logger.debug("Certification : {0}".format(signed_cert)) - responses = await self._bma_connector.bma_access.broadcast(currency, bma.wot.Certify, {}, - {'cert': signed_cert}) + responses = await self._bma_connector.broadcast(connection.currency, bma.wot.certify, req_args={'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: diff --git a/src/sakia/tests/functional/test_certification_dialog.py b/src/sakia/tests/functional/test_certification_dialog.py index d2e5c49bfe438ff95a70c67b8fb0d128ac6961b0..6e5f750350992fd6d3acdaa3f93a858827009181 100644 --- a/src/sakia/tests/functional/test_certification_dialog.py +++ b/src/sakia/tests/functional/test_certification_dialog.py @@ -1,9 +1,10 @@ import asyncio import pytest from duniterpy.documents import Certification -from PyQt5.QtCore import QLocale, Qt +from PyQt5.QtCore import QLocale, Qt, QEvent from PyQt5.QtTest import QTest from PyQt5.QtWidgets import QDialogButtonBox, QApplication, QMessageBox +from PyQt5.QtGui import QKeyEvent from sakia.gui.dialogs.certification.controller import CertificationController @@ -17,10 +18,21 @@ async def test_certification_init_community(application_with_one_connection, fak async def exec_test(): certification_dialog.model.connection.password = bob.password - certification_dialog.view.radio_pubkey.setChecked(True) - assert certification_dialog.view.edit_pubkey.isEnabled() is True - QTest.keyClicks(certification_dialog.view.edit_pubkey, alice.key.pubkey) + QTest.keyClicks(certification_dialog.view.search_user.combobox_search.lineEdit(), "nothing") + await asyncio.sleep(1) + certification_dialog.search_user.view.search() + await asyncio.sleep(1) + assert certification_dialog.user_information.model.identity is None + assert not certification_dialog.view.button_box.button(QDialogButtonBox.Ok).isEnabled() + certification_dialog.view.search_user.combobox_search.lineEdit().clear() + QTest.keyClicks(certification_dialog.view.search_user.combobox_search.lineEdit(), alice.key.pubkey) await asyncio.sleep(0.1) + certification_dialog.search_user.view.search() + await asyncio.sleep(0.1) + certification_dialog.search_user.view.node_selected.emit(0) + await asyncio.sleep(0.1) + assert certification_dialog.user_information.model.identity.uid == "alice" + assert certification_dialog.view.button_box.button(QDialogButtonBox.Ok).isEnabled() QTest.mouseClick(certification_dialog.view.button_box.button(QDialogButtonBox.Ok), Qt.LeftButton) await asyncio.sleep(0.1) assert Certification is type(fake_server.forge.pool[0])