diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 6f42b474b..1d15b4ca0 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -330,20 +330,35 @@ class Account(object): return from_dc_charpointer(res) def get_setup_contact_qr(self): - """ get/Create Setup-Contact QR Code as ascii-string """ + """ get/create Setup-Contact QR Code as ascii-string. + + this string needs to be transferred to another DC account + in a second channel (typically used by mobiles with QRcode-show + scan UX) + where setup_secure_contact(qr) is called. + """ res = lib.dc_get_securejoin_qr(self._dc_context, 0) return from_dc_charpointer(res) def check_qr(self, qr): - """ check qr code ...""" + """ check qr code and return :class:`ScannedQRCode` instance representing the result""" res = ffi.gc( lib.dc_check_qr(self._dc_context, as_dc_charpointer(qr)), lib.dc_lot_unref ) - return DCLot(res) + lot = DCLot(res) + if lot.state() == const.DC_QR_ERROR: + raise ValueError("invalid or unknown QR code: {}".format(lot.text1())) + return ScannedQRCode(lot) def setup_secure_contact(self, qr): - """ """ + """ setup secure contact and return a channel after contact is established. + + Note that this function may block for a long time as messages are exchanged + with the emitter of the QR code. On success a :class:`deltachat.chatting.Chat` instance + is returned. + :param qr: valid "setup contact" QR code (all other QR codes will result in an exception) + """ + assert self.check_qr(qr).is_ask_verifycontact() chat_id = lib.dc_join_securejoin(self._dc_context, as_dc_charpointer(qr)) if chat_id == 0: raise ValueError("could not setup secure contact") @@ -512,3 +527,18 @@ def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref): # we are deep into Python Interpreter shutdown, # so no need to clear the callback context mapping. pass + + +class ScannedQRCode: + def __init__(self, dc_lot): + self._dc_lot = dc_lot + + def is_ask_verifycontact(self): + return self._dc_lot.state() == const.DC_QR_ASK_VERIFYCONTACT + + def is_ask_verifygroup(self): + return self._dc_lot.state() == const.DC_QR_ASK_VERIFYGROUP + + @property + def contact_id(self): + return self._dc_lot.id() diff --git a/python/src/deltachat/const.py b/python/src/deltachat/const.py index b68a990d8..391747afe 100644 --- a/python/src/deltachat/const.py +++ b/python/src/deltachat/const.py @@ -13,6 +13,15 @@ DC_GCL_NO_SPECIALS = 0x02 DC_GCL_ADD_ALLDONE_HINT = 0x04 DC_GCL_VERIFIED_ONLY = 0x01 DC_GCL_ADD_SELF = 0x02 +DC_QR_ASK_VERIFYCONTACT = 200 +DC_QR_ASK_VERIFYGROUP = 202 +DC_QR_FPR_OK = 210 +DC_QR_FPR_MISMATCH = 220 +DC_QR_FPR_WITHOUT_ADDR = 230 +DC_QR_ADDR = 320 +DC_QR_TEXT = 330 +DC_QR_URL = 332 +DC_QR_ERROR = 400 DC_CHAT_ID_DEADDROP = 1 DC_CHAT_ID_TRASH = 3 DC_CHAT_ID_MSGS_IN_CREATION = 4 @@ -69,15 +78,13 @@ DC_EVENT_IMEX_FILE_WRITTEN = 2052 DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060 DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061 DC_EVENT_GET_STRING = 2091 -DC_EVENT_HTTP_GET = 2100 -DC_EVENT_HTTP_POST = 2110 DC_EVENT_FILE_COPIED = 2055 DC_EVENT_IS_OFFLINE = 2081 # end const generated def read_event_defines(f): - rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_MSG|DC_STATE_|DC_CONTACT_ID_|DC_GCL|DC_CHAT)\S+)\s+([x\d]+).*') + rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_STATE_|DC_CONTACT_ID_|DC_GCL|DC_CHAT)\S+)\s+([x\d]+).*') for line in f: m = rex.match(line) if m: @@ -90,7 +97,7 @@ if __name__ == "__main__": if len(sys.argv) >= 2: deltah = sys.argv[1] else: - deltah = joinpath(dirname(dirname(dirname(here_dir))), "src", "deltachat.h") + deltah = joinpath(dirname(dirname(dirname(here_dir))), "deltachat-ffi", "deltachat.h") assert os.path.exists(deltah) lines = [] diff --git a/python/tests/test_account.py b/python/tests/test_account.py index f3e66d16e..5809cffae 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -306,6 +306,19 @@ class TestOfflineChat: chat1.set_draft(None) assert chat1.get_draft() is None + def test_setup_contact(self, acfactory, lp): + # note that the receiving account needs to be configured and running + # before ther setup message is send. DC does not read old messages + # as of Jul2019 + ac1 = acfactory.get_configured_offline_account() + ac2 = acfactory.get_configured_offline_account() + qr = ac1.get_setup_contact_qr() + assert qr.startswith("OPENPGP4FPR:") + res = ac2.check_qr(qr) + assert res.is_ask_verifycontact() + assert not res.is_ask_verifygroup() + assert res.contact_id == 10 + class TestOnlineAccount: def test_one_account_init(self, acfactory): @@ -548,9 +561,6 @@ class TestOnlineAccount: assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"] def test_setup_contact(self, acfactory, lp): - # note that the receiving account needs to be configured and running - # before ther setup message is send. DC does not read old messages - # as of Jul2019 ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account() wait_configuration_progress(ac2, 1000) @@ -559,11 +569,6 @@ class TestOnlineAccount: qr = ac1.get_setup_contact_qr() assert qr.startswith("OPENPGP4FPR:") - - res = ac2.check_qr(qr) - assert res.state() == 200 - assert res.id() == 10 - lp.sec("ac2: start QR-code based setup contact protocol") ch = ac2.setup_secure_contact(qr) assert ch.id >= 10