diff options
author | Colin Reeder <vpzomtrrfrt@gmail.com> | 2020-10-23 20:40:37 -0600 |
---|---|---|
committer | Colin Reeder <vpzomtrrfrt@gmail.com> | 2020-10-23 20:40:37 -0600 |
commit | bda4da06734247e128f08f17a7100773889ca325 (patch) | |
tree | 1fc6516bd28b4c728f5b838e17b6766eada0dbba | |
parent | 78e3a60d7be71e0e7eab4ab638790bca70807159 (diff) |
Allow site admins to delete posts & comments
-rw-r--r-- | openapi/openapi.json | 6 | ||||
-rw-r--r-- | src/main.rs | 10 | ||||
-rw-r--r-- | src/routes/api/comments.rs | 25 | ||||
-rw-r--r-- | src/routes/api/mod.rs | 6 | ||||
-rw-r--r-- | src/routes/api/posts.rs | 33 |
5 files changed, 55 insertions, 25 deletions
diff --git a/openapi/openapi.json b/openapi/openapi.json index 1ac57ff..73814a7 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -136,7 +136,7 @@ }, "PostCommentInfo": { "type": "object", - "required": ["id", "content_text", "content_html", "author", "created", "deleted", "replies", "has_replies", "attachments"], + "required": ["id", "content_text", "content_html", "author", "created", "deleted", "replies", "has_replies", "attachments", "local"], "properties": { "id": {"type": "integer"}, "content_text": {"type": "string", "nullable": true}, @@ -156,6 +156,7 @@ }, "created": {"type": "string", "format": "date-time"}, "deleted": {"type": "boolean"}, + "local": {"type": "boolean"}, "replies": { "type": "array", "nullable": true, @@ -1135,9 +1136,10 @@ "schema": { "allOf": [{"$ref": "#/components/schemas/PostListPost"}], "type": "object", - "required": ["approved", "replies"], + "required": ["approved", "local", "replies"], "properties": { "approved": {"type": "boolean"}, + "local": {"type": "local"}, "replies": { "type": "array", "items": {"$ref": "#/components/schemas/PostCommentInfo"} diff --git a/src/main.rs b/src/main.rs index 85e7e0c..569beb0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -631,6 +631,16 @@ pub async fn require_login( }) } +pub async fn is_site_admin(db: &tokio_postgres::Client, user: UserLocalID) -> Result<bool, Error> { + let row = db + .query_opt("SELECT is_site_admin FROM person WHERE id=$1", &[&user]) + .await?; + Ok(match row { + None => false, + Some(row) => row.get(0), + }) +} + pub fn spawn_task<F: std::future::Future<Output = Result<(), Error>> + Send + 'static>(task: F) { use futures::future::TryFutureExt; tokio::spawn(task.map_err(|err| { diff --git a/src/routes/api/comments.rs b/src/routes/api/comments.rs index a4d683d..274b315 100644 --- a/src/routes/api/comments.rs +++ b/src/routes/api/comments.rs @@ -119,6 +119,7 @@ async fn route_unstable_comments_get( author, created: created.to_rfc3339(), deleted: row.get(10), + local: row.get(4), has_replies: !replies.is_empty(), replies: Some(replies), your_vote, @@ -144,11 +145,11 @@ async fn route_unstable_comments_delete( let lang = crate::get_lang_for_req(&req); let db = ctx.db_pool.get().await?; - let user = crate::require_login(&req, &db).await?; + let login_user = crate::require_login(&req, &db).await?; let row = db .query_opt( - "SELECT author, (SELECT community FROM post WHERE id=reply.post) FROM reply WHERE id=$1 AND deleted=FALSE", + "SELECT author, (SELECT community FROM post WHERE id=reply.post), local FROM reply WHERE id=$1 AND deleted=FALSE", &[&comment_id], ) .await?; @@ -156,13 +157,19 @@ async fn route_unstable_comments_delete( None => Ok(crate::empty_response()), // already gone Some(row) => { let author = row.get::<_, Option<_>>(0).map(UserLocalID); - if author != Some(user) { - return Err(crate::Error::UserError(crate::simple_response( - hyper::StatusCode::FORBIDDEN, - lang.tr("comment_not_yours", None).into_owned(), - ))); + if author != Some(login_user) { + if row.get(2) && crate::is_site_admin(&db, login_user).await? { + // still ok + } else { + return Err(crate::Error::UserError(crate::simple_response( + hyper::StatusCode::FORBIDDEN, + lang.tr("comment_not_yours", None).into_owned(), + ))); + } } + let actor = author.unwrap_or(login_user); + db.execute( "UPDATE reply SET content_text='[deleted]', content_markdown=NULL, content_html=NULL, deleted=TRUE WHERE id=$1", &[&comment_id], @@ -174,7 +181,7 @@ async fn route_unstable_comments_delete( if let Some(community) = community { let delete_ap = crate::apub_util::local_comment_delete_to_ap( comment_id, - user, + actor, &ctx.host_url_apub, )?; let row = db.query_one("SELECT local, ap_id, COALESCE(ap_shared_inbox, ap_inbox) FROM community WHERE id=$1", &[&community]).await?; @@ -195,7 +202,7 @@ async fn route_unstable_comments_delete( crate::spawn_task(async move { ctx.enqueue_task(&crate::tasks::DeliverToInbox { inbox: Cow::Owned(community_inbox.parse()?), - sign_as: Some(crate::ActorLocalRef::Person(user)), + sign_as: Some(crate::ActorLocalRef::Person(actor)), object: body, }) .await diff --git a/src/routes/api/mod.rs b/src/routes/api/mod.rs index f8e55c1..d62123d 100644 --- a/src/routes/api/mod.rs +++ b/src/routes/api/mod.rs @@ -130,6 +130,7 @@ struct RespPostCommentInfo<'a> { author: Option<RespMinimalAuthorInfo<'a>>, created: String, deleted: bool, + local: bool, replies: Option<Vec<RespPostCommentInfo<'a>>>, has_replies: bool, #[serde(skip_serializing_if = "Option::is_none")] @@ -628,7 +629,7 @@ async fn get_comments_replies<'a>( ) -> Result<HashMap<CommentLocalID, Vec<RespPostCommentInfo<'a>>>, crate::Error> { use futures::TryStreamExt; - let sql1 = "SELECT reply.id, reply.author, reply.content_text, reply.created, reply.parent, reply.content_html, person.username, person.local, person.ap_id, reply.deleted, person.avatar, reply.attachment_href"; + let sql1 = "SELECT reply.id, reply.author, reply.content_text, reply.created, reply.parent, reply.content_html, person.username, person.local, person.ap_id, reply.deleted, person.avatar, reply.attachment_href, reply.local"; let (sql2, values): (_, Vec<&(dyn tokio_postgres::types::ToSql + Sync)>) = if include_your_for.is_some() { ( @@ -694,11 +695,12 @@ async fn get_comments_replies<'a>( author, created: created.to_rfc3339(), deleted: row.get(9), + local: row.get(12), replies: None, has_replies: false, your_vote: match include_your_for { None => None, - Some(_) => Some(if row.get(12) { + Some(_) => Some(if row.get(13) { Some(crate::Empty {}) } else { None diff --git a/src/routes/api/posts.rs b/src/routes/api/posts.rs index 74eae9f..b1548be 100644 --- a/src/routes/api/posts.rs +++ b/src/routes/api/posts.rs @@ -16,7 +16,7 @@ async fn get_post_comments<'a>( ) -> Result<Vec<RespPostCommentInfo<'a>>, crate::Error> { use futures::TryStreamExt; - let sql1 = "SELECT reply.id, reply.author, reply.content_text, reply.created, reply.content_html, person.username, person.local, person.ap_id, reply.deleted, person.avatar, attachment_href"; + let sql1 = "SELECT reply.id, reply.author, reply.content_text, reply.created, reply.content_html, person.username, person.local, person.ap_id, reply.deleted, person.avatar, attachment_href, reply.local"; let (sql2, values): (_, Vec<&(dyn tokio_postgres::types::ToSql + Sync)>) = if include_your_for.is_some() { ( @@ -81,11 +81,12 @@ async fn get_post_comments<'a>( author, created: created.to_rfc3339(), deleted: row.get(8), + local: row.get(11), replies: None, has_replies: false, your_vote: match include_your_for { None => None, - Some(_) => Some(if row.get(11) { + Some(_) => Some(if row.get(12) { Some(crate::Empty {}) } else { None @@ -277,6 +278,7 @@ async fn route_unstable_posts_get( #[serde(flatten)] post: &'a RespPostListPost<'a>, approved: bool, + local: bool, replies: Vec<RespPostCommentInfo<'a>>, } @@ -284,7 +286,7 @@ async fn route_unstable_posts_get( let (row, comments, your_vote) = futures::future::try_join3( db.query_opt( - "SELECT post.author, post.href, post.content_text, post.title, post.created, post.content_html, community.id, community.name, community.local, community.ap_id, person.username, person.local, person.ap_id, (SELECT COUNT(*) FROM post_like WHERE post_like.post = $1), post.approved, person.avatar FROM community, post LEFT OUTER JOIN person ON (person.id = post.author) WHERE post.community = community.id AND post.id = $1", + "SELECT post.author, post.href, post.content_text, post.title, post.created, post.content_html, community.id, community.name, community.local, community.ap_id, person.username, person.local, person.ap_id, (SELECT COUNT(*) FROM post_like WHERE post_like.post = $1), post.approved, person.avatar, post.local FROM community, post LEFT OUTER JOIN person ON (person.id = post.author) WHERE post.community = community.id AND post.id = $1", &[&post_id], ) .map_err(crate::Error::from), @@ -367,6 +369,7 @@ async fn route_unstable_posts_get( let output = RespPostInfo { post: &post, + local: row.get(16), replies: comments, approved: row.get(14), }; @@ -386,11 +389,11 @@ async fn route_unstable_posts_delete( let lang = crate::get_lang_for_req(&req); let db = ctx.db_pool.get().await?; - let user = crate::require_login(&req, &db).await?; + let login_user = crate::require_login(&req, &db).await?; let row = db .query_opt( - "SELECT author, community FROM post WHERE id=$1 AND deleted=FALSE", + "SELECT author, community, local FROM post WHERE id=$1 AND deleted=FALSE", &[&post_id], ) .await?; @@ -398,13 +401,19 @@ async fn route_unstable_posts_delete( None => Ok(crate::empty_response()), // already gone Some(row) => { let author = row.get::<_, Option<_>>(0).map(UserLocalID); - if author != Some(user) { - return Err(crate::Error::UserError(crate::simple_response( - hyper::StatusCode::FORBIDDEN, - lang.tr("post_not_yours", None).into_owned(), - ))); + if author != Some(login_user) { + if row.get(2) && crate::is_site_admin(&db, login_user).await? { + // still ok + } else { + return Err(crate::Error::UserError(crate::simple_response( + hyper::StatusCode::FORBIDDEN, + lang.tr("post_not_yours", None).into_owned(), + ))); + } } + let actor = author.unwrap_or(login_user); + db.execute("UPDATE post SET had_href=(href IS NOT NULL), href=NULL, title='[deleted]', content_text='[deleted]', content_markdown=NULL, content_html=NULL, deleted=TRUE WHERE id=$1", &[&post_id]).await?; crate::spawn_task(async move { @@ -412,7 +421,7 @@ async fn route_unstable_posts_delete( if let Some(community) = community { let delete_ap = crate::apub_util::local_post_delete_to_ap( post_id, - user, + actor, &ctx.host_url_apub, )?; let row = db.query_one("SELECT local, ap_id, COALESCE(ap_shared_inbox, ap_inbox) FROM community WHERE id=$1", &[&community]).await?; @@ -433,7 +442,7 @@ async fn route_unstable_posts_delete( crate::spawn_task(async move { ctx.enqueue_task(&crate::tasks::DeliverToInbox { inbox: Cow::Owned(community_inbox.parse()?), - sign_as: Some(crate::ActorLocalRef::Person(user)), + sign_as: Some(crate::ActorLocalRef::Person(actor)), object: serde_json::to_string(&delete_ap)?, }) .await |