1use std::collections::BTreeMap;
2
3use axum::extract::State;
4use futures::{StreamExt, future::join};
5use ruma::{
6 OneTimeKeyAlgorithm, OwnedDeviceId, OwnedOneTimeKeyId, OwnedUserId, ServerName, UserId,
7 api::{
8 client::keys::claim_keys,
9 federation::keys::claim_keys::v1::{
10 Request as FederationRequest, Response as FederationResponse,
11 },
12 },
13 encryption::OneTimeKey,
14 serde::Raw,
15};
16use tuwunel_core::{
17 Result,
18 utils::{IterStream, stream::BroadbandExt},
19};
20use tuwunel_service::{
21 Services,
22 federation::feds::{OutcomeExt, fanout_with},
23};
24
25use super::{FailureMap, federation_failures, federation_opts};
26use crate::Ruma;
27
28#[derive(Default)]
29struct Claims {
30 one_time_keys: OneTimeKeyMap,
31 failures: FailureMap,
32}
33
34type RequestClaims = BTreeMap<OwnedUserId, Algorithms>;
35type ServerClaims<'a> = BTreeMap<&'a ServerName, RequestClaims>;
36type LocalClaim<'a> = (&'a UserId, &'a Algorithms);
37type Algorithms = BTreeMap<OwnedDeviceId, OneTimeKeyAlgorithm>;
38type OneTimeKeys = BTreeMap<OwnedOneTimeKeyId, Raw<OneTimeKey>>;
39type OneTimeKeyMap = BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, OneTimeKeys>>;
40
41pub(crate) async fn claim_keys_route(
45 State(services): State<crate::State>,
46 body: Ruma<claim_keys::v3::Request>,
47) -> Result<claim_keys::v3::Response> {
48 claim_keys_helper(&services, &body.one_time_keys).await
49}
50
51pub async fn claim_keys_helper(
55 services: &Services,
56 one_time_keys_input: &RequestClaims,
57) -> Result<claim_keys::v3::Response> {
58 let (local_users, remote_users): (Vec<_>, Vec<_>) = one_time_keys_input
59 .iter()
60 .map(|(uid, map)| (uid.as_ref(), map))
61 .partition(|(user_id, _)| services.globals.user_is_local(user_id));
62
63 let server: ServerClaims<'_> =
64 remote_users
65 .into_iter()
66 .fold(BTreeMap::new(), |mut acc, (user_id, map)| {
67 acc.entry(user_id.server_name())
68 .or_default()
69 .insert(user_id.to_owned(), map.clone());
70 acc
71 });
72
73 let local = collect_local_one_time_keys(services, &local_users);
74 let federation = collect_federation_one_time_keys(services, server);
75
76 let (local, federation) = join(local, federation).await;
77 let merged = local.merge(federation);
78
79 Ok(claim_keys::v3::Response {
80 failures: merged.failures,
81 one_time_keys: merged.one_time_keys,
82 })
83}
84
85async fn collect_local_one_time_keys(services: &Services, users: &[LocalClaim<'_>]) -> Claims {
86 let take_one_time_key = async |(user_id, device_id, algorithm)| {
87 let key = services
88 .users
89 .take_one_time_key(user_id, device_id, algorithm)
90 .await
91 .ok();
92
93 let key = match key {
95 | Some(key) => Some(key),
96 | None => services
97 .users
98 .take_fallback_key(user_id, device_id, algorithm)
99 .await
100 .ok(),
101 };
102
103 key.map(|key| (device_id.to_owned(), [key].into()))
104 };
105
106 let one_time_keys = users
107 .iter()
108 .copied()
109 .stream()
110 .broad_filter_map(async |(user_id, requested)| {
111 let device_keys: BTreeMap<_, _> = requested
112 .iter()
113 .stream()
114 .map(|(device_id, algorithm)| (user_id, device_id.as_ref(), algorithm))
115 .filter_map(take_one_time_key)
116 .collect()
117 .await;
118
119 (!device_keys.is_empty()).then(|| (user_id.to_owned(), device_keys))
121 })
122 .collect()
123 .await;
124
125 Claims { one_time_keys, ..Default::default() }
126}
127
128async fn collect_federation_one_time_keys(
129 services: &Services,
130 server: ServerClaims<'_>,
131) -> Claims {
132 let requests = server
133 .into_iter()
134 .stream()
135 .map(|(server, one_time_keys)| (server.to_owned(), FederationRequest { one_time_keys }));
136
137 let outcomes = fanout_with(
138 requests,
139 async |server, request| {
140 let response = services
141 .federation
142 .execute_keys(&server, request)
143 .await?;
144
145 Ok(retain_origin_users(&server, response))
146 },
147 federation_opts(services),
148 );
149
150 let (claims, faults) = outcomes
151 .merge(Claims::default(), |mut claims, response: FederationResponse| {
152 claims
153 .one_time_keys
154 .extend(response.one_time_keys);
155
156 claims
157 })
158 .await;
159
160 Claims {
161 failures: federation_failures("claim_keys", faults).collect(),
162 ..claims
163 }
164}
165
166fn retain_origin_users(
172 origin: &ServerName,
173 mut response: FederationResponse,
174) -> FederationResponse {
175 response
176 .one_time_keys
177 .retain(|user, _| user.server_name().eq(origin));
178
179 response
180}
181
182impl Claims {
183 fn merge(mut self, other: Self) -> Self {
184 self.one_time_keys.extend(other.one_time_keys);
185 self.failures.extend(other.failures);
186 self
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use ruma::{server_name, user_id};
193
194 use super::*;
195
196 #[test]
197 fn drops_one_time_keys_of_users_on_other_servers() {
198 let remote = user_id!("@bob:remote.example");
199 let users = [remote, user_id!("@alice:local.example"), user_id!("@carol:third.example")];
200 let one_time_keys = users.map(|user| (user.to_owned(), BTreeMap::new()));
201 let response = FederationResponse::new(one_time_keys.into());
202 let response = retain_origin_users(server_name!("remote.example"), response);
203
204 assert!(response.one_time_keys.keys().eq([remote]));
205 }
206}