libsignal_service/push_service/
profile.rs

1use libsignal_protocol::Aci;
2use reqwest::Method;
3use serde::{Deserialize, Serialize};
4use zkgroup::profiles::{ProfileKeyCommitment, ProfileKeyVersion};
5
6use crate::{
7    configuration::Endpoint,
8    content::ServiceError,
9    push_service::AvatarWrite,
10    utils::{serde_base64, serde_optional_base64},
11};
12
13use super::{DeviceCapabilities, HttpAuthOverride, PushService, ReqwestExt};
14
15#[derive(Debug, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct SignalServiceProfile {
18    #[serde(default, with = "serde_optional_base64")]
19    pub identity_key: Option<Vec<u8>>,
20    #[serde(default, with = "serde_optional_base64")]
21    pub name: Option<Vec<u8>>,
22    #[serde(default, with = "serde_optional_base64")]
23    pub about: Option<Vec<u8>>,
24    #[serde(default, with = "serde_optional_base64")]
25    pub about_emoji: Option<Vec<u8>>,
26
27    // TODO: not sure whether this is via optional_base64
28    // #[serde(default, with = "serde_optional_base64")]
29    // pub payment_address: Option<Vec<u8>>,
30    pub avatar: Option<String>,
31    pub unidentified_access: Option<String>,
32
33    #[serde(default)]
34    pub unrestricted_unidentified_access: bool,
35
36    pub capabilities: DeviceCapabilities,
37}
38
39#[derive(Debug, Serialize)]
40#[serde(rename_all = "camelCase")]
41struct SignalServiceProfileWrite<'s> {
42    /// Hex-encoded
43    version: &'s str,
44    #[serde(with = "serde_base64")]
45    name: &'s [u8],
46    #[serde(with = "serde_base64")]
47    about: &'s [u8],
48    #[serde(with = "serde_base64")]
49    about_emoji: &'s [u8],
50    avatar: bool,
51    same_avatar: bool,
52    #[serde(with = "serde_base64")]
53    commitment: &'s [u8],
54}
55
56impl PushService {
57    pub async fn retrieve_profile_by_id(
58        &mut self,
59        address: Aci,
60        profile_key: Option<zkgroup::profiles::ProfileKey>,
61    ) -> Result<SignalServiceProfile, ServiceError> {
62        let path = if let Some(key) = profile_key {
63            let version =
64                bincode::serialize(&key.get_profile_key_version(address))?;
65            let version = std::str::from_utf8(&version)
66                .expect("hex encoded profile key version");
67            format!("/v1/profile/{}/{}", address.service_id_string(), version)
68        } else {
69            format!("/v1/profile/{}", address.service_id_string())
70        };
71        // TODO: set locale to en_US
72        self.request(
73            Method::GET,
74            Endpoint::service(path),
75            HttpAuthOverride::NoOverride,
76        )?
77        .send()
78        .await?
79        .service_error_for_status()
80        .await?
81        .json()
82        .await
83        .map_err(Into::into)
84    }
85
86    pub async fn retrieve_profile_avatar(
87        &mut self,
88        path: &str,
89    ) -> Result<impl futures::io::AsyncRead + Send + Unpin, ServiceError> {
90        self.get_from_cdn(0, path).await
91    }
92
93    pub async fn retrieve_groups_v2_profile_avatar(
94        &mut self,
95        path: &str,
96    ) -> Result<impl futures::io::AsyncRead + Send + Unpin, ServiceError> {
97        self.get_from_cdn(0, path).await
98    }
99
100    /// Writes a profile and returns the avatar URL, if one was provided.
101    ///
102    /// The name, about and emoji fields are encrypted with an [`ProfileCipher`][struct@crate::profile_cipher::ProfileCipher].
103    /// See [`AccountManager`][struct@crate::AccountManager] for a convenience method.
104    ///
105    /// Java equivalent: `writeProfile`
106    pub async fn write_profile<'s, C, S>(
107        &mut self,
108        version: &ProfileKeyVersion,
109        name: &[u8],
110        about: &[u8],
111        emoji: &[u8],
112        commitment: &ProfileKeyCommitment,
113        avatar: AvatarWrite<&mut C>,
114    ) -> Result<Option<String>, ServiceError>
115    where
116        C: std::io::Read + Send + 's,
117        S: AsRef<str>,
118    {
119        // Bincode is transparent and will return a hex-encoded string.
120        let version = bincode::serialize(version)?;
121        let version = std::str::from_utf8(&version)
122            .expect("profile_key_version is hex encoded string");
123        let commitment = bincode::serialize(commitment)?;
124
125        let command = SignalServiceProfileWrite {
126            version,
127            name,
128            about,
129            about_emoji: emoji,
130            avatar: !matches!(avatar, AvatarWrite::NoAvatar),
131            same_avatar: matches!(avatar, AvatarWrite::RetainAvatar),
132            commitment: &commitment,
133        };
134
135        // XXX this should  be a struct; cfr ProfileAvatarUploadAttributes
136        let upload_url: Result<String, _> = self
137            .request(
138                Method::PUT,
139                Endpoint::service("/v1/profile"),
140                HttpAuthOverride::NoOverride,
141            )?
142            .json(&command)
143            .send()
144            .await?
145            .service_error_for_status()
146            .await?
147            .json()
148            .await;
149
150        match (upload_url, avatar) {
151            (_url, AvatarWrite::NewAvatar(_avatar)) => {
152                // FIXME
153                unreachable!("Uploading avatar unimplemented");
154            },
155            // FIXME cleanup when #54883 is stable and MSRV:
156            // or-patterns syntax is experimental
157            // see issue #54883 <https://github.com/rust-lang/rust/issues/54883> for more information
158            (Err(_), AvatarWrite::RetainAvatar)
159            | (Err(_), AvatarWrite::NoAvatar) => {
160                // OWS sends an empty string when there's no attachment
161                Ok(None)
162            },
163            (Ok(_resp), AvatarWrite::RetainAvatar)
164            | (Ok(_resp), AvatarWrite::NoAvatar) => {
165                tracing::warn!(
166                    "No avatar supplied but got avatar upload URL. Ignoring"
167                );
168                Ok(None)
169            },
170        }
171    }
172}