Make a distinction between moderators and admins (#619)
* Make a distinction between moderators and admins And rework the user list in the moderation interface, to be able to run the same action on many users, and to have a huge list of actions whithout loosing space. * Make user's role an enum + make it impossible for a moderator to escalate privileges With the help of diesel-derive-enum (maybe it could be used in other places too?) Also, moderators are still able to grant or revoke moderation rights to other people, but maybe only admins should be able to do it? * Cargo fmt * copy/pasting is bad * Remove diesel-derive-enum and use an integer instead It was not compatible with both Postgres and SQlite, because for one it generated a schema with the "User_role" type, but for the other it was "Text"… * Reset translations * Use an enum to avoid magic numbers + fix the tests * Reset translations * Fix down.sql
This commit is contained in:
@@ -14,10 +14,26 @@ impl<'a, 'r> FromRequest<'a, 'r> for Admin {
|
||||
|
||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Admin, ()> {
|
||||
let user = request.guard::<User>()?;
|
||||
if user.is_admin {
|
||||
if user.is_admin() {
|
||||
Outcome::Success(Admin(user))
|
||||
} else {
|
||||
Outcome::Failure((Status::Unauthorized, ()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `Admin` but for moderators.
|
||||
pub struct Moderator(pub User);
|
||||
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for Moderator {
|
||||
type Error = ();
|
||||
|
||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Moderator, ()> {
|
||||
let user = request.guard::<User>()?;
|
||||
if user.is_moderator() {
|
||||
Outcome::Success(Moderator(user))
|
||||
} else {
|
||||
Outcome::Failure((Status::Unauthorized, ()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use medias::Media;
|
||||
use plume_common::utils::md_to_html;
|
||||
use safe_string::SafeString;
|
||||
use schema::{instances, users};
|
||||
use users::User;
|
||||
use users::{Role, User};
|
||||
use {Connection, Error, Result};
|
||||
|
||||
#[derive(Clone, Identifiable, Queryable)]
|
||||
@@ -117,7 +117,7 @@ impl Instance {
|
||||
pub fn has_admin(&self, conn: &Connection) -> Result<bool> {
|
||||
users::table
|
||||
.filter(users::instance_id.eq(self.id))
|
||||
.filter(users::is_admin.eq(true))
|
||||
.filter(users::role.eq(Role::Admin as i32))
|
||||
.load::<User>(conn)
|
||||
.map_err(Error::from)
|
||||
.map(|r| !r.is_empty())
|
||||
@@ -126,7 +126,7 @@ impl Instance {
|
||||
pub fn main_admin(&self, conn: &Connection) -> Result<User> {
|
||||
users::table
|
||||
.filter(users::instance_id.eq(self.id))
|
||||
.filter(users::is_admin.eq(true))
|
||||
.filter(users::role.eq(Role::Admin as i32))
|
||||
.limit(1)
|
||||
.get_result::<User>(conn)
|
||||
.map_err(Error::from)
|
||||
|
||||
@@ -375,6 +375,7 @@ pub mod post_authors;
|
||||
pub mod posts;
|
||||
pub mod reshares;
|
||||
pub mod safe_string;
|
||||
#[allow(unused_imports)]
|
||||
pub mod schema;
|
||||
pub mod search;
|
||||
pub mod tags;
|
||||
|
||||
@@ -202,7 +202,6 @@ table! {
|
||||
display_name -> Varchar,
|
||||
outbox_url -> Varchar,
|
||||
inbox_url -> Varchar,
|
||||
is_admin -> Bool,
|
||||
summary -> Text,
|
||||
email -> Nullable<Text>,
|
||||
hashed_password -> Nullable<Text>,
|
||||
@@ -217,6 +216,7 @@ table! {
|
||||
last_fetched_date -> Timestamp,
|
||||
fqn -> Text,
|
||||
summary_html -> Text,
|
||||
role -> Int4,
|
||||
preferred_theme -> Nullable<Varchar>,
|
||||
hide_custom_css -> Bool,
|
||||
}
|
||||
|
||||
+31
-22
@@ -52,6 +52,12 @@ use {ap_url, Connection, Error, PlumeRocket, Result};
|
||||
|
||||
pub type CustomPerson = CustomObject<ApSignature, Person>;
|
||||
|
||||
pub enum Role {
|
||||
Admin = 0,
|
||||
Moderator = 1,
|
||||
Normal = 2,
|
||||
}
|
||||
|
||||
#[derive(Queryable, Identifiable, Clone, Debug, AsChangeset)]
|
||||
pub struct User {
|
||||
pub id: i32,
|
||||
@@ -59,7 +65,6 @@ pub struct User {
|
||||
pub display_name: String,
|
||||
pub outbox_url: String,
|
||||
pub inbox_url: String,
|
||||
pub is_admin: bool,
|
||||
pub summary: String,
|
||||
pub email: Option<String>,
|
||||
pub hashed_password: Option<String>,
|
||||
@@ -74,6 +79,10 @@ pub struct User {
|
||||
pub last_fetched_date: NaiveDateTime,
|
||||
pub fqn: String,
|
||||
pub summary_html: SafeString,
|
||||
/// 0 = admin
|
||||
/// 1 = moderator
|
||||
/// anything else = normal user
|
||||
pub role: i32,
|
||||
pub preferred_theme: Option<String>,
|
||||
pub hide_custom_css: bool,
|
||||
}
|
||||
@@ -85,7 +94,6 @@ pub struct NewUser {
|
||||
pub display_name: String,
|
||||
pub outbox_url: String,
|
||||
pub inbox_url: String,
|
||||
pub is_admin: bool,
|
||||
pub summary: String,
|
||||
pub email: Option<String>,
|
||||
pub hashed_password: Option<String>,
|
||||
@@ -97,6 +105,7 @@ pub struct NewUser {
|
||||
pub followers_endpoint: String,
|
||||
pub avatar_id: Option<i32>,
|
||||
pub summary_html: SafeString,
|
||||
pub role: i32,
|
||||
pub fqn: String,
|
||||
}
|
||||
|
||||
@@ -110,6 +119,14 @@ impl User {
|
||||
find_by!(users, find_by_name, username as &str, instance_id as i32);
|
||||
find_by!(users, find_by_ap_url, ap_url as &str);
|
||||
|
||||
pub fn is_moderator(&self) -> bool {
|
||||
self.role == Role::Admin as i32 || self.role == Role::Moderator as i32
|
||||
}
|
||||
|
||||
pub fn is_admin(&self) -> bool {
|
||||
self.role == Role::Admin as i32
|
||||
}
|
||||
|
||||
pub fn one_by_instance(conn: &Connection) -> Result<Vec<User>> {
|
||||
users::table
|
||||
.filter(users::instance_id.eq_any(users::table.select(users::instance_id).distinct()))
|
||||
@@ -162,17 +179,9 @@ impl User {
|
||||
Instance::get(conn, self.instance_id)
|
||||
}
|
||||
|
||||
pub fn grant_admin_rights(&self, conn: &Connection) -> Result<()> {
|
||||
pub fn set_role(&self, conn: &Connection, new_role: Role) -> Result<()> {
|
||||
diesel::update(self)
|
||||
.set(users::is_admin.eq(true))
|
||||
.execute(conn)
|
||||
.map(|_| ())
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn revoke_admin_rights(&self, conn: &Connection) -> Result<()> {
|
||||
diesel::update(self)
|
||||
.set(users::is_admin.eq(false))
|
||||
.set(users::role.eq(new_role as i32))
|
||||
.execute(conn)
|
||||
.map(|_| ())
|
||||
.map_err(Error::from)
|
||||
@@ -762,7 +771,7 @@ impl FromId<PlumeRocket> for User {
|
||||
username,
|
||||
outbox_url: acct.object.ap_actor_props.outbox_string()?,
|
||||
inbox_url: acct.object.ap_actor_props.inbox_string()?,
|
||||
is_admin: false,
|
||||
role: 2,
|
||||
summary: acct
|
||||
.object
|
||||
.object_props
|
||||
@@ -879,7 +888,7 @@ impl NewUser {
|
||||
conn: &Connection,
|
||||
username: String,
|
||||
display_name: String,
|
||||
is_admin: bool,
|
||||
role: Role,
|
||||
summary: &str,
|
||||
email: String,
|
||||
password: String,
|
||||
@@ -892,7 +901,7 @@ impl NewUser {
|
||||
NewUser {
|
||||
username: username.clone(),
|
||||
display_name,
|
||||
is_admin,
|
||||
role: role as i32,
|
||||
summary: summary.to_owned(),
|
||||
summary_html: SafeString::new(&utils::md_to_html(&summary, None, false, None).0),
|
||||
email: Some(email),
|
||||
@@ -927,7 +936,7 @@ pub(crate) mod tests {
|
||||
conn,
|
||||
"admin".to_owned(),
|
||||
"The admin".to_owned(),
|
||||
true,
|
||||
Role::Admin,
|
||||
"Hello there, I'm the admin",
|
||||
"admin@example.com".to_owned(),
|
||||
"invalid_admin_password".to_owned(),
|
||||
@@ -937,7 +946,7 @@ pub(crate) mod tests {
|
||||
conn,
|
||||
"user".to_owned(),
|
||||
"Some user".to_owned(),
|
||||
false,
|
||||
Role::Normal,
|
||||
"Hello there, I'm no one",
|
||||
"user@example.com".to_owned(),
|
||||
"invalid_user_password".to_owned(),
|
||||
@@ -947,7 +956,7 @@ pub(crate) mod tests {
|
||||
conn,
|
||||
"other".to_owned(),
|
||||
"Another user".to_owned(),
|
||||
false,
|
||||
Role::Normal,
|
||||
"Hello there, I'm someone else",
|
||||
"other@example.com".to_owned(),
|
||||
"invalid_other_password".to_owned(),
|
||||
@@ -966,7 +975,7 @@ pub(crate) mod tests {
|
||||
conn,
|
||||
"test".to_owned(),
|
||||
"test user".to_owned(),
|
||||
false,
|
||||
Role::Normal,
|
||||
"Hello I'm a test",
|
||||
"test@example.com".to_owned(),
|
||||
User::hash_pass("test_password").unwrap(),
|
||||
@@ -1031,11 +1040,11 @@ pub(crate) mod tests {
|
||||
local_inst
|
||||
.main_admin(conn)
|
||||
.unwrap()
|
||||
.revoke_admin_rights(conn)
|
||||
.set_role(conn, Role::Normal)
|
||||
.unwrap();
|
||||
i += 1;
|
||||
}
|
||||
inserted[0].grant_admin_rights(conn).unwrap();
|
||||
inserted[0].set_role(conn, Role::Admin).unwrap();
|
||||
assert_eq!(inserted[0].id, local_inst.main_admin(conn).unwrap().id);
|
||||
|
||||
Ok(())
|
||||
@@ -1051,7 +1060,7 @@ pub(crate) mod tests {
|
||||
conn,
|
||||
"test".to_owned(),
|
||||
"test user".to_owned(),
|
||||
false,
|
||||
Role::Normal,
|
||||
"Hello I'm a test",
|
||||
"test@example.com".to_owned(),
|
||||
User::hash_pass("test_password").unwrap(),
|
||||
|
||||
Reference in New Issue
Block a user