add test and permission check
This commit is contained in:
+112
-41
@@ -6,36 +6,59 @@ use crate::domain::dto::message::{
|
||||
use crate::domain::dto::reaction::{CreateReactionRequest, DeleteReactionQuery, ReactionResponse};
|
||||
use crate::http::context::CurrentUser;
|
||||
use crate::http::error::HTTPError;
|
||||
use crate::models::{channel, channel_user};
|
||||
use crate::http::permissions::check_channel_permission;
|
||||
use crate::permissions::ChannelPermission;
|
||||
use crate::routes::message::mapper;
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
};
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub(crate) async fn can_access(
|
||||
pub(crate) async fn require_channel_permission(
|
||||
state: &AppState,
|
||||
channel_id: Uuid,
|
||||
user_id: Uuid,
|
||||
) -> Result<bool, HTTPError> {
|
||||
let Some(channel) = channel::Entity::find_by_id(channel_id)
|
||||
.one(&state.db)
|
||||
.await?
|
||||
else {
|
||||
return Ok(false);
|
||||
};
|
||||
if channel.channel_type != channel::ChannelType::DM {
|
||||
return Ok(true);
|
||||
is_superuser: bool,
|
||||
required: ChannelPermission,
|
||||
) -> Result<(), HTTPError> {
|
||||
let _ = is_superuser;
|
||||
if check_channel_permission(state, user_id, channel_id, required).await? {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(HTTPError::Forbidden)
|
||||
}
|
||||
}
|
||||
|
||||
fn allows_channel_permission(granted: ChannelPermission, required: ChannelPermission) -> bool {
|
||||
granted.contains(required)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn message_permissions_require_every_requested_bit() {
|
||||
let granted = ChannelPermission::READ_CHANNEL | ChannelPermission::SEND_MESSAGE;
|
||||
assert!(allows_channel_permission(
|
||||
granted,
|
||||
ChannelPermission::READ_CHANNEL
|
||||
));
|
||||
assert!(!allows_channel_permission(
|
||||
granted,
|
||||
ChannelPermission::SEND_MESSAGE | ChannelPermission::ATTACH_FILES
|
||||
));
|
||||
assert!(!allows_channel_permission(
|
||||
granted,
|
||||
ChannelPermission::EDIT_OTHERS_MESSAGES
|
||||
));
|
||||
assert!(!allows_channel_permission(
|
||||
ChannelPermission::empty(),
|
||||
ChannelPermission::READ_CHANNEL
|
||||
));
|
||||
}
|
||||
Ok(channel_user::Entity::find()
|
||||
.filter(channel_user::Column::ChannelId.eq(channel_id))
|
||||
.filter(channel_user::Column::UserId.eq(user_id))
|
||||
.one(&state.db)
|
||||
.await?
|
||||
.is_some())
|
||||
}
|
||||
|
||||
/// Liste une fenêtre paginée de messages
|
||||
@@ -64,11 +87,17 @@ pub async fn get_all(
|
||||
}
|
||||
|
||||
let params = mapper::query_params_to_message_filter(filters);
|
||||
if let Some(channel_id) = params.channel_id {
|
||||
if !can_access(&state, channel_id, user.id).await? {
|
||||
return Err(HTTPError::Forbidden);
|
||||
}
|
||||
}
|
||||
let channel_id = params
|
||||
.channel_id
|
||||
.ok_or_else(|| HTTPError::BadRequest("channel_id is required".into()))?;
|
||||
require_channel_permission(
|
||||
&state,
|
||||
channel_id,
|
||||
user.id,
|
||||
user.is_superuser,
|
||||
ChannelPermission::READ_CHANNEL,
|
||||
)
|
||||
.await?;
|
||||
let page = state.repositories.message.filter(params).await?;
|
||||
let message_ids: Vec<_> = page.messages.iter().map(|message| message.id).collect();
|
||||
let mut reactions = state
|
||||
@@ -126,9 +155,14 @@ pub async fn get_by_id(
|
||||
.get_by_id(id)
|
||||
.await?
|
||||
.ok_or(HTTPError::NotFound)?;
|
||||
if !can_access(&state, message.channel_id, user.id).await? {
|
||||
return Err(HTTPError::Forbidden);
|
||||
}
|
||||
require_channel_permission(
|
||||
&state,
|
||||
message.channel_id,
|
||||
user.id,
|
||||
user.is_superuser,
|
||||
ChannelPermission::READ_CHANNEL,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let reactions = state
|
||||
.services
|
||||
@@ -180,9 +214,11 @@ pub async fn create(
|
||||
.get_by_id(payload.channel_id)
|
||||
.await?
|
||||
.ok_or(HTTPError::BadRequest("Channel not found".to_string()))?;
|
||||
if !can_access(&state, channel.id, user.id).await? {
|
||||
return Err(HTTPError::Forbidden);
|
||||
let mut required = ChannelPermission::SEND_MESSAGE;
|
||||
if !payload.file_ids.is_empty() {
|
||||
required |= ChannelPermission::ATTACH_FILES;
|
||||
}
|
||||
require_channel_permission(&state, channel.id, user.id, user.is_superuser, required).await?;
|
||||
|
||||
if payload.content.trim().is_empty() && payload.file_ids.is_empty() {
|
||||
return Err(HTTPError::BadRequest(
|
||||
@@ -192,7 +228,7 @@ pub async fn create(
|
||||
|
||||
// Optionnel: vérifier reply_to_id
|
||||
if let Some(reply_id) = payload.reply_to_id {
|
||||
state
|
||||
let parent = state
|
||||
.repositories
|
||||
.message
|
||||
.get_by_id(reply_id)
|
||||
@@ -200,6 +236,11 @@ pub async fn create(
|
||||
.ok_or(HTTPError::BadRequest(
|
||||
"Parent message not found".to_string(),
|
||||
))?;
|
||||
if parent.channel_id != channel.id {
|
||||
return Err(HTTPError::BadRequest(
|
||||
"Parent message belongs to another channel".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let message = state
|
||||
@@ -274,9 +315,19 @@ pub async fn update(
|
||||
.ok_or(HTTPError::NotFound)?;
|
||||
|
||||
// Vérifier que l'utilisateur est l'auteur
|
||||
if message.user_id != user.id && !user.is_superuser {
|
||||
return Err(HTTPError::Forbidden);
|
||||
}
|
||||
let required = if message.user_id == user.id {
|
||||
ChannelPermission::EDIT_OWN_MESSAGE
|
||||
} else {
|
||||
ChannelPermission::EDIT_OTHERS_MESSAGES
|
||||
};
|
||||
require_channel_permission(
|
||||
&state,
|
||||
message.channel_id,
|
||||
user.id,
|
||||
user.is_superuser,
|
||||
required,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let message = state
|
||||
.services
|
||||
@@ -332,9 +383,14 @@ pub async fn add_reaction(
|
||||
.get_by_id(message_id)
|
||||
.await?
|
||||
.ok_or(HTTPError::NotFound)?;
|
||||
if !can_access(&state, message.channel_id, user.id).await? {
|
||||
return Err(HTTPError::Forbidden);
|
||||
}
|
||||
require_channel_permission(
|
||||
&state,
|
||||
message.channel_id,
|
||||
user.id,
|
||||
user.is_superuser,
|
||||
ChannelPermission::READ_CHANNEL | ChannelPermission::ADD_REACTIONS,
|
||||
)
|
||||
.await?;
|
||||
let (reaction, created) = state
|
||||
.services
|
||||
.message_reaction
|
||||
@@ -378,9 +434,14 @@ pub async fn remove_reaction(
|
||||
.get_by_id(message_id)
|
||||
.await?
|
||||
.ok_or(HTTPError::NotFound)?;
|
||||
if !can_access(&state, message.channel_id, user.id).await? {
|
||||
return Err(HTTPError::Forbidden);
|
||||
}
|
||||
require_channel_permission(
|
||||
&state,
|
||||
message.channel_id,
|
||||
user.id,
|
||||
user.is_superuser,
|
||||
ChannelPermission::READ_CHANNEL,
|
||||
)
|
||||
.await?;
|
||||
state
|
||||
.services
|
||||
.message_reaction
|
||||
@@ -420,9 +481,19 @@ pub async fn delete(
|
||||
.await?
|
||||
.ok_or(HTTPError::NotFound)?;
|
||||
|
||||
if message.user_id != user.id && !user.is_superuser {
|
||||
return Err(HTTPError::Forbidden);
|
||||
}
|
||||
let required = if message.user_id == user.id {
|
||||
ChannelPermission::DELETE_OWN_MESSAGE
|
||||
} else {
|
||||
ChannelPermission::DELETE_OTHERS_MESSAGES
|
||||
};
|
||||
require_channel_permission(
|
||||
&state,
|
||||
message.channel_id,
|
||||
user.id,
|
||||
user.is_superuser,
|
||||
required,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let attachment_ids: Vec<_> = state
|
||||
.repositories
|
||||
|
||||
Reference in New Issue
Block a user