mirror of
https://github.com/chatmail/core.git
synced 2026-10-02 11:10:25 +03:00
feat: carry all published relay addresses in securejoin links (#8591)
Carry all published "secondary" addresses in `r` param of the securejoin URL. Closes: #8590 Signed-off-by: Jagoda Ślązak <jslazak@jslazak.com>
This commit is contained in:
@@ -43,6 +43,28 @@ def test_qr_setup_contact(acf, alice_and_remote_bob, version) -> None:
|
|||||||
alice2.wait_for_securejoin_inviter_success()
|
alice2.wait_for_securejoin_inviter_success()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("version", ["2.24.0"])
|
||||||
|
def test_qr_setup_contact_multitransport(acf, alice_and_remote_bob, version) -> None:
|
||||||
|
"""Test other-core Bob profile can do securejoin with Alice on current core, with multiple transports."""
|
||||||
|
alice, alice_contact_bob, remote_eval = alice_and_remote_bob(version)
|
||||||
|
relay_qr = acf.get_account_qr()
|
||||||
|
alice.add_transport_from_qr(relay_qr)
|
||||||
|
alice.add_transport_from_qr(relay_qr)
|
||||||
|
|
||||||
|
qr_code = alice.get_qr_code()
|
||||||
|
remote_eval(f"bob.secure_join({qr_code!r})")
|
||||||
|
alice.wait_for_securejoin_inviter_success()
|
||||||
|
|
||||||
|
# Test that Alice verified Bob's profile.
|
||||||
|
alice_contact_bob_snapshot = alice_contact_bob.get_snapshot()
|
||||||
|
assert alice_contact_bob_snapshot.is_verified
|
||||||
|
|
||||||
|
remote_eval("bob.wait_for_securejoin_joiner_success()")
|
||||||
|
|
||||||
|
# Test that Bob verified Alice's profile.
|
||||||
|
assert remote_eval("bob_contact_alice.get_snapshot().is_verified")
|
||||||
|
|
||||||
|
|
||||||
def test_send_and_receive_message(alice_and_remote_bob) -> None:
|
def test_send_and_receive_message(alice_and_remote_bob) -> None:
|
||||||
"""Test other-core Bob profile can send a message to Alice on current core."""
|
"""Test other-core Bob profile can send a message to Alice on current core."""
|
||||||
alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.23.0")
|
alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.23.0")
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import urllib.parse
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from deltachat_rpc_client import EventType
|
from deltachat_rpc_client import EventType
|
||||||
@@ -319,3 +321,33 @@ def test_remove_primary_transport(acf, log) -> None:
|
|||||||
assert msg2.text == "Hello again!"
|
assert msg2.text == "Hello again!"
|
||||||
assert msg2.chat.get_basic_snapshot().chat_type == ChatType.SINGLE
|
assert msg2.chat.get_basic_snapshot().chat_type == ChatType.SINGLE
|
||||||
assert msg2.chat == alice.create_chat(bob)
|
assert msg2.chat == alice.create_chat(bob)
|
||||||
|
|
||||||
|
|
||||||
|
def test_qr_works_after_removing_primary_transport(acf, log) -> None:
|
||||||
|
log.section("Alice setups an account and adds two additional relays")
|
||||||
|
alice = acf.new_configured_account()
|
||||||
|
relay_qr = acf.get_account_qr()
|
||||||
|
alice.add_transport_from_qr(relay_qr)
|
||||||
|
alice.add_transport_from_qr(relay_qr)
|
||||||
|
|
||||||
|
first_addr = alice.list_transports()[0]["addr"]
|
||||||
|
second_addr = alice.list_transports()[1]["addr"]
|
||||||
|
third_addr = alice.list_transports()[2]["addr"]
|
||||||
|
|
||||||
|
log.section("Alice creates a QR code")
|
||||||
|
chat_qr = alice.get_qr_code()
|
||||||
|
chat_qr_unquoted = urllib.parse.unquote(chat_qr)
|
||||||
|
assert f"&a={first_addr}" in chat_qr_unquoted
|
||||||
|
assert f"&r={third_addr},{second_addr}" in chat_qr_unquoted
|
||||||
|
|
||||||
|
log.section("Alice removes first and second transport")
|
||||||
|
alice.set_config("configured_addr", third_addr)
|
||||||
|
alice.delete_transport(first_addr)
|
||||||
|
alice.delete_transport(second_addr)
|
||||||
|
|
||||||
|
log.section("Bob scans the QR code, which still works")
|
||||||
|
alice.bring_online()
|
||||||
|
bob = acf.get_online_account()
|
||||||
|
bob.secure_join(chat_qr)
|
||||||
|
alice.wait_for_securejoin_inviter_success()
|
||||||
|
bob.wait_for_securejoin_joiner_success()
|
||||||
|
|||||||
@@ -463,6 +463,8 @@ pub fn format_backup(qr: &Qr) -> Result<String> {
|
|||||||
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH`
|
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH`
|
||||||
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&b=BROADCAST_NAME&x=BROADCAST_ID&j=INVITENUMBER&s=AUTH`
|
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&b=BROADCAST_NAME&x=BROADCAST_ID&j=INVITENUMBER&s=AUTH`
|
||||||
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR`
|
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR`
|
||||||
|
///
|
||||||
|
/// with optional `&r=ADDRS` param.
|
||||||
async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
||||||
let payload = qr
|
let payload = qr
|
||||||
.get(OPENPGP4FPR_SCHEME.len()..)
|
.get(OPENPGP4FPR_SCHEME.len()..)
|
||||||
@@ -492,10 +494,17 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let addr = if let Some(addr) = param.get("a") {
|
let addrs = {
|
||||||
Some(normalize_address(addr)?)
|
let mut addrs = Vec::new();
|
||||||
} else {
|
if let Some(primary_addr) = param.get("a") {
|
||||||
None
|
addrs.push(normalize_address(primary_addr)?);
|
||||||
|
};
|
||||||
|
if let Some(secondary_addrs_raw) = param.get("r") {
|
||||||
|
for secondary_address in secondary_addrs_raw.split(',') {
|
||||||
|
addrs.push(normalize_address(secondary_address)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addrs
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = decode_name(¶m, "n")?.unwrap_or_default();
|
let name = decode_name(¶m, "n")?.unwrap_or_default();
|
||||||
@@ -528,7 +537,9 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
invitenumber = Some("".to_string());
|
invitenumber = Some("".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (Some(addr), Some(invitenumber), Some(authcode)) = (&addr, invitenumber, authcode) {
|
if let (Some(addr), Some(invitenumber), Some(authcode)) =
|
||||||
|
(addrs.first(), invitenumber, authcode)
|
||||||
|
{
|
||||||
let addr = ContactAddress::new(addr)?;
|
let addr = ContactAddress::new(addr)?;
|
||||||
let (contact_id, _) = Contact::add_or_lookup_ext(
|
let (contact_id, _) = Contact::add_or_lookup_ext(
|
||||||
context,
|
context,
|
||||||
@@ -571,7 +582,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
grpid,
|
grpid,
|
||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
addrs: vec![addr.to_string()],
|
addrs,
|
||||||
invitenumber,
|
invitenumber,
|
||||||
authcode,
|
authcode,
|
||||||
is_v3,
|
is_v3,
|
||||||
@@ -608,7 +619,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
grpid,
|
grpid,
|
||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
addrs: vec![addr.to_string()],
|
addrs,
|
||||||
invitenumber,
|
invitenumber,
|
||||||
authcode,
|
authcode,
|
||||||
is_v3,
|
is_v3,
|
||||||
@@ -634,16 +645,16 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
Ok(Qr::AskVerifyContact {
|
Ok(Qr::AskVerifyContact {
|
||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
addrs: vec![addr.to_string()],
|
addrs,
|
||||||
invitenumber,
|
invitenumber,
|
||||||
authcode,
|
authcode,
|
||||||
is_v3,
|
is_v3,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if let Some(addr) = addr {
|
} else if let Some(addr) = addrs.first() {
|
||||||
let fingerprint = fingerprint.hex();
|
let fingerprint = fingerprint.hex();
|
||||||
let (contact_id, _) =
|
let (contact_id, _) =
|
||||||
Contact::add_or_lookup_ext(context, "", &addr, &fingerprint, Origin::UnhandledQrScan)
|
Contact::add_or_lookup_ext(context, "", addr, &fingerprint, Origin::UnhandledQrScan)
|
||||||
.await?;
|
.await?;
|
||||||
let contact = Contact::get_by_id(context, contact_id).await?;
|
let contact = Contact::get_by_id(context, contact_id).await?;
|
||||||
|
|
||||||
|
|||||||
+15
-3
@@ -139,6 +139,18 @@ pub async fn get_securejoin_qr(context: &Context, chat: Option<ChatId>) -> Resul
|
|||||||
let self_addr = context.get_primary_self_addr().await?;
|
let self_addr = context.get_primary_self_addr().await?;
|
||||||
let self_addr_urlencoded = utf8_percent_encode(&self_addr, DISALLOWED_CHARACTERS).to_string();
|
let self_addr_urlencoded = utf8_percent_encode(&self_addr, DISALLOWED_CHARACTERS).to_string();
|
||||||
|
|
||||||
|
let r_param = context
|
||||||
|
.get_published_secondary_self_addrs()
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.reduce(|acc, addr| {
|
||||||
|
format!(
|
||||||
|
"{acc},{}",
|
||||||
|
utf8_percent_encode(&addr, DISALLOWED_CHARACTERS)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map_or(String::default(), |addrs| format!("&r={addrs}"));
|
||||||
|
|
||||||
let self_name = context
|
let self_name = context
|
||||||
.get_config(Config::Displayname)
|
.get_config(Config::Displayname)
|
||||||
.await?
|
.await?
|
||||||
@@ -165,11 +177,11 @@ pub async fn get_securejoin_qr(context: &Context, chat: Option<ChatId>) -> Resul
|
|||||||
if chat.typ == Chattype::OutBroadcast {
|
if chat.typ == Chattype::OutBroadcast {
|
||||||
// For historic reansons, broadcasts currently use j instead of i for the invitenumber.
|
// For historic reansons, broadcasts currently use j instead of i for the invitenumber.
|
||||||
format!(
|
format!(
|
||||||
"https://i.delta.chat/#{fingerprint}&v=3&x={grpid}&j={invitenumber}&s={auth}&a={self_addr_urlencoded}&n={self_name_urlencoded}&b={chat_name_urlencoded}",
|
"https://i.delta.chat/#{fingerprint}&v=3&x={grpid}&j={invitenumber}&s={auth}&a={self_addr_urlencoded}{r_param}&n={self_name_urlencoded}&b={chat_name_urlencoded}",
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
format!(
|
format!(
|
||||||
"https://i.delta.chat/#{fingerprint}&v=3&x={grpid}&i={invitenumber}&s={auth}&a={self_addr_urlencoded}&n={self_name_urlencoded}&g={chat_name_urlencoded}",
|
"https://i.delta.chat/#{fingerprint}&v=3&x={grpid}&i={invitenumber}&s={auth}&a={self_addr_urlencoded}{r_param}&n={self_name_urlencoded}&g={chat_name_urlencoded}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -182,7 +194,7 @@ pub async fn get_securejoin_qr(context: &Context, chat: Option<ChatId>) -> Resul
|
|||||||
context.scheduler.interrupt_smtp().await;
|
context.scheduler.interrupt_smtp().await;
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"https://i.delta.chat/#{fingerprint}&v=3&i={invitenumber}&s={auth}&a={self_addr_urlencoded}&n={self_name_urlencoded}",
|
"https://i.delta.chat/#{fingerprint}&v=3&i={invitenumber}&s={auth}&a={self_addr_urlencoded}{r_param}&n={self_name_urlencoded}",
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user