Skip to main content

tuwunel_admin/
utils.rs

1use std::borrow::Cow;
2
3use futures::StreamExt;
4use ruma::{OwnedRoomId, OwnedUserId, RoomId, UserId};
5use tuwunel_core::{Err, Result, err, utils::BoolExt};
6use tuwunel_service::Services;
7
8const UNFEDERATABLE_CAVEAT: &str = ", but this room's `m.room.create` event sets `m.federate` \
9                                    to false, so remote users still cannot join it or be \
10                                    invited to it. That property is fixed when the room is \
11                                    created and no command can change it.";
12
13pub(crate) async fn get_room_info(
14	services: &Services,
15	room_id: &RoomId,
16) -> (OwnedRoomId, u64, String) {
17	let join_count = services
18		.state_cache
19		.room_joined_count(room_id)
20		.await
21		.unwrap_or(0);
22
23	let name = match services.state_accessor.get_name(room_id).await {
24		| Ok(name) => name,
25		| Err(_) if join_count == 2 => services
26			.state_cache
27			.room_members(room_id)
28			.map(ToString::to_string)
29			.collect::<Vec<_>>()
30			.await
31			.join(", "),
32		| Err(_) => room_id.to_string(),
33	};
34
35	(room_id.into(), join_count, name)
36}
37
38/// Builds the reply for a command that re-enables a room's inbound
39/// federation handling.
40///
41/// A room whose create event sets `m.federate` to false still cannot
42/// federate afterward, so the reply for such a room extends `prefix` with
43/// that caveat rather than confirming with `confirmation`. `confirmation`
44/// is a complete sentence; `prefix` is a fragment the caveat continues.
45pub(crate) async fn room_enabled_reply(
46	services: &Services,
47	room_id: &RoomId,
48	confirmation: &'static str,
49	prefix: &'static str,
50) -> Cow<'static, str> {
51	services
52		.state_accessor
53		.is_federating(room_id)
54		.await
55		.map_or_else(|| format!("{prefix}{UNFEDERATABLE_CAVEAT}").into(), || confirmation.into())
56}
57
58/// Parses user ID that is an active (not guest or deactivated) local user.
59///
60/// An account with an empty password reads as deactivated, which is how a
61/// guest is refused along with a deactivated account.
62pub(crate) async fn parse_active_local_user_id(
63	services: &Services,
64	user_id: &str,
65) -> Result<OwnedUserId> {
66	let user_id = parse_existing_local_user_id(services, user_id).await?;
67
68	if services.users.is_deactivated(&user_id).await? {
69		return Err!("User {user_id:?} is deactivated.");
70	}
71
72	Ok(user_id)
73}
74
75/// Parses user ID as our local user holding an account, deactivated or not.
76///
77/// A command writing for an absent user would create the account or leave
78/// state behind for whoever registers the name later, so it refuses here.
79pub(crate) async fn parse_existing_local_user_id(
80	services: &Services,
81	user_id: &str,
82) -> Result<OwnedUserId> {
83	let user_id = parse_user_id(services, user_id)?;
84
85	check_existing_local_user(services, &user_id).await?;
86
87	Ok(user_id)
88}
89
90/// Parses user ID as our local user.
91///
92/// The account need not exist; a command that requires one takes
93/// `parse_existing_local_user_id` instead.
94pub(crate) fn parse_local_user_id(services: &Services, user_id: &str) -> Result<OwnedUserId> {
95	let user_id = parse_user_id(services, user_id)?;
96
97	if !services.globals.user_is_local(&user_id) {
98		return Err!("User {user_id:?} does not belong to our server.");
99	}
100
101	Ok(user_id)
102}
103
104/// Parses user ID.
105///
106/// The input is lowercased, and a bare localpart resolves against our server
107/// name.
108pub(crate) fn parse_user_id(services: &Services, user_id: &str) -> Result<OwnedUserId> {
109	UserId::parse_with_server_name(user_id.to_lowercase(), services.globals.server_name())
110		.map_err(|e| err!("The supplied username is not a valid username: {e}"))
111}
112
113/// Refuses a user unless they are local and hold an account.
114///
115/// The remote counterpart is `check_known_remote_user`.
116pub(crate) async fn check_existing_local_user(services: &Services, user_id: &UserId) -> Result {
117	if !services.globals.user_is_local(user_id) {
118		return Err!("User {user_id:?} does not belong to our server.");
119	}
120
121	if !services.users.exists(user_id).await {
122		return Err!("User {user_id:?} does not exist on this server.");
123	}
124
125	Ok(())
126}
127
128pub(crate) async fn check_known_remote_user(services: &Services, user_id: &UserId) -> Result {
129	if services.globals.user_is_local(user_id) {
130		return Err!("User {user_id:?} belongs to our server.");
131	}
132
133	if !services.users.exists(user_id).await {
134		return Err!("User {user_id:?} is not known to this server.");
135	}
136
137	Ok(())
138}