mirror of https://github.com/synctv-org/synctv
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
320 lines
9.9 KiB
Rust
320 lines
9.9 KiB
Rust
//! Permission check integration tests with database
|
|
//!
|
|
//! Tests verify Allow/Deny permission pattern and integration with database.
|
|
//!
|
|
//! Requires Docker for testcontainers.
|
|
|
|
use sqlx::PgPool;
|
|
use synctv_core::{
|
|
models::{
|
|
Room, RoomId, RoomMember, RoomMemberPermissionBits, RoomPermission, RoomPermissionSet,
|
|
RoomRole, SignupMethod, User, UserId, UserStatus,
|
|
},
|
|
repository::{RoomMemberRepository, RoomRepository, UserRepository},
|
|
service::PermissionService,
|
|
};
|
|
use synctv_core_testing::{create_test_pool, ok};
|
|
|
|
/// Create a test user in the database (required for FK constraints)
|
|
async fn create_test_user(pool: &PgPool, user_id: &UserId) -> UserId {
|
|
let username = format!("test_user_{user_id}");
|
|
let user = User {
|
|
id: *user_id,
|
|
username,
|
|
signup_method: SignupMethod::Email,
|
|
role: synctv_core::models::UserRole::User,
|
|
avatar_file_reference_id: None,
|
|
status: UserStatus::Active,
|
|
created_at: chrono::Utc::now(),
|
|
updated_at: chrono::Utc::now(),
|
|
version: 0,
|
|
deleted_at: None,
|
|
is_banned: false,
|
|
banned_at: None,
|
|
banned_by: None,
|
|
banned_reason: None,
|
|
};
|
|
let user_repo = UserRepository::new(pool.clone());
|
|
ok(
|
|
user_repo.create(&user).await,
|
|
"permission test user should be created",
|
|
)
|
|
.id
|
|
}
|
|
|
|
fn make_member(room_id: RoomId, user_id: UserId, role: RoomRole) -> RoomMember {
|
|
RoomMember {
|
|
room_id,
|
|
user_id,
|
|
role,
|
|
added_permissions: 0,
|
|
removed_permissions: 0,
|
|
admin_added_permissions: 0,
|
|
admin_removed_permissions: 0,
|
|
remark_name: String::new(),
|
|
display_tag: String::new(),
|
|
joined_at: chrono::Utc::now(),
|
|
version: 0,
|
|
}
|
|
}
|
|
|
|
fn make_room(creator_id: UserId) -> Room {
|
|
Room {
|
|
id: RoomId::new(),
|
|
name: "Test Room".to_string(),
|
|
description: "Test".to_string(),
|
|
cover_file_reference_id: None,
|
|
category: None,
|
|
labels: Vec::new(),
|
|
created_by: creator_id,
|
|
status: synctv_core::models::RoomStatus::Active,
|
|
is_banned: false,
|
|
is_public: true,
|
|
closed_at: None,
|
|
created_at: chrono::Utc::now(),
|
|
updated_at: chrono::Utc::now(),
|
|
deleted_at: None,
|
|
version: 0,
|
|
last_activity_at: chrono::Utc::now(),
|
|
}
|
|
}
|
|
|
|
fn make_perm_service(
|
|
member_repo: RoomMemberRepository,
|
|
room_repo: RoomRepository,
|
|
) -> PermissionService {
|
|
ok(
|
|
PermissionService::new(member_repo, room_repo, None, 1000, 300),
|
|
"permission service should build",
|
|
)
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "Requires Docker"]
|
|
async fn test_permission_check_with_database_member() {
|
|
let (_container, pool) = create_test_pool().await;
|
|
|
|
let room_repo = RoomRepository::new(pool.clone());
|
|
let member_repo = RoomMemberRepository::new(pool.clone());
|
|
|
|
let creator_id = create_test_user(&pool, &UserId::new()).await;
|
|
let room = make_room(creator_id);
|
|
let room = ok(
|
|
room_repo.create(&room).await,
|
|
"permission test room should be created",
|
|
);
|
|
|
|
let user_id = create_test_user(&pool, &UserId::new()).await;
|
|
let mut member = make_member(room.id, user_id, RoomRole::Member);
|
|
member.added_permissions =
|
|
RoomMemberPermissionBits::SEND_CHAT_MESSAGES | RoomMemberPermissionBits::MANAGE_OWN_MEDIA;
|
|
ok(
|
|
member_repo.add(&member).await,
|
|
"permission test member should be created",
|
|
);
|
|
|
|
let perm_service = make_perm_service(member_repo, room_repo);
|
|
|
|
// check_permission_no_cache returns Ok(()) if granted, Err if denied
|
|
ok(
|
|
perm_service
|
|
.check_permission_no_cache(&room.id, &user_id, RoomPermission::SEND_CHAT_MESSAGES)
|
|
.await,
|
|
"user should have CHAT permission",
|
|
);
|
|
|
|
ok(
|
|
perm_service
|
|
.check_permission_no_cache(&room.id, &user_id, RoomPermission::MANAGE_OWN_MEDIA)
|
|
.await,
|
|
"user should have MANAGE_OWN_MEDIA permission",
|
|
);
|
|
|
|
let kick_result = perm_service
|
|
.check_permission_no_cache(&room.id, &user_id, RoomPermission::REMOVE_MEMBERS)
|
|
.await;
|
|
assert!(
|
|
kick_result.is_err(),
|
|
"User should not have REMOVE_MEMBERS permission"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "Requires Docker"]
|
|
async fn test_permission_allow_deny_pattern() {
|
|
let (_container, pool) = create_test_pool().await;
|
|
|
|
let room_repo = RoomRepository::new(pool.clone());
|
|
let member_repo = RoomMemberRepository::new(pool.clone());
|
|
|
|
let creator_id = create_test_user(&pool, &UserId::new()).await;
|
|
let room = make_room(creator_id);
|
|
let room = ok(
|
|
room_repo.create(&room).await,
|
|
"permission test room should be created",
|
|
);
|
|
|
|
let admin_id = create_test_user(&pool, &UserId::new()).await;
|
|
let admin_member = make_member(room.id, admin_id, RoomRole::Admin);
|
|
ok(
|
|
member_repo.add(&admin_member).await,
|
|
"permission test admin should be created",
|
|
);
|
|
|
|
let guest_id = create_test_user(&pool, &UserId::new()).await;
|
|
let guest_member = make_member(room.id, guest_id, RoomRole::Guest);
|
|
ok(
|
|
member_repo.add(&guest_member).await,
|
|
"permission test guest should be created",
|
|
);
|
|
|
|
let perm_service = make_perm_service(member_repo, room_repo);
|
|
|
|
ok(
|
|
perm_service
|
|
.check_permission_no_cache(&room.id, &admin_id, RoomPermission::REMOVE_MEMBERS)
|
|
.await,
|
|
"admin should be able to kick members",
|
|
);
|
|
|
|
let guest_chat_result = perm_service
|
|
.check_permission_no_cache(&room.id, &guest_id, RoomPermission::SEND_CHAT_MESSAGES)
|
|
.await;
|
|
assert!(
|
|
guest_chat_result.is_err(),
|
|
"Guest should not be able to send chat"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "Requires Docker"]
|
|
async fn test_permission_removed_member_denied() {
|
|
let (_container, pool) = create_test_pool().await;
|
|
|
|
let room_repo = RoomRepository::new(pool.clone());
|
|
let member_repo = RoomMemberRepository::new(pool.clone());
|
|
|
|
let creator_id = create_test_user(&pool, &UserId::new()).await;
|
|
let room = make_room(creator_id);
|
|
let room = ok(
|
|
room_repo.create(&room).await,
|
|
"permission test room should be created",
|
|
);
|
|
|
|
let user_id = create_test_user(&pool, &UserId::new()).await;
|
|
let member = make_member(room.id, user_id, RoomRole::Member);
|
|
ok(
|
|
member_repo.add(&member).await,
|
|
"permission test member should be created",
|
|
);
|
|
ok(
|
|
member_repo.remove(&room.id, &user_id).await,
|
|
"permission test membership should be removed",
|
|
);
|
|
|
|
let perm_service = make_perm_service(member_repo, room_repo);
|
|
|
|
let result = perm_service
|
|
.check_permission_no_cache(&room.id, &user_id, RoomPermission::SEND_CHAT_MESSAGES)
|
|
.await;
|
|
assert!(
|
|
result.is_err(),
|
|
"Removed member should not have permissions"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "Requires Docker"]
|
|
async fn test_permission_non_member_denied() {
|
|
let (_container, pool) = create_test_pool().await;
|
|
|
|
let room_repo = RoomRepository::new(pool.clone());
|
|
let member_repo = RoomMemberRepository::new(pool.clone());
|
|
|
|
let creator_id = create_test_user(&pool, &UserId::new()).await;
|
|
let room = make_room(creator_id);
|
|
let room = ok(
|
|
room_repo.create(&room).await,
|
|
"permission test room should be created",
|
|
);
|
|
|
|
let perm_service = make_perm_service(member_repo, room_repo);
|
|
|
|
let non_member_id = UserId::new();
|
|
let result = perm_service
|
|
.check_permission_no_cache(&room.id, &non_member_id, RoomPermission::SEND_CHAT_MESSAGES)
|
|
.await;
|
|
assert!(result.is_err(), "Non-member should not have permissions");
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "Requires Docker"]
|
|
async fn test_permission_bit_operations() {
|
|
let mut perms = RoomPermissionSet(0);
|
|
|
|
perms.grant(RoomPermission::SEND_CHAT_MESSAGES);
|
|
assert!(perms.has(RoomPermission::SEND_CHAT_MESSAGES));
|
|
|
|
perms.grant(RoomPermission::MANAGE_OWN_MEDIA);
|
|
assert!(perms.has(RoomPermission::SEND_CHAT_MESSAGES));
|
|
assert!(perms.has(RoomPermission::MANAGE_OWN_MEDIA));
|
|
|
|
perms.revoke(RoomPermission::SEND_CHAT_MESSAGES);
|
|
assert!(!perms.has(RoomPermission::SEND_CHAT_MESSAGES));
|
|
assert!(perms.has(RoomPermission::MANAGE_OWN_MEDIA));
|
|
|
|
perms = RoomPermissionSet::default_admin();
|
|
assert!(perms.has(RoomPermission::SEND_CHAT_MESSAGES));
|
|
assert!(perms.has(RoomPermission::REMOVE_MEMBERS));
|
|
assert!(perms.has(RoomPermission::MANAGE_ROOM_SETTINGS));
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "Requires Docker"]
|
|
async fn test_concurrent_permission_checks() {
|
|
use std::sync::Arc;
|
|
|
|
let (_container, pool) = create_test_pool().await;
|
|
|
|
let room_repo = RoomRepository::new(pool.clone());
|
|
let member_repo = RoomMemberRepository::new(pool.clone());
|
|
|
|
let creator_id = create_test_user(&pool, &UserId::new()).await;
|
|
let room = make_room(creator_id);
|
|
let room = ok(
|
|
room_repo.create(&room).await,
|
|
"permission test room should be created",
|
|
);
|
|
|
|
let user_id = create_test_user(&pool, &UserId::new()).await;
|
|
let member = make_member(room.id, user_id, RoomRole::Member);
|
|
ok(
|
|
member_repo.add(&member).await,
|
|
"permission test member should be created",
|
|
);
|
|
|
|
let perm_service = Arc::new(make_perm_service(member_repo, room_repo));
|
|
|
|
let mut handles = vec![];
|
|
for _ in 0..10 {
|
|
let service = perm_service.clone();
|
|
let room_id = room.id;
|
|
let uid = user_id;
|
|
|
|
let handle = tokio::spawn(async move {
|
|
ok(
|
|
service
|
|
.check_permission(&room_id, &uid, RoomPermission::SEND_CHAT_MESSAGES)
|
|
.await,
|
|
"permission check should succeed",
|
|
);
|
|
});
|
|
handles.push(handle);
|
|
}
|
|
|
|
// All checks should complete without error (Ok(()) means permission granted)
|
|
for handle in handles {
|
|
ok(handle.await, "permission check task should join");
|
|
}
|
|
}
|