Skip to main content

ruma_client_api/uiaa/auth_data/
data_serde.rs

1//! Custom Serialize / Deserialize implementations for the authentication data types.
2
3use std::borrow::Cow;
4
5use as_variant::as_variant;
6use ruma_common::{
7    serde::{JsonObject, from_raw_json_value},
8    thirdparty::Medium,
9};
10use serde::{Deserialize, Deserializer, Serialize, de};
11use serde_json::{Value as JsonValue, value::RawValue as RawJsonValue};
12
13use super::{
14    AuthData, CustomThirdPartyUserIdentifier, EmailUserIdentifier, MsisdnUserIdentifier,
15    UserIdentifier,
16};
17use crate::uiaa::{CustomAuthData, CustomUserIdentifier};
18
19impl<'de> Deserialize<'de> for AuthData {
20    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
21    where
22        D: Deserializer<'de>,
23    {
24        let json = Box::<RawJsonValue>::deserialize(deserializer)?;
25
26        #[derive(Deserialize)]
27        struct ExtractType<'a> {
28            #[serde(borrow, rename = "type")]
29            auth_type: Option<Cow<'a, str>>,
30        }
31
32        let ExtractType { auth_type } = from_raw_json_value(&json)?;
33
34        match auth_type.as_deref() {
35            Some("m.login.password") => from_raw_json_value(&json).map(Self::Password),
36            Some("m.login.recaptcha") => from_raw_json_value(&json).map(Self::ReCaptcha),
37            Some("m.login.email.identity") => from_raw_json_value(&json).map(Self::EmailIdentity),
38            Some("m.login.msisdn") => from_raw_json_value(&json).map(Self::Msisdn),
39            Some("m.login.dummy") => from_raw_json_value(&json).map(Self::Dummy),
40            Some("m.login.registration_token") => {
41                from_raw_json_value(&json).map(Self::RegistrationToken)
42            }
43            Some("m.login.terms") => from_raw_json_value(&json).map(Self::Terms),
44            Some("m.oauth" | "org.matrix.cross_signing_reset") => {
45                from_raw_json_value(&json).map(Self::OAuth)
46            }
47            None => from_raw_json_value(&json).map(Self::FallbackAcknowledgement),
48            Some(_) => {
49                let mut data = from_raw_json_value::<JsonObject, _>(&json)?;
50                let auth_type = as_variant!(
51                    data.remove("type").expect("we already checked that the type field is present"),
52                    JsonValue::String
53                )
54                .expect("we already checked that the type is a string");
55                let session = data
56                    .remove("session")
57                    .and_then(|session| as_variant!(session, JsonValue::String));
58
59                Ok(Self::_Custom(CustomAuthData { auth_type, session, data }))
60            }
61        }
62    }
63}
64
65impl<'de> Deserialize<'de> for UserIdentifier {
66    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
67    where
68        D: Deserializer<'de>,
69    {
70        #[derive(Deserialize)]
71        struct ExtractType<'a> {
72            #[serde(borrow, rename = "type")]
73            identifier_type: Cow<'a, str>,
74        }
75
76        let json = Box::<RawJsonValue>::deserialize(deserializer)?;
77        let ExtractType { identifier_type } =
78            serde_json::from_str(json.get()).map_err(de::Error::custom)?;
79
80        match identifier_type.as_ref() {
81            "m.id.user" => from_raw_json_value(&json).map(Self::Matrix),
82            "m.id.phone" => from_raw_json_value(&json).map(Self::PhoneNumber),
83            "m.id.thirdparty" => {
84                let id: CustomThirdPartyUserIdentifier = from_raw_json_value(&json)?;
85                match &id.medium {
86                    Medium::Email => Ok(Self::Email(EmailUserIdentifier { address: id.address })),
87                    Medium::Msisdn => Ok(Self::Msisdn(MsisdnUserIdentifier { number: id.address })),
88                    _ => Ok(Self::_CustomThirdParty(id)),
89                }
90            }
91            _ => {
92                let mut data = from_raw_json_value::<JsonObject, _>(&json)?;
93                let identifier_type = as_variant!(
94                    data.remove("type").expect("we already checked that the type field is present"),
95                    JsonValue::String
96                )
97                .expect("we already checked that the type is a string");
98
99                Ok(Self::_Custom(CustomUserIdentifier { identifier_type, data }))
100            }
101        }
102    }
103}
104
105impl Serialize for EmailUserIdentifier {
106    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
107    where
108        S: serde::Serializer,
109    {
110        let Self { address } = self;
111
112        CustomThirdPartyUserIdentifier { medium: Medium::Email, address: address.clone() }
113            .serialize(serializer)
114    }
115}
116
117impl<'de> Deserialize<'de> for EmailUserIdentifier {
118    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
119    where
120        D: Deserializer<'de>,
121    {
122        let CustomThirdPartyUserIdentifier { medium, address } =
123            CustomThirdPartyUserIdentifier::deserialize(deserializer)?;
124
125        if medium != Medium::Email {
126            return Err(de::Error::invalid_value(
127                de::Unexpected::Str(medium.as_str()),
128                &Medium::Email.as_str(),
129            ));
130        }
131
132        Ok(Self { address })
133    }
134}
135
136impl Serialize for MsisdnUserIdentifier {
137    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
138    where
139        S: serde::Serializer,
140    {
141        let Self { number } = self;
142
143        CustomThirdPartyUserIdentifier { medium: Medium::Msisdn, address: number.clone() }
144            .serialize(serializer)
145    }
146}
147
148impl<'de> Deserialize<'de> for MsisdnUserIdentifier {
149    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
150    where
151        D: Deserializer<'de>,
152    {
153        let CustomThirdPartyUserIdentifier { medium, address } =
154            CustomThirdPartyUserIdentifier::deserialize(deserializer)?;
155
156        if medium != Medium::Msisdn {
157            return Err(de::Error::invalid_value(
158                de::Unexpected::Str(medium.as_str()),
159                &Medium::Msisdn.as_str(),
160            ));
161        }
162
163        Ok(Self { number: address })
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use assert_matches2::assert_let;
170    use ruma_common::canonical_json::assert_to_canonical_json_eq;
171    use serde_json::{Value as JsonValue, from_value as from_json_value, json};
172
173    use crate::uiaa::{
174        AuthData, EmailUserIdentifier, MatrixUserIdentifier, MsisdnUserIdentifier,
175        PhoneNumberUserIdentifier, UserIdentifier,
176    };
177
178    #[test]
179    fn serialize_user_identifier() {
180        assert_to_canonical_json_eq!(
181            UserIdentifier::Matrix(MatrixUserIdentifier::new("@user:notareal.hs".to_owned())),
182            json!({
183                "type": "m.id.user",
184                "user": "@user:notareal.hs",
185            })
186        );
187
188        assert_to_canonical_json_eq!(
189            UserIdentifier::PhoneNumber(PhoneNumberUserIdentifier::new(
190                "33".to_owned(),
191                "0102030405".to_owned()
192            )),
193            json!({
194                "type": "m.id.phone",
195                "country": "33",
196                "phone": "0102030405",
197            })
198        );
199
200        assert_to_canonical_json_eq!(
201            UserIdentifier::Email(EmailUserIdentifier::new("me@myprovider.net".to_owned())),
202            json!({
203                "type": "m.id.thirdparty",
204                "medium": "email",
205                "address": "me@myprovider.net",
206            })
207        );
208
209        assert_to_canonical_json_eq!(
210            UserIdentifier::Msisdn(MsisdnUserIdentifier::new("330102030405".to_owned())),
211            json!({
212                "type": "m.id.thirdparty",
213                "medium": "msisdn",
214                "address": "330102030405",
215            })
216        );
217
218        assert_to_canonical_json_eq!(
219            UserIdentifier::third_party_id("robot".into(), "01001110".to_owned()),
220            json!({
221                "type": "m.id.thirdparty",
222                "medium": "robot",
223                "address": "01001110",
224            })
225        );
226    }
227
228    #[test]
229    fn deserialize_user_identifier() {
230        let json = json!({
231            "type": "m.id.user",
232            "user": "@user:notareal.hs",
233        });
234        assert_let!(Ok(UserIdentifier::Matrix(id)) = from_json_value(json));
235        assert_eq!(id.user, "@user:notareal.hs");
236
237        let json = json!({
238            "type": "m.id.phone",
239            "country": "33",
240            "phone": "0102030405",
241        });
242        assert_let!(
243            Ok(UserIdentifier::PhoneNumber(PhoneNumberUserIdentifier { country, phone })) =
244                from_json_value(json)
245        );
246        assert_eq!(country, "33");
247        assert_eq!(phone, "0102030405");
248
249        let json = json!({
250            "type": "m.id.thirdparty",
251            "medium": "email",
252            "address": "me@myprovider.net",
253        });
254        assert_let!(Ok(UserIdentifier::Email(id)) = from_json_value(json));
255        assert_eq!(id.address, "me@myprovider.net");
256
257        let json = json!({
258            "type": "m.id.thirdparty",
259            "medium": "msisdn",
260            "address": "330102030405",
261        });
262        assert_let!(Ok(UserIdentifier::Msisdn(id)) = from_json_value(json));
263        assert_eq!(id.number, "330102030405");
264
265        let json = json!({
266            "type": "m.id.thirdparty",
267            "medium": "robot",
268            "address": "01110010",
269        });
270        let id = from_json_value::<UserIdentifier>(json).unwrap();
271        let (medium, address) = id.as_third_party_id().unwrap();
272        assert_eq!(medium.as_str(), "robot");
273        assert_eq!(address, "01110010");
274    }
275
276    #[test]
277    fn custom_user_identifier_roundtrip() {
278        let json = json!({
279            "type": "local.dev.identifier",
280            "foo": "bar",
281        });
282
283        let id = from_json_value::<UserIdentifier>(json.clone()).unwrap();
284        assert_eq!(id.identifier_type(), "local.dev.identifier");
285        let data = &*id.data();
286        assert_let!(Some(JsonValue::String(foo)) = data.get("foo"));
287        assert_eq!(foo, "bar");
288
289        assert_to_canonical_json_eq!(id, json);
290    }
291
292    #[test]
293    fn custom_auth_data_roundtrip() {
294        let json = json!({
295            "type": "local.dev.auth",
296            "session": "abcdef",
297            "foo": "bar",
298        });
299
300        let auth_data = from_json_value::<AuthData>(json.clone()).unwrap();
301        assert_eq!(auth_data.auth_type().unwrap().as_str(), "local.dev.auth");
302        assert_eq!(auth_data.session(), Some("abcdef"));
303        let data = auth_data.data();
304        assert_eq!(data.len(), 1);
305        assert_let!(Some(JsonValue::String(foo)) = data.get("foo"));
306        assert_eq!(foo, "bar");
307
308        assert_to_canonical_json_eq!(auth_data, json);
309    }
310}