tuwunel_admin/query/
oauth.rs1use clap::Subcommand;
2use futures::{StreamExt, TryStreamExt};
3use ruma::OwnedUserId;
4use tuwunel_core::{
5 Err, Result, apply,
6 either::{Either, Left, Right},
7 err,
8 itertools::Itertools,
9 utils::stream::{IterStream, ReadyExt},
10};
11use tuwunel_service::oauth::{Provider, ProviderId, SessionId};
12
13use crate::{admin_command, admin_command_dispatch};
14
15#[admin_command_dispatch(handler_prefix = "oauth")]
16#[derive(Debug, Subcommand)]
17pub(crate) enum OauthCommand {
19 Associate {
21 provider: String,
23
24 user_id: OwnedUserId,
26
27 #[arg(long, required = true)]
29 claim: Vec<String>,
30 },
31
32 ListProviders,
34
35 ListUsers,
37
38 ListSessions {
40 #[arg(long)]
41 user: Option<OwnedUserId>,
42 },
43
44 ShowProvider {
46 id: ProviderId,
47
48 #[arg(long)]
49 config: bool,
50 },
51
52 ShowSession {
54 id: SessionId,
55 },
56
57 ShowUser {
59 user_id: OwnedUserId,
60 },
61
62 TokenInfo {
64 id: SessionId,
65 },
66
67 Revoke {
69 #[arg(value_parser = session_or_user_id)]
70 id: Either<SessionId, OwnedUserId>,
71 },
72
73 Delete {
75 #[arg(value_parser = session_or_user_id)]
76 id: Either<SessionId, OwnedUserId>,
77
78 #[arg(long)]
79 force: bool,
80 },
81}
82
83type SessionOrUserId = Either<SessionId, OwnedUserId>;
84
85fn session_or_user_id(input: &str) -> Result<SessionOrUserId> {
86 OwnedUserId::parse(input)
87 .map(Right)
88 .or_else(|_| Ok(Left(input.to_owned())))
89}
90
91#[admin_command]
92pub(super) async fn oauth_associate(
93 &self,
94 provider: String,
95 user_id: OwnedUserId,
96 claim: Vec<String>,
97) -> Result {
98 if !self.services.globals.user_is_local(&user_id) {
99 return Err!(Request(NotFound("User {user_id:?} does not belong to this server.")));
100 }
101
102 if !self.services.users.exists(&user_id).await {
103 return Err!(Request(NotFound("User {user_id:?} is not registered")));
104 }
105
106 let provider = self
107 .services
108 .oauth
109 .providers
110 .get(&provider)
111 .await?;
112
113 let claim = claim
114 .iter()
115 .map(|kv| {
116 let (key, val) = kv
117 .split_once('=')
118 .ok_or_else(|| err!("Missing '=' in --claim {kv}=???"))?;
119
120 if !key.is_empty() && !val.is_empty() {
121 Ok((key, val))
122 } else {
123 Err!("Missing key or value in --claim=key=value argument")
124 }
125 })
126 .map_ok(apply!(2, ToOwned::to_owned))
127 .collect::<Result<_>>()?;
128
129 let _replaced = self
130 .services
131 .oauth
132 .sessions
133 .set_user_association_pending(provider.id(), &user_id, claim);
134
135 Ok(())
136}
137
138#[admin_command]
139pub(super) async fn oauth_list_providers(&self) -> Result {
140 self.services
141 .config
142 .identity_provider
143 .values()
144 .try_stream()
145 .map_ok(Provider::id)
146 .map_ok(|id| format!("{id}\n"))
147 .try_for_each(async |id| self.write_str(&id).await)
148 .await
149}
150
151#[admin_command]
152pub(super) async fn oauth_list_users(&self) -> Result {
153 self.services
154 .oauth
155 .sessions
156 .users()
157 .map(|id| format!("{id}\n"))
158 .map(Ok)
159 .try_for_each(async |id: String| self.write_str(&id).await)
160 .await
161}
162
163#[admin_command]
164pub(super) async fn oauth_list_sessions(&self, user_id: Option<OwnedUserId>) -> Result {
165 if let Some(user_id) = user_id.as_deref() {
166 return self
167 .services
168 .oauth
169 .sessions
170 .get_sess_id_by_user(user_id)
171 .map_ok(|id| format!("{id}\n"))
172 .try_for_each(async |id: String| self.write_str(&id).await)
173 .await;
174 }
175
176 self.services
177 .oauth
178 .sessions
179 .stream()
180 .ready_filter_map(|sess| sess.sess_id)
181 .map(|sess_id| format!("{sess_id:?}\n"))
182 .for_each(async |id: String| {
183 self.write_str(&id).await.ok();
184 })
185 .await;
186
187 Ok(())
188}
189
190#[admin_command]
191pub(super) async fn oauth_show_provider(&self, id: ProviderId, config: bool) -> Result {
192 if config {
193 let config = self.services.oauth.providers.get_config(&id)?;
194
195 self.write_str(&format!("{config:#?}\n")).await?;
196 return Ok(());
197 }
198
199 let provider = self.services.oauth.providers.get(&id).await?;
200
201 self.write_str(&format!("{provider:#?}\n")).await
202}
203
204#[admin_command]
205pub(super) async fn oauth_show_session(&self, id: SessionId) -> Result {
206 let session = self.services.oauth.sessions.get(&id).await?;
207
208 self.write_str(&format!("{session:#?}\n")).await
209}
210
211#[admin_command]
212pub(super) async fn oauth_show_user(&self, user_id: OwnedUserId) -> Result {
213 self.services
214 .oauth
215 .sessions
216 .get_sess_id_by_user(&user_id)
217 .try_for_each(async |id| {
218 let session = self.services.oauth.sessions.get(&id).await?;
219
220 self.write_str(&format!("{session:#?}\n")).await
221 })
222 .await
223}
224
225#[admin_command]
226pub(super) async fn oauth_token_info(&self, id: SessionId) -> Result {
227 let session = self.services.oauth.sessions.get(&id).await?;
228
229 let provider = self
230 .services
231 .oauth
232 .sessions
233 .provider(&session)
234 .await?;
235
236 let tokeninfo = self
237 .services
238 .oauth
239 .request_tokeninfo((&provider, &session))
240 .await?;
241
242 self.write_str(&format!("{tokeninfo:#?}\n")).await
243}
244
245#[admin_command]
246pub(super) async fn oauth_revoke(&self, id: SessionOrUserId) -> Result {
247 match id {
248 | Left(sess_id) => {
249 let session = self.services.oauth.sessions.get(&sess_id).await?;
250
251 let provider = self
252 .services
253 .oauth
254 .sessions
255 .provider(&session)
256 .await?;
257
258 self.services
259 .oauth
260 .revoke_token((&provider, &session))
261 .await
262 .ok();
263 },
264 | Right(user_id) =>
265 self.services
266 .oauth
267 .revoke_user_tokens(&user_id)
268 .await,
269 }
270
271 self.write_str("revoked").await
272}
273
274#[admin_command]
275pub(super) async fn oauth_delete(&self, id: SessionOrUserId, force: bool) -> Result {
276 if !force {
277 return Err!(
278 "Deleting these records can cause registration conflicts. Use --force to be sure."
279 );
280 }
281
282 match &id {
283 | Left(sess_id) => {
284 self.services.oauth.sessions.delete(sess_id).await;
285 },
286 | Right(user_id) => {
287 self.services
288 .oauth
289 .delete_user_sessions(user_id)
290 .await;
291 },
292 }
293
294 self.write_string(format!("deleted any oauth state for {id}"))
295 .await
296}