mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-07-11 12:48:48 +00:00
fix: Adjust admin API routes to work with new auth logic
This commit is contained in:
Generated
+12
-11
@@ -4601,7 +4601,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma"
|
||||
version = "0.16.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=fca1dbc060f730c1aaf84c9c2fb1e8a587455be8#fca1dbc060f730c1aaf84c9c2fb1e8a587455be8"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff#0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"js_int",
|
||||
@@ -4620,8 +4620,9 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-appservice-api"
|
||||
version = "0.16.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=fca1dbc060f730c1aaf84c9c2fb1e8a587455be8#fca1dbc060f730c1aaf84c9c2fb1e8a587455be8"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff#0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff"
|
||||
dependencies = [
|
||||
"http",
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
"ruma-events",
|
||||
@@ -4632,7 +4633,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-client-api"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=fca1dbc060f730c1aaf84c9c2fb1e8a587455be8#fca1dbc060f730c1aaf84c9c2fb1e8a587455be8"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff#0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"assign",
|
||||
@@ -4654,7 +4655,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-common"
|
||||
version = "0.19.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=fca1dbc060f730c1aaf84c9c2fb1e8a587455be8#fca1dbc060f730c1aaf84c9c2fb1e8a587455be8"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff#0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"base64 0.22.1",
|
||||
@@ -4687,7 +4688,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-events"
|
||||
version = "0.34.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=fca1dbc060f730c1aaf84c9c2fb1e8a587455be8#fca1dbc060f730c1aaf84c9c2fb1e8a587455be8"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff#0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"indexmap 2.14.0",
|
||||
@@ -4708,7 +4709,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-federation-api"
|
||||
version = "0.15.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=fca1dbc060f730c1aaf84c9c2fb1e8a587455be8#fca1dbc060f730c1aaf84c9c2fb1e8a587455be8"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff#0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"headers",
|
||||
@@ -4731,7 +4732,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-identifiers-validation"
|
||||
version = "0.12.1"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=fca1dbc060f730c1aaf84c9c2fb1e8a587455be8#fca1dbc060f730c1aaf84c9c2fb1e8a587455be8"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff#0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"thiserror 2.0.18",
|
||||
@@ -4740,7 +4741,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-macros"
|
||||
version = "0.19.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=fca1dbc060f730c1aaf84c9c2fb1e8a587455be8#fca1dbc060f730c1aaf84c9c2fb1e8a587455be8"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff#0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"cfg-if",
|
||||
@@ -4756,7 +4757,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-push-gateway-api"
|
||||
version = "0.15.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=fca1dbc060f730c1aaf84c9c2fb1e8a587455be8#fca1dbc060f730c1aaf84c9c2fb1e8a587455be8"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff#0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -4768,7 +4769,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-signatures"
|
||||
version = "0.21.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=fca1dbc060f730c1aaf84c9c2fb1e8a587455be8#fca1dbc060f730c1aaf84c9c2fb1e8a587455be8"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff#0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"ed25519-dalek",
|
||||
@@ -4785,7 +4786,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-state-res"
|
||||
version = "0.17.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=fca1dbc060f730c1aaf84c9c2fb1e8a587455be8#fca1dbc060f730c1aaf84c9c2fb1e8a587455be8"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff#0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
|
||||
+3
-2
@@ -349,8 +349,8 @@ version = "1.1.1"
|
||||
# Used for matrix spec type definitions and helpers
|
||||
[workspace.dependencies.ruma]
|
||||
# version = "0.14.1"
|
||||
git = "https://github.com/ruma/ruma.git"
|
||||
rev = "fca1dbc060f730c1aaf84c9c2fb1e8a587455be8"
|
||||
git = "https://github.com/gingershaped/ruwuma.git"
|
||||
rev = "0713b97ed53b7cb5e639f5e2f067c0fe6ac525ff"
|
||||
features = [
|
||||
"appservice-api-c",
|
||||
"client-api",
|
||||
@@ -386,6 +386,7 @@ features = [
|
||||
"unstable-msc4406",
|
||||
"unstable-msc4439",
|
||||
"unstable-msc4466",
|
||||
"unstable-msc4484",
|
||||
"unstable-extensible-events",
|
||||
]
|
||||
|
||||
|
||||
@@ -101,7 +101,8 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) async fn suspend(&self, user_id: String) -> Result {
|
||||
|
||||
pub(super) async fn suspend(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
|
||||
|
||||
@@ -115,7 +116,7 @@ pub(super) async fn suspend(&self, user_id: String) -> Result {
|
||||
// TODO: Record the actual user that sent the suspension where possible
|
||||
self.services
|
||||
.users
|
||||
.suspend_account(&user_id, self.sender_or_service_user())
|
||||
.suspend_account(&user_id, self.sender)
|
||||
.await;
|
||||
|
||||
self.write_str(&format!("User {user_id} has been suspended."))
|
||||
@@ -934,7 +935,8 @@ pub(super) async fn force_leave_remote_room(
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) async fn lock(&self, user_id: String) -> Result {
|
||||
|
||||
pub(super) async fn lock(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
|
||||
|
||||
@@ -948,7 +950,7 @@ pub(super) async fn lock(&self, user_id: String) -> Result {
|
||||
|
||||
self.services
|
||||
.users
|
||||
.lock_account(&user_id, self.sender_or_service_user())
|
||||
.lock_account(&user_id, self.sender)
|
||||
.await;
|
||||
|
||||
self.write_str(&format!("User {user_id} has been locked."))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Result};
|
||||
use futures::future::{join, join3};
|
||||
use futures::join;
|
||||
use ruma::api::client::admin::{is_user_locked, lock_user};
|
||||
|
||||
use crate::Ruma;
|
||||
@@ -12,15 +12,7 @@ pub(crate) async fn get_lock_status(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<is_user_locked::v1::Request>,
|
||||
) -> Result<is_user_locked::v1::Response> {
|
||||
let (admin, status) = join(
|
||||
services.users.is_admin(body.identity.expect_sender_user()?),
|
||||
services.users.status(&body.user_id),
|
||||
)
|
||||
.await;
|
||||
|
||||
if !admin {
|
||||
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
|
||||
}
|
||||
let status = services.users.status(&body.user_id).await;
|
||||
|
||||
status.ensure_active()?;
|
||||
|
||||
@@ -36,22 +28,16 @@ pub(crate) async fn put_lock_status(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<lock_user::v1::Request>,
|
||||
) -> Result<lock_user::v1::Response> {
|
||||
let sender_user = body.identity.expect_sender_user()?;
|
||||
let sender_user = body.identity.sender_user();
|
||||
|
||||
let (sender_admin, status, target_admin) = join3(
|
||||
services.users.is_admin(sender_user),
|
||||
let (status, target_admin) = join!(
|
||||
services.users.status(&body.user_id),
|
||||
services.users.is_admin(&body.user_id),
|
||||
)
|
||||
.await;
|
||||
|
||||
if !sender_admin {
|
||||
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
|
||||
}
|
||||
);
|
||||
|
||||
status.ensure_active()?;
|
||||
|
||||
if body.user_id == *sender_user {
|
||||
if sender_user.is_some_and(|sender_user| body.user_id == sender_user) {
|
||||
return Err!(Request(Forbidden("You cannot lock yourself")));
|
||||
}
|
||||
|
||||
@@ -79,7 +65,7 @@ pub(crate) async fn put_lock_status(
|
||||
// Notify the admin room that an account has been un/suspended
|
||||
services
|
||||
.admin
|
||||
.send_text(&format!("{} has been {} by {}.", body.user_id, action, sender_user))
|
||||
.send_text(&format!("{} has been {} by {}.", body.user_id, action, body.identity))
|
||||
.await;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Event, Result,
|
||||
Event, Result,
|
||||
utils::stream::{BroadbandExt, WidebandExt},
|
||||
};
|
||||
use futures::StreamExt;
|
||||
@@ -28,15 +28,10 @@
|
||||
/// pagination or including banned rooms. It is recommended to use the
|
||||
/// `/v1/rooms` endpoint instead. This endpoint may be removed in a future
|
||||
/// release.
|
||||
pub(crate) async fn legacy_list_rooms_route(
|
||||
pub(crate) async fn legacy_list_rooms(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<rooms::list::unstable::Request>,
|
||||
_body: Ruma<rooms::list::unstable::Request>,
|
||||
) -> Result<rooms::list::unstable::Response> {
|
||||
let sender_user = body.identity.expect_sender_user()?;
|
||||
if !services.users.is_admin(sender_user).await {
|
||||
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
|
||||
}
|
||||
|
||||
let mut rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.metadata
|
||||
@@ -57,15 +52,10 @@ pub(crate) async fn legacy_list_rooms_route(
|
||||
/// # `GET /_continuwuity/admin/v1/rooms`
|
||||
///
|
||||
/// Lists rooms known to this server.
|
||||
pub(crate) async fn list_rooms_route(
|
||||
pub(crate) async fn list_rooms(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<rooms::list::v1::Request>,
|
||||
) -> Result<rooms::list::v1::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
if !services.users.is_admin(sender_user).await {
|
||||
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
|
||||
}
|
||||
|
||||
let include_banned_rooms = body.include_banned_rooms;
|
||||
let rooms = services
|
||||
.rooms
|
||||
|
||||
@@ -1,66 +1,71 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, err, error, info,
|
||||
err, error, info,
|
||||
utils::{IterStream, stream::BroadbandExt},
|
||||
warn,
|
||||
};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::UserId;
|
||||
use ruma::{UserId, api::client::profile::PropagateTo, profile::ProfileFieldValue};
|
||||
use ruminuwuity::admin::continuwuity::users;
|
||||
use service::users::HashedPassword;
|
||||
use service::users::{HashedPassword, ProfileFieldChange};
|
||||
|
||||
use crate::router::Ruma;
|
||||
|
||||
/// # `POST /_continuwuity/admin/v1/users/create`
|
||||
///
|
||||
/// Creates a new user.
|
||||
pub(crate) async fn create_user_route(
|
||||
pub(crate) async fn create_user(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<users::create::v1::Request>,
|
||||
) -> conduwuit::Result<users::create::v1::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if !services.users.is_admin(sender_user).await {
|
||||
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
|
||||
}
|
||||
let user_id =
|
||||
&UserId::parse_with_server_name(&body.localpart, services.globals.server_name())?;
|
||||
if services.users.is_active_local(user_id).await {
|
||||
return Err!(Conflict("A user with this username already exists"));
|
||||
}
|
||||
|
||||
let email = body.email
|
||||
.clone()
|
||||
.map(lettre::Address::try_from)
|
||||
.transpose()
|
||||
.map_err(|e| err!(Request(BadJson("Invalid email address: {e}"))))?;
|
||||
|
||||
services
|
||||
.users
|
||||
.create_local_account(
|
||||
user_id,
|
||||
HashedPassword::new(&body.password)?,
|
||||
body.email
|
||||
.clone()
|
||||
.map(lettre::Address::try_from)
|
||||
.transpose()
|
||||
.map_err(|e| err!(Request(BadJson("Invalid email address: {e}"))))?,
|
||||
)
|
||||
.await;
|
||||
.create_shadow_account(user_id)
|
||||
.await?;
|
||||
|
||||
services.users.convert_to_local_account(user_id, HashedPassword::new(&body.password)?).await?;
|
||||
|
||||
if let Some(email) = &email {
|
||||
services.threepid.associate_localpart_email(user_id.localpart(), email).await?;
|
||||
}
|
||||
|
||||
if body.suspended {
|
||||
services.users.suspend_account(user_id, sender_user).await;
|
||||
services
|
||||
.users
|
||||
.suspend_account(user_id, body.identity.sender_user())
|
||||
.await;
|
||||
}
|
||||
if body.locked {
|
||||
services.users.lock_account(user_id, sender_user).await;
|
||||
services
|
||||
.users
|
||||
.lock_account(user_id, body.identity.sender_user())
|
||||
.await;
|
||||
}
|
||||
if body.login_disabled {
|
||||
services.users.disable_login(user_id);
|
||||
}
|
||||
if let Some(ref value) = body.display_name {
|
||||
services.users.set_profile_key(
|
||||
services.users.set_profile_field(
|
||||
user_id,
|
||||
"displayname",
|
||||
Some(serde_json::to_value(value)?),
|
||||
ProfileFieldChange::Set(ProfileFieldValue::DisplayName(value.to_owned())),
|
||||
PropagateTo::None,
|
||||
);
|
||||
}
|
||||
if let Some(ref value) = body.avatar_url {
|
||||
services
|
||||
.users
|
||||
.set_profile_key(user_id, "avatar_url", Some(serde_json::to_value(value)?));
|
||||
services.users.set_profile_field(
|
||||
user_id,
|
||||
ProfileFieldChange::Set(ProfileFieldValue::AvatarUrl(value.to_owned())),
|
||||
PropagateTo::None,
|
||||
);
|
||||
}
|
||||
if body.admin {
|
||||
services
|
||||
@@ -70,14 +75,18 @@ pub(crate) async fn create_user_route(
|
||||
.inspect_err(|e| error!("failed to make new user {user_id} an admin: {e}"))
|
||||
.ok();
|
||||
}
|
||||
if !body.skip_auto_join {
|
||||
services.users.join_auto_join_rooms(user_id).await;
|
||||
}
|
||||
|
||||
body.auto_join_rooms
|
||||
.clone()
|
||||
.into_iter()
|
||||
.stream()
|
||||
.chain(
|
||||
if body.skip_auto_join {
|
||||
vec![]
|
||||
} else {
|
||||
services.config.auto_join_rooms.clone()
|
||||
}.into_iter().stream()
|
||||
)
|
||||
.broad_filter_map(|room| async move {
|
||||
services
|
||||
.rooms
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, utils::stream::WidebandExt};
|
||||
use conduwuit::utils::stream::WidebandExt;
|
||||
use futures::StreamExt;
|
||||
use ruminuwuity::admin::continuwuity::users;
|
||||
use tokio::join;
|
||||
@@ -9,24 +9,18 @@
|
||||
/// # `GET /_continuwuity/admin/v1/users`
|
||||
///
|
||||
/// Lists all users on this homeserver.
|
||||
pub(crate) async fn list_users_route(
|
||||
pub(crate) async fn list_users(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<users::list::v1::Request>,
|
||||
) -> conduwuit::Result<users::list::v1::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if !services.users.is_admin(sender_user).await {
|
||||
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
|
||||
}
|
||||
|
||||
let users = services
|
||||
.users
|
||||
.list_local_users()
|
||||
.stream_local_users()
|
||||
.skip(body.offset.unwrap_or_default())
|
||||
.take(body.limit.unwrap_or(100).min(100))
|
||||
.wide_filter_map(|user_id| async move {
|
||||
let (deactivated, suspended, locked, admin, login_disabled) = join!(
|
||||
services.users.is_deactivated(&user_id),
|
||||
let (status, suspended, locked, admin, login_disabled) = join!(
|
||||
services.users.status(&user_id),
|
||||
services.users.is_suspended(&user_id),
|
||||
services.users.is_locked(&user_id),
|
||||
services.users.is_admin(&user_id),
|
||||
@@ -34,7 +28,7 @@ pub(crate) async fn list_users_route(
|
||||
);
|
||||
Some(users::list::v1::User {
|
||||
user_id: user_id.clone(),
|
||||
deactivated: deactivated.unwrap_or_default(),
|
||||
deactivated: !status.is_active(),
|
||||
suspended: suspended.unwrap_or_default(),
|
||||
locked: locked.unwrap_or_default(),
|
||||
admin,
|
||||
|
||||
@@ -67,7 +67,7 @@ pub(crate) async fn put_suspended_status(
|
||||
let action = if body.suspended {
|
||||
services
|
||||
.users
|
||||
.suspend_account(&body.user_id, sender_user)
|
||||
.suspend_account(&body.user_id, body.identity.sender_user())
|
||||
.await;
|
||||
"suspended"
|
||||
} else {
|
||||
|
||||
+6
-3
@@ -18,7 +18,7 @@
|
||||
pub(super) use self::{args::Args as Ruma, auth::ClientIdentity, response::RumaResponse};
|
||||
#[cfg(feature = "admin_api")]
|
||||
use crate::client::admin::site as admin_api;
|
||||
use crate::{client, server};
|
||||
use crate::{client::{self, admin}, server};
|
||||
|
||||
pub fn build(router: Router<State>, state: State) -> Router<State> {
|
||||
let config = &state.server.config;
|
||||
@@ -193,8 +193,11 @@ pub fn build(router: Router<State>, state: State) -> Router<State> {
|
||||
.ruma_route(&client::get_authorization_server_metadata_route)
|
||||
.merge(client::oauth::router(state))
|
||||
.route("/_continuwuity/server_version", get(client::continuwuity_server_version))
|
||||
.ruma_route(&admin::rooms::ban::ban_room)
|
||||
.ruma_route(&admin::rooms::list::list_rooms);
|
||||
.ruma_route(&admin::site::rooms::ban_room)
|
||||
.ruma_route(&admin::site::rooms::list_rooms)
|
||||
.ruma_route(&admin::site::rooms::legacy_list_rooms)
|
||||
.ruma_route(&admin::site::users::create_user)
|
||||
.ruma_route(&admin::site::users::list_users);
|
||||
|
||||
if config.allow_federation {
|
||||
router = router
|
||||
|
||||
+81
-12
@@ -1,11 +1,14 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
fmt::Display,
|
||||
};
|
||||
|
||||
use conduwuit::{Err, Error, Result, err};
|
||||
use http::StatusCode;
|
||||
use ruma::{
|
||||
DeviceId, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
|
||||
api::{
|
||||
IncomingRequest,
|
||||
IncomingRequest, OAuthClientScope,
|
||||
auth_scheme::{
|
||||
AccessToken, AccessTokenOptional, AppserviceToken, AppserviceTokenOptional,
|
||||
AuthScheme, NoAccessToken, NoAuthentication,
|
||||
@@ -77,6 +80,17 @@ pub(crate) fn appservice_info(&self) -> Option<&RegistrationInfo> {
|
||||
pub(crate) fn is_appservice(&self) -> bool { matches!(self, Self::Appservice { .. }) }
|
||||
}
|
||||
|
||||
impl Display for ClientIdentity {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
| Self::User { sender_user, sender_device } =>
|
||||
write!(f, "{sender_user} ({sender_device})"),
|
||||
| Self::Appservice { sender_user, appservice_info, .. } =>
|
||||
write!(f, "appservice `{}` using {sender_user}", appservice_info.registration.id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait CheckAuth: AuthScheme {
|
||||
type Identity: Send;
|
||||
|
||||
@@ -95,7 +109,8 @@ fn authenticate<R: IncomingRequest + Any, B: AsRef<[u8]> + Sync>(
|
||||
))))
|
||||
})?;
|
||||
|
||||
Self::verify(services, output, incoming_request, query, route).await
|
||||
Self::verify(services, output, incoming_request, query, route, R::REQUIRED_SCOPES)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +120,7 @@ fn verify<B: AsRef<[u8]> + Sync>(
|
||||
request: &hyper::Request<B>,
|
||||
query: AuthQueryParams,
|
||||
route: TypeId,
|
||||
required_scopes: &[OAuthClientScope],
|
||||
) -> impl Future<Output = Result<Self::Identity>> + Send;
|
||||
}
|
||||
|
||||
@@ -117,6 +133,7 @@ async fn verify<B: AsRef<[u8]> + Sync>(
|
||||
request: &hyper::Request<B>,
|
||||
_query: AuthQueryParams,
|
||||
_route: TypeId,
|
||||
_required_scopes: &[OAuthClientScope],
|
||||
) -> Result<Self::Identity> {
|
||||
let destination = services.globals.server_name();
|
||||
if output
|
||||
@@ -166,6 +183,7 @@ async fn verify<B: AsRef<[u8]> + Sync>(
|
||||
_request: &hyper::Request<B>,
|
||||
query: AuthQueryParams,
|
||||
route: TypeId,
|
||||
required_scopes: &[OAuthClientScope],
|
||||
) -> Result<Self::Identity> {
|
||||
if output.is_empty() {
|
||||
return Err!(Request(Unauthorized("Missing access token.")));
|
||||
@@ -198,6 +216,32 @@ async fn verify<B: AsRef<[u8]> + Sync>(
|
||||
}
|
||||
}
|
||||
|
||||
// If this device is bound to an OAuth session, check its scopes. This will also
|
||||
// handle admin-only endpoints for OAuth clients.
|
||||
if let Some(session) = services
|
||||
.oauth
|
||||
.get_session_info_for_device(&sender_user, &sender_device)
|
||||
.await
|
||||
{
|
||||
if !required_scopes
|
||||
.iter()
|
||||
.any(|scope| session.scopes.contains(scope))
|
||||
{
|
||||
return Err!(Request(Forbidden(
|
||||
"You don't have the necessary scopes to use this endpoint."
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
// Otherwise, explicitly if the endpoint is restricted to admins only.
|
||||
if required_scopes.contains(&OAuthClientScope::ServerAdministration)
|
||||
&& services.users.is_admin(&sender_user).await
|
||||
{
|
||||
return Err!(Request(Forbidden(
|
||||
"Only server administrators can use this endpoint"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ClientIdentity::User { sender_user, sender_device })
|
||||
} else if let Ok(appservice_info) = services.appservice.find_from_token(&output).await {
|
||||
let Ok(sender_user) = query.user_id.clone().map_or_else(
|
||||
@@ -263,12 +307,19 @@ async fn verify<B: AsRef<[u8]> + Sync>(
|
||||
request: &hyper::Request<B>,
|
||||
query: AuthQueryParams,
|
||||
route: TypeId,
|
||||
required_scopes: &[OAuthClientScope],
|
||||
) -> Result<Self::Identity> {
|
||||
match output {
|
||||
| Some(token) =>
|
||||
<AccessToken as CheckAuth>::verify(services, token, request, query, route)
|
||||
.await
|
||||
.map(Some),
|
||||
| Some(token) => <AccessToken as CheckAuth>::verify(
|
||||
services,
|
||||
token,
|
||||
request,
|
||||
query,
|
||||
route,
|
||||
required_scopes,
|
||||
)
|
||||
.await
|
||||
.map(Some),
|
||||
| None => Ok(None),
|
||||
}
|
||||
}
|
||||
@@ -283,6 +334,7 @@ async fn verify<B: AsRef<[u8]> + Sync>(
|
||||
_request: &hyper::Request<B>,
|
||||
_query: AuthQueryParams,
|
||||
_route: TypeId,
|
||||
_required_scopes: &[OAuthClientScope],
|
||||
) -> Result<Self::Identity> {
|
||||
if output.is_empty() {
|
||||
return Err!(Request(Unauthorized("Missing access token.")));
|
||||
@@ -304,12 +356,19 @@ async fn verify<B: AsRef<[u8]> + Sync>(
|
||||
request: &hyper::Request<B>,
|
||||
query: AuthQueryParams,
|
||||
route: TypeId,
|
||||
required_scopes: &[OAuthClientScope],
|
||||
) -> Result<Self::Identity> {
|
||||
match output {
|
||||
| Some(token) =>
|
||||
<AppserviceToken as CheckAuth>::verify(services, token, request, query, route)
|
||||
.await
|
||||
.map(Some),
|
||||
| Some(token) => <AppserviceToken as CheckAuth>::verify(
|
||||
services,
|
||||
token,
|
||||
request,
|
||||
query,
|
||||
route,
|
||||
required_scopes,
|
||||
)
|
||||
.await
|
||||
.map(Some),
|
||||
| None => Ok(None),
|
||||
}
|
||||
}
|
||||
@@ -324,6 +383,7 @@ async fn verify<B: AsRef<[u8]> + Sync>(
|
||||
_request: &hyper::Request<B>,
|
||||
_query: AuthQueryParams,
|
||||
_route: TypeId,
|
||||
_required_scopes: &[OAuthClientScope],
|
||||
) -> Result<Self::Identity> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -338,6 +398,7 @@ async fn verify<B: AsRef<[u8]> + Sync>(
|
||||
request: &hyper::Request<B>,
|
||||
query: AuthQueryParams,
|
||||
route: TypeId,
|
||||
required_scopes: &[OAuthClientScope],
|
||||
) -> Result<Self::Identity> {
|
||||
// We handle these the same as AccessTokenOptional
|
||||
let token = AccessTokenOptional::extract_authentication(request).map_err(|err| {
|
||||
@@ -357,6 +418,14 @@ async fn verify<B: AsRef<[u8]> + Sync>(
|
||||
)));
|
||||
}
|
||||
|
||||
<AccessTokenOptional as CheckAuth>::verify(services, token, request, query, route).await
|
||||
<AccessTokenOptional as CheckAuth>::verify(
|
||||
services,
|
||||
token,
|
||||
request,
|
||||
query,
|
||||
route,
|
||||
required_scopes,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pub mod v1 {
|
||||
use ruma::{
|
||||
OwnedRoomAliasId, OwnedRoomId, OwnedUserId,
|
||||
api::{auth_scheme::AccessToken, request, response},
|
||||
api::{OAuthClientScope, auth_scheme::AccessToken, request, response},
|
||||
metadata,
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ pub mod v1 {
|
||||
method: PUT,
|
||||
rate_limited: false,
|
||||
authentication: AccessToken,
|
||||
required_scopes: [OAuthClientScope::ServerAdministration],
|
||||
history: {
|
||||
unstable("org.continuwuity.admin") => "/_continuwuity/admin/rooms/{room_id}/ban",
|
||||
1.0 => "/_continuwuity/admin/v1/rooms/{room_id}/ban",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pub mod unstable {
|
||||
use ruma::{
|
||||
OwnedRoomId,
|
||||
api::{auth_scheme::AccessToken, request, response},
|
||||
api::{OAuthClientScope, auth_scheme::AccessToken, request, response},
|
||||
metadata,
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ pub mod unstable {
|
||||
method: GET,
|
||||
rate_limited: false,
|
||||
authentication: AccessToken,
|
||||
required_scopes: [OAuthClientScope::ServerAdministration],
|
||||
history: {
|
||||
unstable => "/_continuwuity/admin/rooms/list",
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pub mod v1 {
|
||||
use ruma::{
|
||||
OwnedMxcUri, OwnedRoomOrAliasId, OwnedUserId,
|
||||
api::{auth_scheme::AccessToken, request, response},
|
||||
api::{OAuthClientScope, auth_scheme::AccessToken, request, response},
|
||||
metadata,
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ pub mod v1 {
|
||||
method: POST,
|
||||
rate_limited: false,
|
||||
authentication: AccessToken,
|
||||
required_scopes: [OAuthClientScope::ServerAdministration],
|
||||
history: {
|
||||
1.0 => "/_continuwuity/admin/v1/users/create",
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pub mod v1 {
|
||||
use ruma::{
|
||||
OwnedUserId,
|
||||
api::{auth_scheme::AccessToken, request, response},
|
||||
api::{OAuthClientScope, auth_scheme::AccessToken, request, response},
|
||||
metadata,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
@@ -10,6 +10,7 @@ pub mod v1 {
|
||||
method: GET,
|
||||
rate_limited: false,
|
||||
authentication: AccessToken,
|
||||
required_scopes: [OAuthClientScope::ServerAdministration],
|
||||
history: {
|
||||
1.0 => "/_continuwuity/admin/v1/users",
|
||||
}
|
||||
|
||||
+40
-14
@@ -8,7 +8,7 @@
|
||||
};
|
||||
|
||||
use regex::Regex;
|
||||
use ruma::OwnedDeviceId;
|
||||
use ruma::{OwnedDeviceId, api::OAuthClientScope};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
@@ -55,26 +55,40 @@ pub enum Prompt {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialOrd, Ord)]
|
||||
pub enum Scope {
|
||||
pub enum RequestedScope {
|
||||
Device(OwnedDeviceId),
|
||||
ClientApi,
|
||||
ApiFullAccess,
|
||||
ServerAdministration,
|
||||
}
|
||||
|
||||
impl PartialEq for Scope {
|
||||
impl RequestedScope {
|
||||
#[must_use]
|
||||
pub fn as_granted_scope(&self) -> Option<OAuthClientScope> {
|
||||
match self {
|
||||
| Self::ApiFullAccess => Some(OAuthClientScope::ApiFullAccess),
|
||||
| Self::ServerAdministration => Some(OAuthClientScope::ServerAdministration),
|
||||
| Self::Device(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for RequestedScope {
|
||||
fn eq(&self, other: &Self) -> bool { discriminant(self) == discriminant(other) }
|
||||
}
|
||||
|
||||
impl Eq for Scope {}
|
||||
impl Eq for RequestedScope {}
|
||||
|
||||
impl Hash for Scope {
|
||||
impl Hash for RequestedScope {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { discriminant(self).hash(state); }
|
||||
}
|
||||
|
||||
impl Display for Scope {
|
||||
impl Display for RequestedScope {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let urn = match self {
|
||||
| Self::ClientApi => "urn:matrix:client:api:*".to_owned(),
|
||||
| Self::ApiFullAccess => "urn:matrix:client:api:*".to_owned(),
|
||||
| Self::Device(device_id) => format!("urn:matrix:client:device:{device_id}"),
|
||||
| Self::ServerAdministration =>
|
||||
"urn:matrix:client:cc.c10y.msc4484.server_administration".to_owned(),
|
||||
};
|
||||
|
||||
f.write_str(&urn)
|
||||
@@ -85,22 +99,27 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
pub struct RawScopes(String);
|
||||
|
||||
impl RawScopes {
|
||||
pub fn to_scopes(&self) -> Result<BTreeSet<Scope>, String> {
|
||||
let client_api_token_regex =
|
||||
pub fn to_scopes(&self) -> Result<BTreeSet<RequestedScope>, String> {
|
||||
let full_access_regex =
|
||||
Regex::new(r"urn:matrix:(client|org.matrix.msc2967.client):api:\*").unwrap();
|
||||
let device_token_regex = Regex::new(
|
||||
r"urn:matrix:(client|org.matrix.msc2967.client):device:([a-zA-Z0-9-._~]{5,})",
|
||||
)
|
||||
.unwrap();
|
||||
let server_administration_regex =
|
||||
Regex::new(r"urn:matrix:client:cc.c10y.msc4484.server_administration").unwrap();
|
||||
|
||||
let mut scopes = BTreeSet::new();
|
||||
|
||||
for token in self.0.split(' ') {
|
||||
let scope_was_new = {
|
||||
if client_api_token_regex.is_match(token) {
|
||||
scopes.insert(Scope::ClientApi)
|
||||
if full_access_regex.is_match(token) {
|
||||
scopes.insert(RequestedScope::ApiFullAccess)
|
||||
} else if let Some(captures) = device_token_regex.captures(token) {
|
||||
scopes.insert(Scope::Device(captures.get(2).unwrap().as_str().into()))
|
||||
scopes
|
||||
.insert(RequestedScope::Device(captures.get(2).unwrap().as_str().into()))
|
||||
} else if server_administration_regex.is_match(token) {
|
||||
scopes.insert(RequestedScope::ServerAdministration)
|
||||
} else if token == "openid" {
|
||||
// TODO(unspecced): Element sets this scope but doesn't use it for anything
|
||||
true
|
||||
@@ -160,8 +179,15 @@ pub enum ErrorCode {
|
||||
InvalidClientMetadata,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum AuthorizationCodeResponse {
|
||||
Success(AuthorizationCodeData),
|
||||
Error(OAuthError),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AuthorizationCodeResponse {
|
||||
pub struct AuthorizationCodeData {
|
||||
pub state: String,
|
||||
pub code: String,
|
||||
}
|
||||
|
||||
+50
-38
@@ -12,7 +12,7 @@
|
||||
use database::{Deserialized, Json, Map};
|
||||
use itertools::Itertools;
|
||||
use lru_cache::LruCache;
|
||||
use ruma::{DeviceId, OwnedDeviceId, OwnedUserId, UserId};
|
||||
use ruma::{DeviceId, OwnedDeviceId, OwnedUserId, UserId, api::OAuthClientScope};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
oauth::{
|
||||
client_metadata::{ApplicationType, ClientMetadata, ResponseType},
|
||||
grant::{
|
||||
AuthorizationCodeQuery, AuthorizationCodeResponse, CodeChallengeMethod, ErrorCode,
|
||||
OAuthError, ResponseMode, Scope, TokenRequest, TokenResponse, TokenType,
|
||||
AuthorizationCodeData, AuthorizationCodeQuery, AuthorizationCodeResponse, CodeChallengeMethod, ErrorCode, OAuthError, RequestedScope, ResponseMode, TokenRequest, TokenResponse, TokenType
|
||||
},
|
||||
},
|
||||
users,
|
||||
@@ -51,7 +50,7 @@ struct Services {
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct SessionInfo {
|
||||
pub client_id: String,
|
||||
pub scopes: BTreeSet<Scope>,
|
||||
pub scopes: BTreeSet<OAuthClientScope>,
|
||||
current_refresh_token: String,
|
||||
}
|
||||
|
||||
@@ -64,7 +63,7 @@ struct RefreshTokenInfo {
|
||||
|
||||
struct PendingCodeGrant {
|
||||
authorizing_user: OwnedUserId,
|
||||
requested_scopes: BTreeSet<Scope>,
|
||||
requested_scopes: BTreeSet<RequestedScope>,
|
||||
client_name: Option<String>,
|
||||
expected_client_id: String,
|
||||
expected_redirect_uri: Url,
|
||||
@@ -224,49 +223,59 @@ pub async fn request_authorization_code(
|
||||
}
|
||||
}
|
||||
|
||||
let requested_scopes = query.scope.to_scopes()?;
|
||||
|
||||
let redirect_uri_query_separator = match query.response_mode {
|
||||
| ResponseMode::Fragment => '#',
|
||||
| ResponseMode::Query => '?',
|
||||
};
|
||||
|
||||
let code = PendingCodeGrant::generate_code();
|
||||
let response = 'response: {
|
||||
let requested_scopes = query.scope.to_scopes()?;
|
||||
|
||||
info!(
|
||||
client_id = &query.client_id,
|
||||
client_name = &client_metadata.client_name,
|
||||
?requested_scopes,
|
||||
?authorizing_user,
|
||||
"Issuing oauth authorization code"
|
||||
);
|
||||
if requested_scopes.contains(&RequestedScope::ServerAdministration) {
|
||||
// Only server admins can request this scope
|
||||
if !self.services.users.is_admin(&authorizing_user).await {
|
||||
break 'response AuthorizationCodeResponse::Error(OAuthError {
|
||||
error: ErrorCode::AccessDenied,
|
||||
error_description: "You are not a server administrator.".into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let code = PendingCodeGrant::generate_code();
|
||||
|
||||
info!(
|
||||
client_id = &query.client_id,
|
||||
client_name = &client_metadata.client_name,
|
||||
?requested_scopes,
|
||||
?authorizing_user,
|
||||
"Issuing oauth authorization code"
|
||||
);
|
||||
|
||||
let pending_grant = PendingCodeGrant {
|
||||
authorizing_user,
|
||||
requested_scopes,
|
||||
client_name: client_metadata.client_name,
|
||||
expected_client_id: query.client_id,
|
||||
expected_redirect_uri: query.redirect_uri.clone(),
|
||||
code_challenge: query.code_challenge,
|
||||
requested_at: SystemTime::now(),
|
||||
};
|
||||
|
||||
self.pending_code_grants
|
||||
.lock()
|
||||
.await
|
||||
.insert(code.clone(), pending_grant);
|
||||
|
||||
AuthorizationCodeResponse::Success(AuthorizationCodeData { state: query.state, code })
|
||||
};
|
||||
|
||||
let redirect_uri = format!(
|
||||
"{}{}{}",
|
||||
query.redirect_uri,
|
||||
redirect_uri_query_separator,
|
||||
serde_urlencoded::to_string(AuthorizationCodeResponse {
|
||||
state: query.state,
|
||||
code: code.clone(),
|
||||
})
|
||||
.unwrap(),
|
||||
serde_urlencoded::to_string(response).unwrap(),
|
||||
);
|
||||
|
||||
let pending_grant = PendingCodeGrant {
|
||||
authorizing_user,
|
||||
requested_scopes,
|
||||
client_name: client_metadata.client_name,
|
||||
expected_client_id: query.client_id,
|
||||
expected_redirect_uri: query.redirect_uri,
|
||||
code_challenge: query.code_challenge,
|
||||
requested_at: SystemTime::now(),
|
||||
};
|
||||
|
||||
self.pending_code_grants
|
||||
.lock()
|
||||
.await
|
||||
.insert(code, pending_grant);
|
||||
|
||||
Ok(redirect_uri)
|
||||
}
|
||||
|
||||
@@ -339,7 +348,7 @@ pub async fn revoke_token(&self, token: String) -> Result<(), OAuthError> {
|
||||
async fn create_session(
|
||||
&self,
|
||||
authorizing_user: OwnedUserId,
|
||||
requested_scopes: BTreeSet<Scope>,
|
||||
requested_scopes: BTreeSet<RequestedScope>,
|
||||
client_name: Option<String>,
|
||||
client_id: String,
|
||||
) -> Result<TokenResponse, OAuthError> {
|
||||
@@ -349,7 +358,7 @@ async fn create_session(
|
||||
let device_id = requested_scopes
|
||||
.iter()
|
||||
.find_map(|scope| {
|
||||
if let Scope::Device(device_id) = scope {
|
||||
if let RequestedScope::Device(device_id) = scope {
|
||||
Some(device_id)
|
||||
} else {
|
||||
None
|
||||
@@ -391,7 +400,10 @@ async fn create_session(
|
||||
Json(SessionInfo {
|
||||
client_id: client_id.clone(),
|
||||
current_refresh_token: refresh_token.clone(),
|
||||
scopes: requested_scopes.clone(),
|
||||
scopes: requested_scopes
|
||||
.iter()
|
||||
.filter_map(RequestedScope::as_granted_scope)
|
||||
.collect(),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
use crate::{
|
||||
Dep, config, globals, media,
|
||||
oauth::grant::AuthorizationCodeResponse,
|
||||
oauth::grant::AuthorizationCodeData,
|
||||
threepid,
|
||||
users::{self, AccountStatus, ProfileFieldChange},
|
||||
};
|
||||
@@ -245,7 +245,7 @@ pub async fn begin_session(&self, prompt: Option<CoreAuthPrompt>) -> (PendingSes
|
||||
pub async fn exchange_code(
|
||||
&self,
|
||||
session: PendingSession,
|
||||
response: AuthorizationCodeResponse,
|
||||
response: AuthorizationCodeData,
|
||||
) -> Result<Claims, &'static str> {
|
||||
let Some(OidcClient { machine, client, .. }) = self.client.as_ref() else {
|
||||
return Err("Delegated authentication is not enabled on this server.");
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
};
|
||||
use ruma::api::{
|
||||
IncomingResponse, OutgoingRequest,
|
||||
appservice::Registration,
|
||||
auth_scheme::{AccessToken, SendAccessToken},
|
||||
appservice::{HomeserverToken, Registration},
|
||||
path_builder::SinglePath,
|
||||
};
|
||||
|
||||
@@ -22,7 +21,7 @@ pub async fn send_appservice_request<T>(
|
||||
request: T,
|
||||
) -> Result<Option<T::IncomingResponse>>
|
||||
where
|
||||
T: OutgoingRequest<Authentication = AccessToken, PathBuilder = SinglePath> + Debug + Send,
|
||||
T: OutgoingRequest<Authentication = HomeserverToken, PathBuilder = SinglePath> + Debug + Send,
|
||||
{
|
||||
let Some(dest) = registration.url else {
|
||||
return Ok(None);
|
||||
@@ -36,7 +35,7 @@ pub async fn send_appservice_request<T>(
|
||||
|
||||
let hs_token = registration.hs_token.as_str();
|
||||
let mut http_request = request
|
||||
.try_into_http_request::<BytesMut>(&dest, SendAccessToken::Appservice(hs_token), ())
|
||||
.try_into_http_request::<BytesMut>(&dest, hs_token, ())
|
||||
.map_err(|e| {
|
||||
err!(BadServerResponse(
|
||||
warn!(appservice = %registration.id, "Failed to find destination {dest}: {e:?}")
|
||||
|
||||
@@ -197,7 +197,7 @@ pub async fn create_local_account(
|
||||
// register, suspend them.
|
||||
if !was_first_user && self.services.config.suspend_on_register {
|
||||
// Note that we can still do auto joins for suspended users
|
||||
self.suspend_account(user_id, &self.services.globals.server_user)
|
||||
self.suspend_account(user_id, Some(&self.services.globals.server_user))
|
||||
.await;
|
||||
|
||||
// And send an @room notice to the admin room, to prompt admins to review the
|
||||
@@ -389,13 +389,13 @@ pub async fn deactivate_account(&self, user_id: &UserId) -> Result<()> {
|
||||
}
|
||||
|
||||
/// Suspend account, placing it in a read-only state
|
||||
pub async fn suspend_account(&self, user_id: &UserId, suspending_user: &UserId) {
|
||||
pub async fn suspend_account(&self, user_id: &UserId, suspending_user: Option<&UserId>) {
|
||||
self.db.userid_suspension.raw_put(
|
||||
user_id,
|
||||
Json(UserSuspension {
|
||||
suspended: true,
|
||||
suspended_at: MilliSecondsSinceUnixEpoch::now().get().into(),
|
||||
suspended_by: suspending_user.to_string(),
|
||||
suspended_by: suspending_user.map(ToString::to_string),
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -406,7 +406,7 @@ pub async fn unsuspend_account(&self, user_id: &UserId) {
|
||||
}
|
||||
|
||||
/// Locks an account, preventing it being used until it is unlocked.
|
||||
pub async fn lock_account(&self, user_id: &UserId, locking_user: &UserId) {
|
||||
pub async fn lock_account(&self, user_id: &UserId, locking_user: Option<&UserId>) {
|
||||
// NOTE: Locking is basically just suspension with a more severe effect,
|
||||
// so we'll just re-use the suspension data structure to store the lock state.
|
||||
let suspension = self
|
||||
@@ -418,7 +418,7 @@ pub async fn lock_account(&self, user_id: &UserId, locking_user: &UserId) {
|
||||
.unwrap_or_else(|_| UserSuspension {
|
||||
suspended: true,
|
||||
suspended_at: MilliSecondsSinceUnixEpoch::now().get().into(),
|
||||
suspended_by: locking_user.to_string(),
|
||||
suspended_by: locking_user.map(ToString::to_string),
|
||||
});
|
||||
|
||||
self.db.userid_lock.raw_put(user_id, Json(suspension));
|
||||
|
||||
@@ -31,7 +31,7 @@ pub struct UserSuspension {
|
||||
/// When the user was suspended (Unix timestamp in milliseconds)
|
||||
pub suspended_at: u64,
|
||||
/// User ID of who suspended this user
|
||||
pub suspended_by: String,
|
||||
pub suspended_by: Option<String>,
|
||||
}
|
||||
|
||||
/// A password hash. This is only for use when setting a user's password,
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
use askama::{Template, filters::HtmlSafe};
|
||||
use base64::Engine;
|
||||
use conduwuit_core::{result::FlatOk, utils};
|
||||
use conduwuit_service::{
|
||||
Services,
|
||||
media::mxc::Mxc,
|
||||
oauth::{client_metadata::ClientMetadata, grant::Scope},
|
||||
use conduwuit_service::{Services, media::mxc::Mxc, oauth::client_metadata::ClientMetadata};
|
||||
use ruma::{
|
||||
OwnedDeviceId, OwnedUserId, UserId,
|
||||
api::{OAuthClientScope, client::device::Device},
|
||||
};
|
||||
use ruma::{OwnedDeviceId, OwnedUserId, UserId, api::client::device::Device};
|
||||
|
||||
pub(super) mod form;
|
||||
|
||||
@@ -183,7 +182,7 @@ pub(super) async fn for_device(
|
||||
#[derive(Debug, Template)]
|
||||
#[template(path = "_components/client_scopes.html.j2")]
|
||||
pub(super) struct ClientScopes {
|
||||
pub scopes: BTreeSet<Scope>,
|
||||
pub scopes: BTreeSet<OAuthClientScope>,
|
||||
}
|
||||
|
||||
impl HtmlSafe for ClientScopes {}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
response::Redirect,
|
||||
routing::on,
|
||||
};
|
||||
use conduwuit_service::oauth::grant::{AuthorizationCodeQuery, Prompt};
|
||||
use conduwuit_service::oauth::grant::{AuthorizationCodeQuery, Prompt, RequestedScope};
|
||||
use ruma::OwnedUserId;
|
||||
use url::Url;
|
||||
|
||||
@@ -100,7 +100,13 @@ async fn route_authorization_code(
|
||||
return Err(WebError::BadRequest("Invalid client ID".to_owned()));
|
||||
};
|
||||
|
||||
let scopes = query.scope.to_scopes().map_err(WebError::BadRequest)?;
|
||||
let scopes = query
|
||||
.scope
|
||||
.to_scopes()
|
||||
.map_err(WebError::BadRequest)?
|
||||
.iter()
|
||||
.filter_map(RequestedScope::as_granted_scope)
|
||||
.collect();
|
||||
|
||||
let client_name = if let Some(name) = &client.client_name {
|
||||
name
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
response::Redirect,
|
||||
routing::on,
|
||||
};
|
||||
use conduwuit_service::{oauth::grant::AuthorizationCodeResponse, oidc::SessionCompletionStatus};
|
||||
use conduwuit_service::{oauth::grant::AuthorizationCodeData, oidc::SessionCompletionStatus};
|
||||
use futures::FutureExt;
|
||||
use ruma::{OwnedServerName, UserId};
|
||||
use serde::{Deserialize, de::IgnoredAny};
|
||||
@@ -57,7 +57,7 @@ struct LoginForm {
|
||||
async fn route_complete(
|
||||
State(services): State<crate::State>,
|
||||
Extension(context): Extension<TemplateContext>,
|
||||
Expect(Query(query)): Expect<Query<AuthorizationCodeResponse>>,
|
||||
Expect(Query(query)): Expect<Query<AuthorizationCodeData>>,
|
||||
session_store: Session,
|
||||
user: User<true>,
|
||||
PostForm(form): PostForm<LoginForm>,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<ul>
|
||||
{% for scope in scopes %}
|
||||
{% match scope %}
|
||||
{% when Scope::ClientApi %}
|
||||
{% when OAuthClientScope::ApiFullAccess %}
|
||||
<li>Send messages and interact with chatrooms on your behalf</li>
|
||||
{% when Scope::Device(_) %}
|
||||
<li>Access your Matrix account</li>
|
||||
{% when OAuthClientScope::ServerAdministration %}
|
||||
<li>⚠️ Administrate this homeserver
|
||||
<br><em class="negative">This is a dangerous permission. Make sure you trust this app.</em></li>
|
||||
{% when _ %}
|
||||
<li>missingno</li>
|
||||
{% endmatch %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user