Skip to content

Commit 047b4d1

Browse files
committed
api: Add list_transports_ex() and set_transport_unpublished() functions
Closes #7980. Unpublished transports are not advertised to contacts, and self-sent messages are not sent there, so that we don't cause extra messages to the corresponding inbox, but can still receive messages from contacts who don't know the new relay addresses yet. - This adds `list_transports_ex()` and `set_transport_unpublished()` JsonRPC functions - By default, transports are published, but when updating, all existing transports except for the primary one become unpublished in order not to break existing users that followed https://delta.chat/legacy-move - It is not possible to unpublish the primary transport, and setting a transport as primary automatically sets it to published An alternative would be to change the existing list_transports API rather than adding a new one list_transports_ex. But to be honest, I don't mind the _ex prefix that much, and I am wary about compatibility issues. But maybe it would be fine; see b08ba4b for how this would look.
1 parent ef19feb commit 047b4d1

File tree

15 files changed

+438
-46
lines changed

15 files changed

+438
-46
lines changed

deltachat-ffi/deltachat.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6706,8 +6706,8 @@ void dc_event_unref(dc_event_t* event);
67066706
* UI should update the list.
67076707
*
67086708
* The event is emitted when the transports are modified on another device
6709-
* using the JSON-RPC calls `add_or_update_transport`, `add_transport_from_qr`, `delete_transport`
6710-
* or `set_config(configured_addr)`.
6709+
* using the JSON-RPC calls `add_or_update_transport`, `add_transport_from_qr`, `delete_transport`,
6710+
* `set_transport_unpublished` or `set_config(configured_addr)`.
67116711
*/
67126712
#define DC_EVENT_TRANSPORTS_MODIFIED 2600
67136713

deltachat-jsonrpc/src/api.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ use self::types::{
6868
},
6969
};
7070
use crate::api::types::chat_list::{get_chat_list_item_by_id, ChatListItemFetchResult};
71+
use crate::api::types::login_param::Transport;
7172
use crate::api::types::qr::{QrObject, SecurejoinSource, SecurejoinUiPath};
7273

7374
#[derive(Debug)]
@@ -528,6 +529,7 @@ impl CommandApi {
528529
/// from a server encoded in a QR code.
529530
/// - [Self::list_transports()] to get a list of all configured transports.
530531
/// - [Self::delete_transport()] to remove a transport.
532+
/// - [Self::set_transport_unpublished()] to set whether contacts see this transport.
531533
async fn add_or_update_transport(
532534
&self,
533535
account_id: u32,
@@ -553,7 +555,23 @@ impl CommandApi {
553555
/// Returns the list of all email accounts that are used as a transport in the current profile.
554556
/// Use [Self::add_or_update_transport()] to add or change a transport
555557
/// and [Self::delete_transport()] to delete a transport.
558+
/// Use [Self::list_transports_ex()] to additionally query
559+
/// whether the transports are marked as 'unpublished'.
556560
async fn list_transports(&self, account_id: u32) -> Result<Vec<EnteredLoginParam>> {
561+
let ctx = self.get_context(account_id).await?;
562+
let res = ctx
563+
.list_transports()
564+
.await?
565+
.into_iter()
566+
.map(|t| t.param.into())
567+
.collect();
568+
Ok(res)
569+
}
570+
571+
/// Returns the list of all email accounts that are used as a transport in the current profile.
572+
/// Use [Self::add_or_update_transport()] to add or change a transport
573+
/// and [Self::delete_transport()] to delete a transport.
574+
async fn list_transports_ex(&self, account_id: u32) -> Result<Vec<Transport>> {
557575
let ctx = self.get_context(account_id).await?;
558576
let res = ctx
559577
.list_transports()
@@ -571,6 +589,26 @@ impl CommandApi {
571589
ctx.delete_transport(&addr).await
572590
}
573591

592+
/// Change whether the transport is unpublished.
593+
///
594+
/// Unpublished transports are not advertised to contacts,
595+
/// and self-sent messages are not sent there,
596+
/// so that we don't cause extra messages to the corresponding inbox,
597+
/// but can still receive messages from contacts who don't know our new transport addresses yet.
598+
///
599+
/// The default is false, but when the user updates from a version that didn't have this flag,
600+
/// existing secondary transports are set to unpublished,
601+
/// so that an existing transport address doesn't suddenly get spammed with a lot of messages.
602+
async fn set_transport_unpublished(
603+
&self,
604+
account_id: u32,
605+
addr: String,
606+
unpublished: bool,
607+
) -> Result<()> {
608+
let ctx = self.get_context(account_id).await?;
609+
ctx.set_transport_unpublished(&addr, unpublished).await
610+
}
611+
574612
/// Signal an ongoing process to stop.
575613
async fn stop_ongoing_process(&self, account_id: u32) -> Result<()> {
576614
let ctx = self.get_context(account_id).await?;

deltachat-jsonrpc/src/api/types/login_param.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ use serde::Deserialize;
44
use serde::Serialize;
55
use yerpc::TypeDef;
66

7+
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
8+
#[serde(rename_all = "camelCase")]
9+
pub struct Transport {
10+
/// The login data entered by the user.
11+
pub param: EnteredLoginParam,
12+
/// Whether this transport is set to 'unpublished'.
13+
/// See `set_transport_unpublished` / `setTransportUnpublished` for details.
14+
pub is_unpublished: bool,
15+
}
16+
717
/// Login parameters entered by the user.
818
///
919
/// Usually it will be enough to only set `addr` and `password`,
@@ -56,6 +66,15 @@ pub struct EnteredLoginParam {
5666
pub oauth2: Option<bool>,
5767
}
5868

69+
impl From<dc::Transport> for Transport {
70+
fn from(transport: dc::Transport) -> Self {
71+
Transport {
72+
param: transport.param.into(),
73+
is_unpublished: transport.is_unpublished,
74+
}
75+
}
76+
}
77+
5978
impl From<dc::EnteredLoginParam> for EnteredLoginParam {
6079
fn from(param: dc::EnteredLoginParam) -> Self {
6180
let imap_security: Socket = param.imap.security.into();

src/chat.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2844,17 +2844,12 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
28442844
let lowercase_from = from.to_lowercase();
28452845

28462846
recipients.retain(|x| x.to_lowercase() != lowercase_from);
2847-
if context.get_config_bool(Config::BccSelf).await? {
2848-
smtp::add_self_recipients(context, &mut recipients, needs_encryption).await?;
2849-
}
2850-
2851-
// Default Webxdc integrations are hidden messages and must not be sent out
2852-
if msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden {
2853-
recipients.clear();
2854-
}
28552847

2856-
if recipients.is_empty() {
2857-
// may happen eg. for groups with only SELF and bcc_self disabled
2848+
// Default Webxdc integrations are hidden messages and must not be sent out:
2849+
if (msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden)
2850+
// This may happen eg. for groups with only SELF and bcc_self disabled:
2851+
|| (!context.get_config_bool(Config::BccSelf).await? && recipients.is_empty())
2852+
{
28582853
info!(
28592854
context,
28602855
"Message {} has no recipient, skipping smtp-send.", msg.id
@@ -2893,6 +2888,10 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
28932888
);
28942889
}
28952890

2891+
if context.get_config_bool(Config::BccSelf).await? {
2892+
smtp::add_self_recipients(context, &mut recipients, rendered_msg.is_encrypted).await?;
2893+
}
2894+
28962895
if needs_encryption && !rendered_msg.is_encrypted {
28972896
/* unrecoverable */
28982897
message::set_msg_failed(

src/config.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -837,7 +837,7 @@ impl Context {
837837
// which only fetches from the primary transport.
838838
transaction
839839
.execute(
840-
"UPDATE transports SET add_timestamp=? WHERE addr=?",
840+
"UPDATE transports SET add_timestamp=?, is_published=1 WHERE addr=?",
841841
(time(), addr),
842842
)
843843
.context(
@@ -964,7 +964,22 @@ impl Context {
964964
pub(crate) async fn get_all_self_addrs(&self) -> Result<Vec<String>> {
965965
self.sql
966966
.query_map_vec(
967-
"SELECT addr FROM transports ORDER BY add_timestamp DESC",
967+
"SELECT addr FROM transports ORDER BY add_timestamp, id DESC",
968+
(),
969+
|row| {
970+
let addr: String = row.get(0)?;
971+
Ok(addr)
972+
},
973+
)
974+
.await
975+
}
976+
977+
/// Returns all published self addresses, newest first.
978+
/// See `[Context::set_transport_unpublished]`
979+
pub(crate) async fn get_published_self_addrs(&self) -> Result<Vec<String>> {
980+
self.sql
981+
.query_map_vec(
982+
"SELECT addr FROM transports WHERE is_published=1 ORDER BY add_timestamp, id DESC",
968983
(),
969984
|row| {
970985
let addr: String = row.get(0)?;
@@ -982,6 +997,24 @@ impl Context {
982997
}).await
983998
}
984999

1000+
/// Returns all published secondary self addresses.
1001+
/// See `[Context::set_transport_unpublished]`
1002+
pub(crate) async fn get_published_secondary_self_addrs(&self) -> Result<Vec<String>> {
1003+
self.sql
1004+
.query_map_vec(
1005+
"SELECT addr FROM transports
1006+
WHERE is_published
1007+
AND addr NOT IN (SELECT value FROM config WHERE keyname='configured_addr')
1008+
ORDER BY add_timestamp, id DESC",
1009+
(),
1010+
|row| {
1011+
let addr: String = row.get(0)?;
1012+
Ok(addr)
1013+
},
1014+
)
1015+
.await
1016+
}
1017+
9851018
/// Returns the primary self address.
9861019
/// Returns an error if no self addr is configured.
9871020
pub async fn get_primary_self_addr(&self) -> Result<String> {

src/configure.rs

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ use crate::constants::NON_ALPHANUMERIC_WITHOUT_DOT;
2828
use crate::context::Context;
2929
use crate::imap::Imap;
3030
use crate::log::warn;
31-
use crate::login_param::EnteredCertificateChecks;
3231
pub use crate::login_param::EnteredLoginParam;
32+
use crate::login_param::{EnteredCertificateChecks, Transport};
3333
use crate::message::Message;
3434
use crate::net::proxy::ProxyConfig;
3535
use crate::oauth2::get_oauth2_addr;
@@ -110,6 +110,7 @@ impl Context {
110110
/// from a server encoded in a QR code.
111111
/// - [Self::list_transports()] to get a list of all configured transports.
112112
/// - [Self::delete_transport()] to remove a transport.
113+
/// - [Self::set_transport_unpublished()] to set whether contacts see this transport.
113114
pub async fn add_or_update_transport(&self, param: &mut EnteredLoginParam) -> Result<()> {
114115
self.stop_io().await;
115116
let result = self.add_transport_inner(param).await;
@@ -188,14 +189,22 @@ impl Context {
188189
/// Returns the list of all email accounts that are used as a transport in the current profile.
189190
/// Use [Self::add_or_update_transport()] to add or change a transport
190191
/// and [Self::delete_transport()] to delete a transport.
191-
pub async fn list_transports(&self) -> Result<Vec<EnteredLoginParam>> {
192+
pub async fn list_transports(&self) -> Result<Vec<Transport>> {
192193
let transports = self
193194
.sql
194-
.query_map_vec("SELECT entered_param FROM transports", (), |row| {
195-
let entered_param: String = row.get(0)?;
196-
let transport: EnteredLoginParam = serde_json::from_str(&entered_param)?;
197-
Ok(transport)
198-
})
195+
.query_map_vec(
196+
"SELECT entered_param, is_published FROM transports",
197+
(),
198+
|row| {
199+
let param: String = row.get(0)?;
200+
let param: EnteredLoginParam = serde_json::from_str(&param)?;
201+
let is_published: bool = row.get(1)?;
202+
Ok(Transport {
203+
param,
204+
is_unpublished: !is_published,
205+
})
206+
},
207+
)
199208
.await?;
200209

201210
Ok(transports)
@@ -261,6 +270,44 @@ impl Context {
261270
Ok(())
262271
}
263272

273+
/// Change whether the transport is unpublished.
274+
///
275+
/// Unpublished transports are not advertised to contacts,
276+
/// and self-sent messages are not sent there,
277+
/// so that we don't cause extra messages to the corresponding inbox,
278+
/// but can still receive messages from contacts who don't know our new transport addresses yet.
279+
///
280+
/// The default is false, but when the user updates from a version that didn't have this flag,
281+
/// existing secondary transports are set to unpublished,
282+
/// so that an existing transport address doesn't suddenly get spammed with a lot of messages.
283+
pub async fn set_transport_unpublished(&self, addr: &str, unpublished: bool) -> Result<()> {
284+
self.sql
285+
.transaction(|trans| {
286+
let primary_addr: String = trans
287+
.query_row(
288+
"SELECT value FROM config WHERE keyname='configured_addr'",
289+
(),
290+
|row| row.get(0),
291+
)
292+
.context("Select primary address")?;
293+
if primary_addr == addr && unpublished {
294+
bail!("Can't set primary relay as unpublished");
295+
}
296+
// We need to update the timestamp so that the key's timestamp changes
297+
// and is recognized as newer by our peers
298+
trans
299+
.execute(
300+
"UPDATE transports SET is_published=?, add_timestamp=? WHERE addr=? AND is_published!=?1",
301+
(!unpublished, time(), addr),
302+
)
303+
.context("Update transports")?;
304+
Ok(())
305+
})
306+
.await?;
307+
send_sync_transports(self).await?;
308+
Ok(())
309+
}
310+
264311
async fn inner_configure(&self, param: &EnteredLoginParam) -> Result<()> {
265312
info!(self, "Configure ...");
266313

src/key.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ pub(crate) async fn load_self_public_key_opt(context: &Context) -> Result<Option
296296
.await?
297297
.context("No transports configured")?;
298298
let addr = context.get_primary_self_addr().await?;
299-
let all_addrs = context.get_all_self_addrs().await?.join(",");
299+
let all_addrs = context.get_published_self_addrs().await?.join(",");
300300
let signed_public_key =
301301
secret_key_to_public_key(context, signed_secret_key, timestamp, &addr, &all_addrs)?;
302302
*lock = Some(signed_public_key.clone());

src/login_param.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@ pub struct EnteredServerLoginParam {
7979
pub password: String,
8080
}
8181

82+
/// A transport, as shown in the "relays" list in the UI.
83+
#[derive(Debug)]
84+
pub struct Transport {
85+
/// The login data entered by the user.
86+
pub param: EnteredLoginParam,
87+
/// Whether this transport is set to 'unpublished'.
88+
/// See [`Context::set_transport_unpublished`] for details.
89+
pub is_unpublished: bool,
90+
}
91+
8292
/// Login parameters entered by the user.
8393
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
8494
pub struct EnteredLoginParam {

src/mimeparser/shared_secret_decryption_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ async fn test_qr_code_security() -> Result<()> {
204204
let charlie_addr = charlie.get_config(Config::Addr).await?.unwrap();
205205

206206
let alice_fp = self_fingerprint(alice).await?;
207-
let secret_for_encryption = dbg!(format!("securejoin/{alice_fp}/{authcode}"));
207+
let secret_for_encryption = format!("securejoin/{alice_fp}/{authcode}");
208208
test_shared_secret_decryption_ex(
209209
bob,
210210
&charlie_addr,

src/smtp.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,7 @@ pub(crate) async fn add_self_recipients(
706706
// them. Normally the user should have a non-chatmail primary transport to send unencrypted
707707
// messages.
708708
if encrypted {
709-
for addr in context.get_secondary_self_addrs().await? {
709+
for addr in context.get_published_secondary_self_addrs().await? {
710710
recipients.push(addr);
711711
}
712712
}

0 commit comments

Comments
 (0)