347 lines
11 KiB
Rust
347 lines
11 KiB
Rust
use super::handlers;
|
|
use crate::core::state::AppState;
|
|
use axum::{Router, routing::get};
|
|
|
|
pub fn router() -> Router<AppState> {
|
|
Router::new()
|
|
.route("/messages", get(handlers::get_all).post(handlers::create))
|
|
.route(
|
|
"/messages/{id}",
|
|
get(handlers::get_by_id)
|
|
.put(handlers::update)
|
|
.delete(handlers::delete),
|
|
)
|
|
.route(
|
|
"/messages/{message_id}/reactions",
|
|
axum::routing::post(handlers::add_reaction),
|
|
)
|
|
.route(
|
|
"/messages/{message_id}/reactions/{emoji_id}",
|
|
axum::routing::delete(handlers::remove_reaction),
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) mod permission_tests {
|
|
use super::*;
|
|
use crate::config::{AppConfig, DEFAULT_CONFIG_TOML};
|
|
use crate::core::App;
|
|
use crate::http::context::{CurrentUser, RequestContext};
|
|
use crate::models::{channel, channel_user_permission, emoji, server_user, user};
|
|
use crate::permissions::ChannelPermission;
|
|
use axum::{
|
|
body::Body,
|
|
http::{Method, Request, StatusCode},
|
|
};
|
|
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
|
|
use std::time::Instant;
|
|
use tower::ServiceExt;
|
|
use uuid::Uuid;
|
|
|
|
pub(crate) struct Fixture {
|
|
pub state: AppState,
|
|
pub user: user::Model,
|
|
pub channel_id: Uuid,
|
|
}
|
|
|
|
impl Fixture {
|
|
pub async fn new() -> Self {
|
|
let mut config: AppConfig = toml::from_str(DEFAULT_CONFIG_TOML).unwrap();
|
|
config.database.url = "sqlite::memory:".into();
|
|
config.network.stun_servers.clear();
|
|
config.media.root =
|
|
format!("src/routes/attachment/.permission-test-{}", Uuid::new_v4());
|
|
let state = App::build(config).await.unwrap().state;
|
|
let user = user::ActiveModel {
|
|
id: Set(Uuid::new_v4()),
|
|
username: Set(format!("test-{}", Uuid::new_v4())),
|
|
password: Set(String::new()),
|
|
pub_key: Set(None),
|
|
created_at: Set(chrono::Utc::now()),
|
|
updated_at: Set(chrono::Utc::now()),
|
|
is_superuser: Set(false),
|
|
}
|
|
.insert(&state.db)
|
|
.await
|
|
.unwrap();
|
|
server_user::ActiveModel {
|
|
id: Set(Uuid::new_v4()),
|
|
server_id: Set(state.default_server.id),
|
|
user_id: Set(user.id),
|
|
username: Set(None),
|
|
joined_at: Set(chrono::Utc::now()),
|
|
updated_at: Set(chrono::Utc::now()),
|
|
}
|
|
.insert(&state.db)
|
|
.await
|
|
.unwrap();
|
|
let channel = channel::ActiveModel {
|
|
id: Set(Uuid::new_v4()),
|
|
server_id: Set(Some(state.default_server.id)),
|
|
category_id: Set(None),
|
|
channel_type: Set(channel::ChannelType::Text),
|
|
name: Set(Some("test".into())),
|
|
created_at: Set(chrono::Utc::now()),
|
|
updated_at: Set(chrono::Utc::now()),
|
|
}
|
|
.insert(&state.db)
|
|
.await
|
|
.unwrap();
|
|
Self {
|
|
state,
|
|
user,
|
|
channel_id: channel.id,
|
|
}
|
|
}
|
|
|
|
pub async fn grant(&self, permissions: ChannelPermission) {
|
|
channel_user_permission::ActiveModel {
|
|
id: Set(Uuid::new_v4()),
|
|
channel_id: Set(self.channel_id),
|
|
user_id: Set(self.user.id),
|
|
permissions: Set(permissions.bits() as i64),
|
|
}
|
|
.insert(&self.state.db)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
pub async fn request(
|
|
&self,
|
|
router: Router<AppState>,
|
|
method: Method,
|
|
uri: &str,
|
|
body: Body,
|
|
content_type: Option<&str>,
|
|
) -> StatusCode {
|
|
let mut builder = Request::builder().method(method.clone()).uri(uri);
|
|
if let Some(content_type) = content_type {
|
|
builder = builder.header("content-type", content_type);
|
|
}
|
|
let mut request = builder.body(body).unwrap();
|
|
request.extensions_mut().insert(RequestContext {
|
|
request_id: Uuid::new_v4(),
|
|
started_at: Instant::now(),
|
|
method,
|
|
uri: uri.parse().unwrap(),
|
|
user: Some(CurrentUser(self.user.clone())),
|
|
});
|
|
router
|
|
.with_state(self.state.clone())
|
|
.oneshot(request)
|
|
.await
|
|
.unwrap()
|
|
.status()
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn message_list_requires_read_channel() {
|
|
let fixture = Fixture::new().await;
|
|
let uri = format!("/messages?channel_id={}", fixture.channel_id);
|
|
// L'accès aux messages du canal demande READ_CHANNEL.
|
|
assert_eq!(
|
|
fixture
|
|
.request(router(), Method::GET, &uri, Body::empty(), None)
|
|
.await,
|
|
StatusCode::FORBIDDEN
|
|
);
|
|
fixture.grant(ChannelPermission::READ_CHANNEL).await;
|
|
assert_eq!(
|
|
fixture
|
|
.request(router(), Method::GET, &uri, Body::empty(), None)
|
|
.await,
|
|
StatusCode::OK
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn message_create_requires_send_message() {
|
|
let fixture = Fixture::new().await;
|
|
let payload = format!(
|
|
r#"{{"channel_id":"{}","content":"hello","file_ids":[]}}"#,
|
|
fixture.channel_id
|
|
);
|
|
let send = || Body::from(payload.clone());
|
|
// SEND_MESSAGE est distinct du droit de lire le canal.
|
|
assert_eq!(
|
|
fixture
|
|
.request(
|
|
router(),
|
|
Method::POST,
|
|
"/messages",
|
|
send(),
|
|
Some("application/json")
|
|
)
|
|
.await,
|
|
StatusCode::FORBIDDEN
|
|
);
|
|
fixture.grant(ChannelPermission::SEND_MESSAGE).await;
|
|
assert_eq!(
|
|
fixture
|
|
.request(
|
|
router(),
|
|
Method::POST,
|
|
"/messages",
|
|
send(),
|
|
Some("application/json")
|
|
)
|
|
.await,
|
|
StatusCode::CREATED
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn message_edit_and_delete_require_own_permissions() {
|
|
// L'édition et la suppression d'un message propre ont des permissions distinctes.
|
|
for (method, permission, success) in [
|
|
(
|
|
Method::PUT,
|
|
ChannelPermission::EDIT_OWN_MESSAGE,
|
|
StatusCode::OK,
|
|
),
|
|
(
|
|
Method::DELETE,
|
|
ChannelPermission::DELETE_OWN_MESSAGE,
|
|
StatusCode::NO_CONTENT,
|
|
),
|
|
] {
|
|
let fixture = Fixture::new().await;
|
|
let message = fixture
|
|
.state
|
|
.services
|
|
.message
|
|
.create_message_with_attachments(
|
|
fixture.channel_id,
|
|
fixture.user.id,
|
|
"hello".into(),
|
|
Vec::new(),
|
|
None,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
let uri = format!("/messages/{}", message.id);
|
|
let body = || Body::from(r#"{"content":"edited"}"#);
|
|
assert_eq!(
|
|
fixture
|
|
.request(
|
|
router(),
|
|
method.clone(),
|
|
&uri,
|
|
body(),
|
|
Some("application/json")
|
|
)
|
|
.await,
|
|
StatusCode::FORBIDDEN
|
|
);
|
|
fixture.grant(permission).await;
|
|
assert_eq!(
|
|
fixture
|
|
.request(router(), method, &uri, body(), Some("application/json"))
|
|
.await,
|
|
success
|
|
);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn reactions_require_read_and_add_permissions() {
|
|
let fixture = Fixture::new().await;
|
|
// Prépare un message et un emoji afin de tester les deux opérations de réaction.
|
|
let message = fixture
|
|
.state
|
|
.services
|
|
.message
|
|
.create_message_with_attachments(
|
|
fixture.channel_id,
|
|
fixture.user.id,
|
|
"hello".into(),
|
|
Vec::new(),
|
|
None,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
let emoji = emoji::ActiveModel {
|
|
id: Set(Uuid::new_v4()),
|
|
server_id: Set(Some(fixture.state.default_server.id)),
|
|
name: Set("wave".into()),
|
|
emoji_type: Set("unicode".into()),
|
|
unicode_sequence: Set(Some("👋".into())),
|
|
supports_skin_tone: Set(false),
|
|
file_path: Set(None),
|
|
mime_type: Set(None),
|
|
file_size: Set(None),
|
|
is_animated: Set(false),
|
|
sha256: Set(None),
|
|
created_at: Set(chrono::Utc::now()),
|
|
updated_at: Set(chrono::Utc::now()),
|
|
}
|
|
.insert(&fixture.state.db)
|
|
.await
|
|
.unwrap();
|
|
let add_uri = format!("/messages/{}/reactions", message.id);
|
|
let remove_uri = format!("{add_uri}/{}", emoji.id);
|
|
let payload = || Body::from(format!(r#"{{"emoji_id":"{}"}}"#, emoji.id));
|
|
// Sans permission, l'ajout et le retrait sont refusés.
|
|
assert_eq!(
|
|
fixture
|
|
.request(
|
|
router(),
|
|
Method::POST,
|
|
&add_uri,
|
|
payload(),
|
|
Some("application/json")
|
|
)
|
|
.await,
|
|
StatusCode::FORBIDDEN
|
|
);
|
|
assert_eq!(
|
|
fixture
|
|
.request(router(), Method::DELETE, &remove_uri, Body::empty(), None)
|
|
.await,
|
|
StatusCode::FORBIDDEN
|
|
);
|
|
fixture.grant(ChannelPermission::READ_CHANNEL).await;
|
|
// Lire le canal seul ne suffit pas pour ajouter une réaction.
|
|
assert_eq!(
|
|
fixture
|
|
.request(
|
|
router(),
|
|
Method::POST,
|
|
&add_uri,
|
|
payload(),
|
|
Some("application/json")
|
|
)
|
|
.await,
|
|
StatusCode::FORBIDDEN
|
|
);
|
|
channel_user_permission::Entity::delete_many()
|
|
.filter(channel_user_permission::Column::UserId.eq(fixture.user.id))
|
|
.exec(&fixture.state.db)
|
|
.await
|
|
.unwrap();
|
|
fixture
|
|
.grant(ChannelPermission::READ_CHANNEL | ChannelPermission::ADD_REACTIONS)
|
|
.await;
|
|
// L'ajout requiert les deux droits; le retrait est ensuite autorisé aussi.
|
|
assert_eq!(
|
|
fixture
|
|
.request(
|
|
router(),
|
|
Method::POST,
|
|
&add_uri,
|
|
payload(),
|
|
Some("application/json")
|
|
)
|
|
.await,
|
|
StatusCode::CREATED
|
|
);
|
|
assert_eq!(
|
|
fixture
|
|
.request(router(), Method::DELETE, &remove_uri, Body::empty(), None)
|
|
.await,
|
|
StatusCode::NO_CONTENT
|
|
);
|
|
}
|
|
}
|