Add check_is_channel_participant

Refactor permission checks to load ancestor permissions into memory
for all checks to make the different logics more explicit.
This commit is contained in:
Conrad Irwin 2023-10-12 19:59:50 -06:00
parent 78432d08ca
commit a7db2aa39d
7 changed files with 292 additions and 38 deletions

View file

@ -37,8 +37,9 @@ impl Database {
}
let channel = channel::ActiveModel {
id: ActiveValue::NotSet,
name: ActiveValue::Set(name.to_string()),
..Default::default()
visibility: ActiveValue::Set(ChannelVisibility::ChannelMembers),
}
.insert(&*tx)
.await?;
@ -89,6 +90,29 @@ impl Database {
.await
}
pub async fn set_channel_visibility(
&self,
channel_id: ChannelId,
visibility: ChannelVisibility,
user_id: UserId,
) -> Result<()> {
self.transaction(move |tx| async move {
self.check_user_is_channel_admin(channel_id, user_id, &*tx)
.await?;
channel::ActiveModel {
id: ActiveValue::Unchanged(channel_id),
visibility: ActiveValue::Set(visibility),
..Default::default()
}
.update(&*tx)
.await?;
Ok(())
})
.await
}
pub async fn delete_channel(
&self,
channel_id: ChannelId,
@ -160,11 +184,11 @@ impl Database {
&self,
channel_id: ChannelId,
invitee_id: UserId,
inviter_id: UserId,
admin_id: UserId,
role: ChannelRole,
) -> Result<()> {
self.transaction(move |tx| async move {
self.check_user_is_channel_admin(channel_id, inviter_id, &*tx)
self.check_user_is_channel_admin(channel_id, admin_id, &*tx)
.await?;
channel_member::ActiveModel {
@ -262,10 +286,10 @@ impl Database {
&self,
channel_id: ChannelId,
member_id: UserId,
remover_id: UserId,
admin_id: UserId,
) -> Result<()> {
self.transaction(|tx| async move {
self.check_user_is_channel_admin(channel_id, remover_id, &*tx)
self.check_user_is_channel_admin(channel_id, admin_id, &*tx)
.await?;
let result = channel_member::Entity::delete_many()
@ -481,12 +505,12 @@ impl Database {
pub async fn set_channel_member_role(
&self,
channel_id: ChannelId,
from: UserId,
admin_id: UserId,
for_user: UserId,
role: ChannelRole,
) -> Result<()> {
self.transaction(|tx| async move {
self.check_user_is_channel_admin(channel_id, from, &*tx)
self.check_user_is_channel_admin(channel_id, admin_id, &*tx)
.await?;
let result = channel_member::Entity::update_many()
@ -613,43 +637,147 @@ impl Database {
Ok(user_ids)
}
pub async fn check_user_is_channel_member(
&self,
channel_id: ChannelId,
user_id: UserId,
tx: &DatabaseTransaction,
) -> Result<()> {
let channel_ids = self.get_channel_ancestors(channel_id, tx).await?;
channel_member::Entity::find()
.filter(
channel_member::Column::ChannelId
.is_in(channel_ids)
.and(channel_member::Column::UserId.eq(user_id)),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("user is not a channel member or channel does not exist"))?;
Ok(())
}
pub async fn check_user_is_channel_admin(
&self,
channel_id: ChannelId,
user_id: UserId,
tx: &DatabaseTransaction,
) -> Result<()> {
match self.channel_role_for_user(channel_id, user_id, tx).await? {
Some(ChannelRole::Admin) => Ok(()),
Some(ChannelRole::Member)
| Some(ChannelRole::Banned)
| Some(ChannelRole::Guest)
| None => Err(anyhow!(
"user is not a channel admin or channel does not exist"
))?,
}
}
pub async fn check_user_is_channel_member(
&self,
channel_id: ChannelId,
user_id: UserId,
tx: &DatabaseTransaction,
) -> Result<()> {
match self.channel_role_for_user(channel_id, user_id, tx).await? {
Some(ChannelRole::Admin) | Some(ChannelRole::Member) => Ok(()),
Some(ChannelRole::Banned) | Some(ChannelRole::Guest) | None => Err(anyhow!(
"user is not a channel member or channel does not exist"
))?,
}
}
pub async fn check_user_is_channel_participant(
&self,
channel_id: ChannelId,
user_id: UserId,
tx: &DatabaseTransaction,
) -> Result<()> {
match self.channel_role_for_user(channel_id, user_id, tx).await? {
Some(ChannelRole::Admin) | Some(ChannelRole::Member) | Some(ChannelRole::Guest) => {
Ok(())
}
Some(ChannelRole::Banned) | None => Err(anyhow!(
"user is not a channel participant or channel does not exist"
))?,
}
}
pub async fn channel_role_for_user(
&self,
channel_id: ChannelId,
user_id: UserId,
tx: &DatabaseTransaction,
) -> Result<Option<ChannelRole>> {
let channel_ids = self.get_channel_ancestors(channel_id, tx).await?;
channel_member::Entity::find()
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryChannelMembership {
ChannelId,
Role,
Admin,
Visibility,
}
let mut rows = channel_member::Entity::find()
.left_join(channel::Entity)
.filter(
channel_member::Column::ChannelId
.is_in(channel_ids)
.and(channel_member::Column::UserId.eq(user_id))
.and(channel_member::Column::Admin.eq(true)),
.and(channel_member::Column::UserId.eq(user_id)),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("user is not a channel admin or channel does not exist"))?;
Ok(())
.select_only()
.column(channel_member::Column::ChannelId)
.column(channel_member::Column::Role)
.column(channel_member::Column::Admin)
.column(channel::Column::Visibility)
.into_values::<_, QueryChannelMembership>()
.stream(&*tx)
.await?;
let mut is_admin = false;
let mut is_member = false;
let mut is_participant = false;
let mut is_banned = false;
let mut current_channel_visibility = None;
// note these channels are not iterated in any particular order,
// our current logic takes the highest permission available.
while let Some(row) = rows.next().await {
let (ch_id, role, admin, visibility): (
ChannelId,
Option<ChannelRole>,
bool,
ChannelVisibility,
) = row?;
match role {
Some(ChannelRole::Admin) => is_admin = true,
Some(ChannelRole::Member) => is_member = true,
Some(ChannelRole::Guest) => {
if visibility == ChannelVisibility::Public {
is_participant = true
}
}
Some(ChannelRole::Banned) => is_banned = true,
None => {
// rows created from pre-role collab server.
if admin {
is_admin = true
} else {
is_member = true
}
}
}
if channel_id == ch_id {
current_channel_visibility = Some(visibility);
}
}
// free up database connection
drop(rows);
Ok(if is_admin {
Some(ChannelRole::Admin)
} else if is_member {
Some(ChannelRole::Member)
} else if is_banned {
Some(ChannelRole::Banned)
} else if is_participant {
if current_channel_visibility.is_none() {
current_channel_visibility = channel::Entity::find()
.filter(channel::Column::Id.eq(channel_id))
.one(&*tx)
.await?
.map(|channel| channel.visibility);
}
if current_channel_visibility == Some(ChannelVisibility::Public) {
Some(ChannelRole::Guest)
} else {
None
}
} else {
None
})
}
/// Returns the channel ancestors, deepest first