From 36e4be7db168713b1b178e5af9c9427441763854 Mon Sep 17 00:00:00 2001
From: Vincent Texier <vit@free.fr>
Date: Sun, 24 May 2015 18:12:37 +0200
Subject: [PATCH] Implement Revoke UID button [WORK IN PROGRESS - BROKEN]

fixme : Revocation signature is wrong
---
 lib/ucoinpy/api/bma/wot/__init__.py    | 10 ++++
 lib/ucoinpy/documents/certification.py | 25 ++++++++
 res/i18n/ts/fr_FR.ts                   | 81 +++++++++++++++++++-------
 res/ui/community_tab.ui                | 28 ++++++++-
 src/cutecoin/core/account.py           | 35 +++++++++--
 src/cutecoin/gui/community_tab.py      | 45 ++++++++++----
 6 files changed, 186 insertions(+), 38 deletions(-)

diff --git a/lib/ucoinpy/api/bma/wot/__init__.py b/lib/ucoinpy/api/bma/wot/__init__.py
index 0de3bfc8..ea9b9a23 100644
--- a/lib/ucoinpy/api/bma/wot/__init__.py
+++ b/lib/ucoinpy/api/bma/wot/__init__.py
@@ -37,6 +37,16 @@ class Add(WOT):
         return self.requests_post('/add', **kwargs).json()
 
 
+class Revoke(WOT):
+    """POST Public key data."""
+
+    def __post__(self, **kwargs):
+        assert 'pubkey' in kwargs
+        assert 'self_' in kwargs
+
+        return self.requests_post('/revoke', **kwargs).json()
+
+
 class Lookup(WOT):
     """GET Public key data."""
 
diff --git a/lib/ucoinpy/documents/certification.py b/lib/ucoinpy/documents/certification.py
index ce5eff0e..517d5bcf 100644
--- a/lib/ucoinpy/documents/certification.py
+++ b/lib/ucoinpy/documents/certification.py
@@ -105,3 +105,28 @@ class Certification(Document):
         return "{0}:{1}:{2}:{3}".format(self.pubkey_from, self.pubkey_to,
                                         self.blocknumber, self.signatures[0])
 
+
+class Revocation(Document):
+    '''
+    A document describing a self-revocation.
+    '''
+    def __init__(self, version, currency, signature):
+        '''
+        Constructor
+        '''
+        super().__init__(version, currency, [signature])
+
+    @staticmethod
+    def raw():
+        return """META:REVOKE"""
+
+    def sign(self, selfcert, keys):
+        '''
+        Sign the current document.
+        Warning : current signatures will be replaced with the new ones.
+        '''
+        self.signatures = []
+        for key in keys:
+            signing = base64.b64encode(key.signature(bytes(selfcert.signed_raw() + self.raw(), 'ascii')))
+            self.signatures.append(signing.decode("ascii"))
+
diff --git a/res/i18n/ts/fr_FR.ts b/res/i18n/ts/fr_FR.ts
index 7f14e202..70a87ab7 100644
--- a/res/i18n/ts/fr_FR.ts
+++ b/res/i18n/ts/fr_FR.ts
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS><TS version="2.0" language="fr_FR" sourcelanguage="en">
+<!DOCTYPE TS>
+<TS version="2.0" language="fr_FR" sourcelanguage="en">
 <context>
     <name>@default</name>
     <message>
@@ -271,12 +272,12 @@
         <translation>Qualification : </translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/community_tab.py" line="310"/>
+        <location filename="../../../src/cutecoin/gui/community_tab.py" line="337"/>
         <source>Renew membership</source>
         <translation>Renouveller le statut de membre</translation>
     </message>
     <message>
-        <location filename="../../ui/community_tab.ui" line="139"/>
+        <location filename="../../ui/community_tab.ui" line="146"/>
         <source>Send leaving demand</source>
         <translation>Quitter la communauté</translation>
     </message>
@@ -301,17 +302,17 @@
         <translation>La clé n&apos;a pas pu être envoyée à la communauté</translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/community_tab.py" line="236"/>
+        <location filename="../../../src/cutecoin/gui/community_tab.py" line="263"/>
         <source>Network error</source>
         <translation>Erreur réseau</translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/community_tab.py" line="236"/>
+        <location filename="../../../src/cutecoin/gui/community_tab.py" line="263"/>
         <source>Couldn&apos;t connect to network : {0}</source>
         <translation>Impossible de se connecter au réseau : {0}</translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/community_tab.py" line="218"/>
+        <location filename="../../../src/cutecoin/gui/community_tab.py" line="245"/>
         <source>Warning</source>
         <translation>Attention</translation>
     </message>
@@ -321,12 +322,12 @@
         <translation>Succès lors de l&apos;envoi de la demande pour quitter la communauté</translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/community_tab.py" line="233"/>
+        <location filename="../../../src/cutecoin/gui/community_tab.py" line="206"/>
         <source>Leaving demand error</source>
         <translation>Erreur lors de l&apos;envoi de la demande pour quitter la communauté</translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/community_tab.py" line="240"/>
+        <location filename="../../../src/cutecoin/gui/community_tab.py" line="267"/>
         <source>Error</source>
         <translation>Erreur</translation>
     </message>
@@ -354,7 +355,7 @@
         <location filename="../../../src/cutecoin/gui/community_tab.py" line="218"/>
         <source>Are you sure ?
 Publishing your UID cannot be canceled.</source>
-        <translation>Êtes vous certain ?
+        <translation type="obsolete">Êtes vous certain ?
 Publier votre UID ne peut être annulé.</translation>
     </message>
     <message>
@@ -414,10 +415,48 @@ Le processus pour rejoindre la communauté devrait être refait à zéro.</trans
         <translation>Voir dans la Toile de Confiance</translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/community_tab.py" line="316"/>
+        <location filename="../../../src/cutecoin/gui/community_tab.py" line="343"/>
         <source>Send membership demand</source>
         <translation>Envoyer une demande de membre</translation>
     </message>
+    <message>
+        <location filename="../../ui/community_tab.ui" line="132"/>
+        <source>Revoke UID</source>
+        <translation>Révoquer votre UID</translation>
+    </message>
+    <message>
+        <location filename="../../../src/cutecoin/gui/community_tab.py" line="218"/>
+        <source>Are you sure ?
+Publishing your UID can be canceled by Revoke UID.</source>
+        <translation>Etes-vous sûr(e) ? Publier votre UID peut être annulé par le bouton Révoquer votre UID.</translation>
+    </message>
+    <message>
+        <location filename="../../../src/cutecoin/gui/community_tab.py" line="233"/>
+        <source>Publish UID error</source>
+        <translatorcomment>Erreur lors de la publication de votre UID</translatorcomment>
+        <translation>Publier votre UID</translation>
+    </message>
+    <message>
+        <location filename="../../../src/cutecoin/gui/community_tab.py" line="245"/>
+        <source>Are you sure ?
+Revoking your UID can only success if it is not already validated by the network.</source>
+        <translation>Etes-vous sûr(e) ? Révoquer votre UID ne peut réussir que s&apos;il n&apos;a pas été déjà validé par le réseau.</translation>
+    </message>
+    <message>
+        <location filename="../../../src/cutecoin/gui/community_tab.py" line="257"/>
+        <source>UID Revoking</source>
+        <translation>Révocation de votre UID</translation>
+    </message>
+    <message>
+        <location filename="../../../src/cutecoin/gui/community_tab.py" line="257"/>
+        <source>Success revoking your UID</source>
+        <translation>Révocation de votre UID réussie</translation>
+    </message>
+    <message>
+        <location filename="../../../src/cutecoin/gui/community_tab.py" line="260"/>
+        <source>Revoke UID error</source>
+        <translation>Erreur lors de la révocation de votre UID</translation>
+    </message>
 </context>
 <context>
     <name>ConfigureContactDialog</name>
@@ -1037,22 +1076,22 @@ Le processus pour rejoindre la communauté devrait être refait à zéro.</trans
         <translation>Sauvegarder</translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="359"/>
+        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="364"/>
         <source>Export</source>
         <translation>Exporter</translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="172"/>
+        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="173"/>
         <source>Loading account {0}</source>
         <translation>Chargement du compte {0}</translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="235"/>
+        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="236"/>
         <source>Latest release : {version}</source>
         <translation>Dernière version : {version}</translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="239"/>
+        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="240"/>
         <source>
             &lt;p&gt;&lt;b&gt;{version_info}&lt;/b&gt;&lt;/p&gt;
             &lt;p&gt;&lt;a href={version_url}&gt;Download link&lt;/a&gt;&lt;/p&gt;
@@ -1063,7 +1102,7 @@ Le processus pour rejoindre la communauté devrait être refait à zéro.</trans
             </translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="244"/>
+        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="245"/>
         <source>
         &lt;h1&gt;Cutecoin&lt;/h1&gt;
 
@@ -1098,32 +1137,32 @@ Le processus pour rejoindre la communauté devrait être refait à zéro.</trans
         </translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="298"/>
+        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="299"/>
         <source>Edit</source>
         <translation>Editer</translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="301"/>
+        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="302"/>
         <source>Delete</source>
         <translation>Supprimer</translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="317"/>
+        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="318"/>
         <source>CuteCoin {0}</source>
         <translation>CuteCoin {0}</translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="341"/>
+        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="342"/>
         <source>CuteCoin {0} - Account : {1}</source>
         <translation>CuteCoin {0} - Compte : {1}</translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="357"/>
+        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="362"/>
         <source>Export an account</source>
         <translation>Exporter un compte</translation>
     </message>
     <message>
-        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="358"/>
+        <location filename="../../../src/cutecoin/gui/mainwindow.py" line="363"/>
         <source>All account files (*.acc)</source>
         <translation>Tout fichier de compte (*.acc)</translation>
     </message>
diff --git a/res/ui/community_tab.ui b/res/ui/community_tab.ui
index 82fa382f..aa2718dd 100644
--- a/res/ui/community_tab.ui
+++ b/res/ui/community_tab.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>457</width>
-    <height>369</height>
+    <width>636</width>
+    <height>404</height>
    </rect>
   </property>
   <property name="contextMenuPolicy">
@@ -126,6 +126,13 @@
              </property>
             </widget>
            </item>
+           <item>
+            <widget class="QPushButton" name="button_revoke_uid">
+             <property name="text">
+              <string>Revoke UID</string>
+             </property>
+            </widget>
+           </item>
            <item>
             <widget class="QPushButton" name="button_membership">
              <property name="text">
@@ -234,6 +241,22 @@
     </hint>
    </hints>
   </connection>
+  <connection>
+   <sender>button_revoke_uid</sender>
+   <signal>clicked()</signal>
+   <receiver>CommunityTabWidget</receiver>
+   <slot>revoke_uid()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>750</x>
+     <y>368</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>622</x>
+     <y>201</y>
+    </hint>
+   </hints>
+  </connection>
  </connections>
  <slots>
   <slot>identity_context_menu(QPoint)</slot>
@@ -241,5 +264,6 @@
   <slot>send_membership_leaving()</slot>
   <slot>search_text()</slot>
   <slot>publish_uid()</slot>
+  <slot>revoke_uid()</slot>
  </slots>
 </ui>
diff --git a/src/cutecoin/core/account.py b/src/cutecoin/core/account.py
index 0aa477aa..ba749ac6 100644
--- a/src/cutecoin/core/account.py
+++ b/src/cutecoin/core/account.py
@@ -6,7 +6,7 @@ Created on 1 févr. 2014
 
 from ucoinpy import PROTOCOL_VERSION
 from ucoinpy.api import bma
-from ucoinpy.documents.certification import SelfCertification, Certification
+from ucoinpy.documents.certification import SelfCertification, Certification, Revocation
 from ucoinpy.documents.membership import Membership
 from ucoinpy.key import SigningKey
 
@@ -231,13 +231,13 @@ class Account(QObject):
             self.wallets = self.wallets[:size]
 
     def certify(self, password, community, pubkey):
-        '''
+        """
         Certify an other identity
 
         :param str password: The account SigningKey password
-        :param community: The community target of the certification
+        :param cutecoin.core.community.Community community: The community target of the certification
         :param str pubkey: The certified identity pubkey
-        '''
+        """
         certified = Person.lookup(pubkey, community)
         blockid = community.current_blockid()
 
@@ -259,6 +259,33 @@ class Account(QObject):
         logging.debug("Posted data : {0}".format(data))
         community.broadcast(bma.wot.Add, {}, data)
 
+    def revoke(self, password, community):
+        """
+        Revoke self-identity on server, not in blockchain
+
+        :param str password: The account SigningKey password
+        :param cutecoin.core.community.Community community: The community target of the revocation
+        """
+        revoked = Person.lookup(self.pubkey, community)
+
+        revocation = Revocation(PROTOCOL_VERSION, community.currency, None)
+
+        selfcert = revoked.selfcert(community)
+
+        key = SigningKey(self.salt, password)
+        revocation.sign(selfcert, [key])
+
+        logging.debug("Self-Revocation Document : \n{0}".format(selfcert.signed_raw() + revocation.raw()))
+        logging.debug("Signature : \n{0}".format(revocation.signatures[0]))
+
+        data = {
+            'pubkey': revoked.pubkey,
+            'self_': selfcert.signed_raw(),
+            'sig': revocation.signatures[0]
+        }
+        logging.debug("Posted data : {0}".format(data))
+        community.broadcast(bma.wot.Revoke, {}, data)
+
     def transfers(self, community):
         '''
         Get all transfers done in a community by all the wallets
diff --git a/src/cutecoin/gui/community_tab.py b/src/cutecoin/gui/community_tab.py
index df57be28..1241d151 100644
--- a/src/cutecoin/gui/community_tab.py
+++ b/src/cutecoin/gui/community_tab.py
@@ -15,7 +15,6 @@ from cutecoin.gui.contact import ConfigureContactDialog
 from cutecoin.gui.member import MemberDialog
 from .wot_tab import WotTabWidget
 from .transfer import TransferMoneyDialog
-from .password_asker import PasswordAskerDialog
 from .certification import CertificationDialog
 from . import toast
 from ..tools.exceptions import PersonNotFoundError, NoPeerAvailable
@@ -194,9 +193,8 @@ Sending a leaving demand  cannot be canceled.
 The process to join back the community later will have to be done again.""")
 .format(self.account.pubkey), QMessageBox.Ok | QMessageBox.Cancel)
         if reply == QMessageBox.Ok:
-            password_asker = PasswordAskerDialog(self.app.current_account)
-            password = password_asker.exec_()
-            if password_asker.result() == QDialog.Rejected:
+            password = self.password_asker.exec_()
+            if self.password_asker.result() == QDialog.Rejected:
                 return
 
             try:
@@ -204,7 +202,7 @@ The process to join back the community later will have to be done again.""")
                 toast.display(self.tr("Membership"), self.tr("Success sending leaving demand"))
             except ValueError as e:
                 QMessageBox.critical(self, self.tr("Leaving demand error"),
-                                  e.message)
+                                  str(e))
             except NoPeerAvailable as e:
                 QMessageBox.critical(self, self.tr("Network error"),
                                      self.tr("Couldn't connect to network : {0}").format(e),
@@ -217,12 +215,11 @@ The process to join back the community later will have to be done again.""")
     def publish_uid(self):
         reply = QMessageBox.warning(self, self.tr("Warning"),
                              self.tr("""Are you sure ?
-Publishing your UID cannot be canceled.""")
+Publishing your UID can be canceled by Revoke UID.""")
 .format(self.account.pubkey), QMessageBox.Ok | QMessageBox.Cancel)
         if reply == QMessageBox.Ok:
-            password_asker = PasswordAskerDialog(self.account)
-            password = password_asker.exec_()
-            if password_asker.result() == QDialog.Rejected:
+            password = self.password_asker.exec_()
+            if self.password_asker.result() == QDialog.Rejected:
                 return
 
             try:
@@ -230,8 +227,34 @@ Publishing your UID cannot be canceled.""")
                 toast.display(self.tr("UID Publishing"),
                               self.tr("Success publishing your UID"))
             except ValueError as e:
-                QMessageBox.critical(self, self.tr("Leaving demand error"),
-                                  e.message)
+                QMessageBox.critical(self, self.tr("Publish UID error"),
+                                  str(e))
+            except NoPeerAvailable as e:
+                QMessageBox.critical(self, self.tr("Network error"),
+                                     self.tr("Couldn't connect to network : {0}").format(e),
+                                     QMessageBox.Ok)
+            except Exception as e:
+                QMessageBox.critical(self, self.tr("Error"),
+                                     "{0}".format(e),
+                                     QMessageBox.Ok)
+
+    def revoke_uid(self):
+        reply = QMessageBox.warning(self, self.tr("Warning"),
+                                 self.tr("""Are you sure ?
+Revoking your UID can only success if it is not already validated by the network.""")
+.format(self.account.pubkey), QMessageBox.Ok | QMessageBox.Cancel)
+        if reply == QMessageBox.Ok:
+            password = self.password_asker.exec_()
+            if self.password_asker.result() == QDialog.Rejected:
+                return
+
+            try:
+                self.account.revoke(password, self.community)
+                toast.display(self.tr("UID Revoking"),
+                              self.tr("Success revoking your UID"))
+            except ValueError as e:
+                QMessageBox.critical(self, self.tr("Revoke UID error"),
+                                  str(e))
             except NoPeerAvailable as e:
                 QMessageBox.critical(self, self.tr("Network error"),
                                      self.tr("Couldn't connect to network : {0}").format(e),
-- 
GitLab