Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions deltachat-jsonrpc/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1091,19 +1091,6 @@ impl CommandApi {
chat::create_group(&ctx, &name).await.map(|id| id.to_u32())
}

/// Create a new encrypted group chat with an admin.
///
/// Similar to [`Self::create_group_chat`], but only the creator (admin) can add/remove
/// members or change the group name, description, avatar, etc.
/// Other members can send messages and leave the group.
/// The `group_admin_id` field in [`FullChat`] will be set to the admin's contact ID.
async fn create_group_with_admin(&self, account_id: u32, name: String) -> Result<u32> {
let ctx = self.get_context(account_id).await?;
chat::create_group_with_admin(&ctx, &name)
.await
.map(|id| id.to_u32())
}

/// Create a new unencrypted group chat.
///
/// Same as [`Self::create_group_chat`], but the chat is unencrypted and can only have
Expand Down
10 changes: 1 addition & 9 deletions deltachat-jsonrpc/src/api/types/chat.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::time::{Duration, SystemTime};

use anyhow::{bail, Context as _, Result};
use deltachat::chat::{self, get_chat_contacts, get_past_chat_contacts, ChatVisibility, get_admin_contact_id};
use deltachat::chat::{self, get_chat_contacts, get_past_chat_contacts, ChatVisibility};
use deltachat::chat::{Chat, ChatId};
use deltachat::constants::Chattype;
use deltachat::contact::{Contact, ContactId};
Expand Down Expand Up @@ -71,9 +71,6 @@ pub struct FullChat {
can_send: bool,
was_seen_recently: bool,
mailing_list_address: Option<String>,

/// Contact ID of the group admin for admin-controlled groups, or `null` for regular groups.
group_admin_id: Option<u32>,
}

impl FullChat {
Expand Down Expand Up @@ -109,10 +106,6 @@ impl FullChat {

let mailing_list_address = chat.get_mailinglist_addr().map(|s| s.to_string());

let group_admin_id = get_admin_contact_id(context, &chat.grpid)
.await?
.map(|id| id.to_u32());

Ok(FullChat {
id: chat_id,
name: chat.name.clone(),
Expand All @@ -135,7 +128,6 @@ impl FullChat {
can_send,
was_seen_recently,
mailing_list_address,
group_admin_id,
})
}
}
Expand Down
111 changes: 0 additions & 111 deletions src/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,26 +57,6 @@ use crate::webxdc::StatusUpdateSerial;

pub(crate) const PARAM_BROADCAST_SECRET: Param = Param::Arg3;

/// Separator between the admin's fingerprint and the random group ID in admin groups.
///
/// Must not be a URL-safe base64 character (`A-Za-z0-9-_`)
pub(crate) const ADMIN_GROUP_ID_SEPARATOR: char = ':';

/// Returns the admin fingerprint if this is an admin group.
pub(crate) fn admin_group_fingerprint(grpid: &str) -> Option<&str> {
grpid.split_once(ADMIN_GROUP_ID_SEPARATOR).map(|(fpr, _)| fpr)
}

/// Returns the base group ID (the random part after the fingerprint), for use in QR codes.
///
/// For regular groups, returns the full grpid unchanged.
pub(crate) fn admin_group_base_id(grpid: &str) -> &str {
grpid
.split_once(ADMIN_GROUP_ID_SEPARATOR)
.map(|(_, id)| id)
.unwrap_or(grpid)
}

/// An chat item, such as a message or a marker.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ChatItem {
Expand Down Expand Up @@ -3563,56 +3543,6 @@ pub async fn create_group(context: &Context, name: &str) -> Result<ChatId> {
create_group_ex(context, Sync, create_id(), name).await
}

/// Creates an encrypted group chat with an admin.
///
/// The group ID is composed of the creator's fingerprint and a random ID separated by
/// [`ADMIN_GROUP_ID_SEPARATOR`].
/// Only the admin (creator) can add/remove members or change the group name, description,
/// or avatar. Other members can send messages and leave the group.
pub async fn create_group_with_admin(context: &Context, name: &str) -> Result<ChatId> {
let fingerprint = self_fingerprint(context).await?;
let base_grpid = create_id();
let grpid = format!("{fingerprint}{ADMIN_GROUP_ID_SEPARATOR}{base_grpid}");
let result = create_group_ex(context, Sync, grpid, name).await;
if let Ok(chat_id) = result {
let secret = create_broadcast_secret();
save_broadcast_secret(context, chat_id, &secret).await?;
Ok(chat_id)
} else {
result
}
}

/// Returns the contact ID of the group admin for an admin group, or `None` for regular groups.
///
/// For admin groups (where `grpid` is `FINGERPRINT.GRPID`), this looks up the contact
/// whose key fingerprint matches the fingerprint in the group ID.
/// Returns [`ContactId::SELF`] if the admin is the current user.
pub async fn get_admin_contact_id(context: &Context, grpid: &str) -> Result<Option<ContactId>> {
let Some(admin_fpr) = admin_group_fingerprint(grpid) else {
return Ok(None);
};

// Check self first (cheaper and handles the case where the contact is not yet in the DB).
use crate::key::self_fingerprint_opt;
if let Some(self_fpr) = self_fingerprint_opt(context).await? {
if self_fpr == admin_fpr {
return Ok(Some(ContactId::SELF));
}
}

// `admin_group_fingerprint` guarantees admin_fpr is non-empty.
let contact_id = context
.sql
.query_row_optional(
"SELECT id FROM contacts WHERE fingerprint=?",
(admin_fpr,),
|row| row.get::<_, ContactId>(0),
)
.await?;
Ok(contact_id)
}

/// Creates an unencrypted group chat.
pub async fn create_group_unencrypted(context: &Context, name: &str) -> Result<ChatId> {
create_group_ex(context, Sync, String::new(), name).await
Expand Down Expand Up @@ -3982,15 +3912,6 @@ pub(crate) async fn add_contact_to_chat_ex(
);
return Ok(false);
}

if let Some(admin_fpr) = admin_group_fingerprint(&chat.grpid) {
let self_fpr = self_fingerprint(context).await?;
ensure!(
self_fpr == admin_fpr,
"Only the group admin can add other members to this group"
);
}

if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
let now = time();
chat.param
Expand Down Expand Up @@ -4247,16 +4168,6 @@ pub async fn remove_contact_from_chat(
bail!("{err_msg}");
}

if contact_id != ContactId::SELF {
if let Some(admin_fpr) = admin_group_fingerprint(&chat.grpid) {
let self_fpr = self_fingerprint(context).await?;
ensure!(
self_fpr == admin_fpr,
"Only the group admin can remove other members from this group"
);
}
}

let mut sync = Nosync;

let removed = if chat.is_promoted() && chat.typ != Chattype::OutBroadcast {
Expand Down Expand Up @@ -4371,14 +4282,6 @@ async fn set_chat_description_ex(
bail!("Cannot set chat description; self not in group");
}

if let Some(admin_fpr) = admin_group_fingerprint(&chat.grpid) {
let self_fpr = self_fingerprint(context).await?;
ensure!(
self_fpr == admin_fpr,
"Only the group admin can change the description of this group"
);
}

let old_description = get_chat_description(context, chat_id).await?;
if old_description == new_description {
return Ok(());
Expand Down Expand Up @@ -4467,13 +4370,6 @@ async fn rename_ex(
"Cannot set chat name; self not in group".into(),
));
} else {
if let Some(admin_fpr) = admin_group_fingerprint(&chat.grpid) {
let self_fpr = self_fingerprint(context).await?;
ensure!(
self_fpr == admin_fpr,
"Only the group admin can rename this group"
);
}
context
.sql
.execute(
Expand Down Expand Up @@ -4545,13 +4441,6 @@ pub async fn set_chat_profile_image(
));
bail!("Failed to set profile image");
}
if let Some(admin_fpr) = admin_group_fingerprint(&chat.grpid) {
let self_fpr = self_fingerprint(context).await?;
ensure!(
self_fpr == admin_fpr,
"Only the group admin can change the profile image of this group"
);
}
let mut msg = Message::new(Viewtype::Text);
msg.param
.set_int(Param::Cmd, SystemMessage::GroupImageChanged as i32);
Expand Down
13 changes: 1 addition & 12 deletions src/ephemeral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ use async_channel::Receiver;
use serde::{Deserialize, Serialize};
use tokio::time::timeout;

use crate::chat::{Chat, ChatId, ChatIdBlocked, admin_group_fingerprint, send_msg};
use crate::chat::{ChatId, ChatIdBlocked, send_msg};
use crate::config::Config;
use crate::constants::{DC_CHAT_ID_LAST_SPECIAL, DC_CHAT_ID_TRASH};
use crate::contact::ContactId;
Expand Down Expand Up @@ -210,17 +210,6 @@ impl ChatId {
///
/// If timer value is 0, disable ephemeral message timer.
pub async fn set_ephemeral_timer(self, context: &Context, timer: Timer) -> Result<()> {
ensure!(!self.is_special(), "Invalid chat ID");

let chat = Chat::load_from_db(context, self).await?;
if admin_group_fingerprint(&chat.grpid).is_some() {
ensure!(
crate::chat::get_admin_contact_id(context, &chat.grpid).await?
== Some(ContactId::SELF),
"Only the group admin can change the ephemeral timer of this group"
);
}

if timer == self.get_ephemeral_timer(context).await? {
return Ok(());
}
Expand Down
4 changes: 2 additions & 2 deletions src/mimeparser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use crate::message::{self, Message, MsgId, Viewtype, get_vcard_summary, set_msg_
use crate::param::{Param, Params};
use crate::simplify::{SimplifiedText, simplify};
use crate::sync::SyncItems;
use crate::tools::{get_filemeta, parse_receive_headers, time, truncate_msg_text, validate_group_id};
use crate::tools::{get_filemeta, parse_receive_headers, time, truncate_msg_text, validate_id};
use crate::{chatlist_events, location, tools};

/// Public key extracted from `Autocrypt-Gossip`
Expand Down Expand Up @@ -1065,7 +1065,7 @@ impl MimeMessage {
/// Returns `Chat-Group-ID` header value if it is a valid group ID.
pub fn get_chat_group_id(&self) -> Option<&str> {
self.get_header(HeaderDef::ChatGroupId)
.filter(|s| validate_group_id(s))
.filter(|s| validate_id(s))
}

async fn parse_mime_recursive<'a>(
Expand Down
20 changes: 4 additions & 16 deletions src/qr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use rand::TryRngCore as _;
use rand::distr::{Alphanumeric, SampleString};
use serde::Deserialize;

use crate::chat::ADMIN_GROUP_ID_SEPARATOR;
use crate::config::Config;
use crate::contact::{Contact, ContactId, Origin};
use crate::context::Context;
Expand Down Expand Up @@ -512,25 +511,14 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
.get("s")
.filter(|&s| validate_id(s))
.map(|s| s.to_string());
let grpid = param
.get("x")
.filter(|&s| validate_id(s))
.map(|s| s.to_string());

let grpname = decode_name(&param, "g")?;
let admin_grpname = decode_name(&param, "z")?;
let broadcast_name = decode_name(&param, "b")?;

// For admin groups, reconstruct the full grpid as FINGERPRINT<SEP>base_grpid.
let grpid = if admin_grpname.is_some() {
param
.get("x")
.filter(|&s| validate_id(s))
.map(|base_id| format!("{}{ADMIN_GROUP_ID_SEPARATOR}{base_id}", fingerprint.hex()))
} else {
param
.get("x")
.filter(|&s| validate_id(s))
.map(|s| s.to_string())
};
let grpname = grpname.or(admin_grpname);

let mut is_v3 = param.get("v") == Some(&"3");

if authcode.is_some() && invitenumber.is_none() {
Expand Down
Loading
Loading