summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorColin Reeder <vpzomtrrfrt@gmail.com>2020-10-23 20:40:37 -0600
committerColin Reeder <vpzomtrrfrt@gmail.com>2020-10-23 20:40:37 -0600
commitbda4da06734247e128f08f17a7100773889ca325 (patch)
tree1fc6516bd28b4c728f5b838e17b6766eada0dbba
parent78e3a60d7be71e0e7eab4ab638790bca70807159 (diff)
Allow site admins to delete posts & comments
-rw-r--r--openapi/openapi.json6
-rw-r--r--src/main.rs10
-rw-r--r--src/routes/api/comments.rs25
-rw-r--r--src/routes/api/mod.rs6
-rw-r--r--src/routes/api/posts.rs33
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