permissions: homepage, sql + crate, add/remove/view for user
This commit is contained in:
parent
8dbeffddc9
commit
440f42ed2e
26 changed files with 880 additions and 1 deletions
|
|
@ -32,3 +32,4 @@ refresh_tokens = { path = "../refresh_tokens" }
|
|||
email = { path = "../email" }
|
||||
totp = { path = "../totp" }
|
||||
roles = { path = "../roles" }
|
||||
permissions = { path = "../permissions" }
|
||||
|
|
|
|||
|
|
@ -99,6 +99,12 @@ impl From<roles::Error> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<permissions::Error> for Error {
|
||||
fn from(e: permissions::Error) -> Self {
|
||||
Error::internal_server_error(e)
|
||||
}
|
||||
}
|
||||
|
||||
// std Types
|
||||
|
||||
impl From<String> for Error {
|
||||
|
|
|
|||
|
|
@ -43,13 +43,17 @@ impl Icon {
|
|||
"paperclip", Paperclip, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-paperclip" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M15 7l-6.5 6.5a1.5 1.5 0 0 0 3 3l6.5 -6.5a3 3 0 0 0 -6 -6l-6.5 6.5a4.5 4.5 0 0 0 9 9l6.5 -6.5"></path></svg>"#,
|
||||
"paperclip-large", PaperclipLarge, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-lg icon-tabler icon-tabler-paperclip" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M15 7l-6.5 6.5a1.5 1.5 0 0 0 3 3l6.5 -6.5a3 3 0 0 0 -6 -6l-6.5 6.5a4.5 4.5 0 0 0 9 9l6.5 -6.5"></path></svg>"#,
|
||||
"users", Users, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-users" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M9 7m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0"></path><path d="M3 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path><path d="M21 21v-2a4 4 0 0 0 -3 -3.85"></path></svg>"#,
|
||||
"users-large", UsersLarge, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-lg icon-tabler icon-tabler-users" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M9 7m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0"></path><path d="M3 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path><path d="M21 21v-2a4 4 0 0 0 -3 -3.85"></path></svg>"#,
|
||||
"mail", Mail, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-mail" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M3 7a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-10z"></path><path d="M3 7l9 6l9 -6"></path></svg>"#,
|
||||
"password", Password, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-password" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M12 10v4"></path><path d="M10 13l4 -2"></path><path d="M10 11l4 2"></path><path d="M5 10v4"></path><path d="M3 13l4 -2"></path><path d="M3 11l4 2"></path><path d="M19 10v4"></path><path d="M17 13l4 -2"></path><path d="M17 11l4 2"></path></svg>"#,
|
||||
"2fa-large", TwoFaLarge, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-lg icon-tabler icon-tabler-2fa" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 16h-4l3.47 -4.66a2 2 0 1 0 -3.47 -1.54"></path><path d="M10 16v-8h4"></path><path d="M10 12l3 0"></path><path d="M17 16v-6a2 2 0 0 1 4 0v6"></path><path d="M17 13l4 0"></path></svg>"#,
|
||||
"check", Check, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-check" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M5 12l5 5l10 -10"></path></svg>"#,
|
||||
"x", X, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-x" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M18 6l-12 12"></path><path d="M6 6l12 12"></path></svg>"#,
|
||||
"progress", Progress, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-progress" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 20.777a8.942 8.942 0 0 1 -2.48 -.969"></path><path d="M14 3.223a9.003 9.003 0 0 1 0 17.554"></path><path d="M4.579 17.093a8.961 8.961 0 0 1 -1.227 -2.592"></path><path d="M3.124 10.5c.16 -.95 .468 -1.85 .9 -2.675l.169 -.305"></path><path d="M6.907 4.579a8.954 8.954 0 0 1 3.093 -1.356"></path></svg>"#,
|
||||
"users-group", UsersGroup, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-users-group" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1"></path><path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M17 10h2a2 2 0 0 1 2 2v1"></path><path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M3 13v-1a2 2 0 0 1 2 -2h2"></path></svg>"#
|
||||
"users-group", UsersGroup, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-users-group" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1"></path><path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M17 10h2a2 2 0 0 1 2 2v1"></path><path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M3 13v-1a2 2 0 0 1 2 -2h2"></path></svg>"#,
|
||||
"users-group-large", UsersGroupLarge, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-lg icon-tabler icon-tabler-users-group" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1"></path><path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M17 10h2a2 2 0 0 1 2 2v1"></path><path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M3 13v-1a2 2 0 0 1 2 -2h2"></path></svg>"#,
|
||||
"adjustments", Adjustments, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-adjustments" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M4 10a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M6 4v4"></path><path d="M6 12v8"></path><path d="M10 16a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M12 4v10"></path><path d="M12 18v2"></path><path d="M16 7a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M18 4v1"></path><path d="M18 9v11"></path></svg>"#,
|
||||
"device-floppy", DeviceFloppy, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-device-floppy" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M6 4h10l4 4v10a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2"></path><path d="M12 14m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path><path d="M14 4l0 4l-6 0l0 -4"></path></svg>"#
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,6 +77,7 @@ pub fn icons_to_templates(tera: &mut Tera) {
|
|||
Icon::Paperclip,
|
||||
Icon::PaperclipLarge,
|
||||
Icon::Users,
|
||||
Icon::UsersLarge,
|
||||
Icon::Mail,
|
||||
Icon::Password,
|
||||
Icon::TwoFaLarge,
|
||||
|
|
@ -80,6 +85,9 @@ pub fn icons_to_templates(tera: &mut Tera) {
|
|||
Icon::X,
|
||||
Icon::Progress,
|
||||
Icon::UsersGroup,
|
||||
Icon::UsersGroupLarge,
|
||||
Icon::Adjustments,
|
||||
Icon::DeviceFloppy,
|
||||
];
|
||||
|
||||
// For each icon, it will output: ("icons/name", "<svg>...</svg>")
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ pub enum AdminMenu {
|
|||
Apps,
|
||||
Users,
|
||||
Roles,
|
||||
Permissions,
|
||||
Settings,
|
||||
}
|
||||
|
||||
|
|
@ -21,6 +22,7 @@ impl AdminMenu {
|
|||
AdminMenu::Users => "users",
|
||||
AdminMenu::Roles => "roles",
|
||||
AdminMenu::Settings => "settings",
|
||||
AdminMenu::Permissions => "permissions",
|
||||
}
|
||||
}
|
||||
pub fn list() -> Vec<MainItem> {
|
||||
|
|
@ -60,6 +62,13 @@ impl AdminMenu {
|
|||
icon: Icon::UsersGroup.svg,
|
||||
sub: None,
|
||||
},
|
||||
MainItem {
|
||||
id: AdminMenu::Permissions.id(),
|
||||
label: "Permissions",
|
||||
link: uri!(routes::admin::permissions::admin_permissions_home).to_string(),
|
||||
icon: Icon::Adjustments.svg,
|
||||
sub: None,
|
||||
},
|
||||
MainItem {
|
||||
id: AdminMenu::Settings.id(),
|
||||
label: "Server settings",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ pub enum Page {
|
|||
AdminRolesList(AdminRolesList),
|
||||
AdminRolesNew(AdminRolesNew),
|
||||
AdminRolesView(AdminRolesView),
|
||||
AdminPermissionsHome(AdminPermissionsHome),
|
||||
AdminPermissionsUsers(AdminPermissionsUsers),
|
||||
AdminPermissionsForUser(AdminPermissionsForUser),
|
||||
}
|
||||
|
||||
impl Page {
|
||||
|
|
@ -66,6 +69,9 @@ impl Page {
|
|||
Page::AdminRolesList(_) => "pages/admin/roles/list",
|
||||
Page::AdminRolesNew(_) => "pages/admin/roles/new",
|
||||
Page::AdminRolesView(_) => "pages/admin/roles/view",
|
||||
Page::AdminPermissionsHome(_) => "pages/admin/permissions/home",
|
||||
Page::AdminPermissionsUsers(_) => "pages/admin/permissions/users",
|
||||
Page::AdminPermissionsForUser(_) => "pages/admin/permissions/for-user",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -97,6 +103,9 @@ impl Page {
|
|||
Page::AdminRolesList(_) => "Roles",
|
||||
Page::AdminRolesNew(_) => "New role",
|
||||
Page::AdminRolesView(_) => "Role info",
|
||||
Page::AdminPermissionsHome(_) => "Permissions",
|
||||
Page::AdminPermissionsUsers(_) => "Users permissions",
|
||||
Page::AdminPermissionsForUser(_) => "Permissions for user",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -130,6 +139,9 @@ impl Page {
|
|||
Page::AdminRolesList(_) => Some(AdminMenu::Roles.into()),
|
||||
Page::AdminRolesNew(_) => Some(AdminMenu::Roles.into()),
|
||||
Page::AdminRolesView(_) => Some(AdminMenu::Roles.into()),
|
||||
Page::AdminPermissionsHome(_) => Some(AdminMenu::Permissions.into()),
|
||||
Page::AdminPermissionsUsers(_) => Some(AdminMenu::Permissions.into()),
|
||||
Page::AdminPermissionsForUser(_) => Some(AdminMenu::Permissions.into()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,6 +173,9 @@ impl Page {
|
|||
Page::AdminRolesList(list) => Box::new(list),
|
||||
Page::AdminRolesNew(new) => Box::new(new),
|
||||
Page::AdminRolesView(view) => Box::new(view),
|
||||
Page::AdminPermissionsHome(home) => Box::new(home),
|
||||
Page::AdminPermissionsUsers(users) => Box::new(users),
|
||||
Page::AdminPermissionsForUser(user) => Box::new(user),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use self::apps::*;
|
||||
use self::permissions::*;
|
||||
use self::roles::*;
|
||||
use self::settings::*;
|
||||
use self::users::*;
|
||||
|
|
@ -7,6 +8,7 @@ use rocket::{routes, Route};
|
|||
|
||||
pub mod apps;
|
||||
pub mod dashboard;
|
||||
pub mod permissions;
|
||||
pub mod roles;
|
||||
pub mod settings;
|
||||
pub mod users;
|
||||
|
|
@ -43,10 +45,15 @@ pub fn routes() -> Vec<Route> {
|
|||
admin_roles_view,
|
||||
admin_roles_archive,
|
||||
admin_roles_info_update,
|
||||
admin_permissions_home,
|
||||
admin_permissions_users,
|
||||
admin_permissions_for_user,
|
||||
admin_permissions_for_user_form,
|
||||
]
|
||||
}
|
||||
|
||||
pub mod content {
|
||||
use super::RolePermission;
|
||||
use apps::App;
|
||||
use jwt::JwtClaims;
|
||||
use rocket::serde::Serialize;
|
||||
|
|
@ -155,4 +162,28 @@ pub mod content {
|
|||
pub role: Role,
|
||||
pub jwt_duration: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
#[derive(Clone)]
|
||||
pub struct AdminPermissionsHome {
|
||||
pub user: JwtClaims,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
#[derive(Clone)]
|
||||
pub struct AdminPermissionsUsers {
|
||||
pub user: JwtClaims,
|
||||
pub users: Vec<User>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
#[derive(Clone)]
|
||||
pub struct AdminPermissionsForUser {
|
||||
pub user: JwtClaims,
|
||||
pub local: User,
|
||||
pub roles_permissions: Vec<RolePermission>,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
193
crates/ezidam/src/routes/admin/permissions.rs
Normal file
193
crates/ezidam/src/routes/admin/permissions.rs
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
use crate::routes::prelude::*;
|
||||
use crate::tokens::JWT_DURATION_MINUTES;
|
||||
use chrono_humanize::Humanize;
|
||||
use permissions::Permission;
|
||||
use rocket::form::validate::Contains;
|
||||
use rocket::serde::Serialize;
|
||||
use rocket::{get, post};
|
||||
use rocket_db_pools::sqlx::types::chrono::{DateTime, Utc};
|
||||
use roles::Role;
|
||||
use std::str::FromStr;
|
||||
use users::User;
|
||||
|
||||
#[get("/admin/permissions")]
|
||||
pub async fn admin_permissions_home(admin: JwtAdmin) -> Result<Page> {
|
||||
Ok(Page::AdminPermissionsHome(
|
||||
super::content::AdminPermissionsHome { user: admin.0 },
|
||||
))
|
||||
}
|
||||
|
||||
#[get("/admin/permissions/users")]
|
||||
pub async fn admin_permissions_users(
|
||||
mut db: Connection<Database>,
|
||||
admin: JwtAdmin,
|
||||
flash: Option<FlashMessage<'_>>,
|
||||
) -> Result<Template> {
|
||||
let mut transaction = db.begin().await?;
|
||||
|
||||
let users = User::get_all(&mut transaction)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|user| !user.is_archived())
|
||||
.collect();
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
let page = Page::AdminPermissionsUsers(super::content::AdminPermissionsUsers {
|
||||
user: admin.0,
|
||||
users,
|
||||
});
|
||||
|
||||
Ok(flash
|
||||
.map(|flash| Page::with_flash(page.clone(), flash))
|
||||
.unwrap_or_else(|| page.into()))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PermissionTiming {
|
||||
created_at: DateTime<Utc>,
|
||||
relative: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RolePermission {
|
||||
role: Role,
|
||||
permission: Option<PermissionTiming>,
|
||||
}
|
||||
|
||||
#[get("/admin/permissions/users/<id>")]
|
||||
pub async fn admin_permissions_for_user(
|
||||
mut db: Connection<Database>,
|
||||
admin: JwtAdmin,
|
||||
id: RocketUserID,
|
||||
flash: Option<FlashMessage<'_>>,
|
||||
) -> Result<Template> {
|
||||
let mut transaction = db.begin().await?;
|
||||
|
||||
// Get user
|
||||
let user = User::get_by_id(&mut transaction, &id.0)
|
||||
.await?
|
||||
.ok_or_else(|| Error::not_found("Failed to find user"))?;
|
||||
|
||||
if user.is_archived() {
|
||||
return Err(Error::forbidden("User is archived"));
|
||||
}
|
||||
|
||||
// Get roles
|
||||
let roles = Role::get_all(&mut transaction)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|role| !role.is_archived())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Get permissions for user
|
||||
let permissions = Permission::get_all(&mut transaction, Some(user.id()), None).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
// For every role, attempt to get existing permission
|
||||
let roles_permissions = roles
|
||||
.iter()
|
||||
.map(|role| RolePermission {
|
||||
role: role.clone(),
|
||||
permission: permissions
|
||||
.iter()
|
||||
.find(|perm| perm.role() == role.name())
|
||||
.map(|perm| PermissionTiming {
|
||||
created_at: perm.created_at(),
|
||||
relative: perm.created_at().humanize(),
|
||||
}),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let page = Page::AdminPermissionsForUser(super::content::AdminPermissionsForUser {
|
||||
user: admin.0,
|
||||
local: user,
|
||||
roles_permissions,
|
||||
});
|
||||
|
||||
Ok(flash
|
||||
.map(|flash| Page::with_flash(page.clone(), flash))
|
||||
.unwrap_or_else(|| page.into()))
|
||||
}
|
||||
|
||||
#[derive(Debug, FromForm)]
|
||||
pub struct PermissionsForUserForm<'r> {
|
||||
pub roles: Vec<&'r str>,
|
||||
}
|
||||
|
||||
#[post("/admin/permissions/users/<id>", data = "<form>")]
|
||||
pub async fn admin_permissions_for_user_form(
|
||||
_admin: JwtAdmin,
|
||||
mut db: Connection<Database>,
|
||||
id: RocketUserID,
|
||||
form: Form<PermissionsForUserForm<'_>>,
|
||||
) -> Result<Flash<Redirect>> {
|
||||
// Parse ids
|
||||
let roles_to_use = form
|
||||
.roles
|
||||
.iter()
|
||||
.map(|role| RoleID::from_str(role))
|
||||
.collect::<std::result::Result<Vec<_>, _>>()
|
||||
.map_err(|_| Error::bad_request(format!("Invalid role detected")))?;
|
||||
|
||||
let mut transaction = db.begin().await?;
|
||||
|
||||
// Get user
|
||||
let user = User::get_by_id(&mut transaction, &id.0)
|
||||
.await?
|
||||
.ok_or_else(|| Error::not_found("Failed to find user"))?;
|
||||
|
||||
if user.is_archived() {
|
||||
return Err(Error::forbidden("User is archived"));
|
||||
}
|
||||
|
||||
// Get roles
|
||||
let roles = Role::get_all(&mut transaction)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|role| !role.is_archived())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Get permissions for user
|
||||
let permissions = Permission::get_all(&mut transaction, Some(user.id()), None).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
let mut transaction = db.begin().await?;
|
||||
|
||||
// For every role, check to add or remove permissions
|
||||
for role in &roles {
|
||||
if roles_to_use.contains(role.name()) {
|
||||
// Intent is to add new permission
|
||||
|
||||
if permissions.iter().all(|perm| perm.role() != role.name()) {
|
||||
// If the permission does not exist, add it
|
||||
Permission::add(&mut transaction, user.id(), role.name()).await?;
|
||||
}
|
||||
} else {
|
||||
// Intent is to delete permission
|
||||
|
||||
if permissions.iter().any(|perm| perm.role() == role.name()) {
|
||||
// If the permission exists, delete it
|
||||
Permission::delete(&mut transaction, user.id(), role.name()).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(Flash::new(
|
||||
Redirect::to(uri!(admin_permissions_users)),
|
||||
FlashKind::Success,
|
||||
format!(
|
||||
"Permissions have been updated for {}.\
|
||||
<br>Changes can take up to {JWT_DURATION_MINUTES} minutes to appear.",
|
||||
user.username()
|
||||
),
|
||||
))
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
{% extends "shell" %}
|
||||
|
||||
{% import "utils/form" as form %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form id="permissions" action="" method="post">
|
||||
|
||||
{% set save = "Save" %}
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Roles for {{ local.username }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="col-auto ms-auto">
|
||||
<div class="btn-list">
|
||||
<button type="submit" class="btn btn-primary d-none d-sm-inline-block">
|
||||
{% include "icons/device-floppy" %}
|
||||
{{ save }}
|
||||
</button>
|
||||
|
||||
<button type="submit" class="btn btn-primary d-sm-none btn-icon" aria-label="{{ save }}">
|
||||
{% include "icons/device-floppy" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
|
||||
{% if flash %}
|
||||
<div class="alert alert-{{flash.0}}" role="alert">
|
||||
<h4 class="alert-title">{{ flash.1 }}</h4>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Roles</h3>
|
||||
</div>
|
||||
<div class="list-group list-group-flush overflow-auto">
|
||||
|
||||
{% for item in roles_permissions %}
|
||||
|
||||
{# If permission exists, show active class #}
|
||||
{% if item.permission %}
|
||||
<div class="list-group-item active">
|
||||
{% else %}
|
||||
<div class="list-group-item">
|
||||
{% endif %}
|
||||
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<div class="col-auto">
|
||||
|
||||
{# If permission exists, tick the box #}
|
||||
|
||||
{% if item.permission %}
|
||||
<input
|
||||
name="roles"
|
||||
value="{{ item.role.name }}"
|
||||
id="select-{{ item.role.name }}"
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
checked
|
||||
>
|
||||
{% else %}
|
||||
<input
|
||||
name="roles"
|
||||
value="{{ item.role.name }}"
|
||||
id="select-{{ item.role.name }}"
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col text-truncate">
|
||||
|
||||
<!-- Role -->
|
||||
<label for="select-{{ item.role.name }}" class="text-body">
|
||||
{{ item.role.label }}
|
||||
</label>
|
||||
|
||||
{# If permission exists, show how long the user belongs to the role #}
|
||||
{% if item.permission %}
|
||||
<div class="d-block text-muted text-truncate mt-1">
|
||||
Since {{ item.permission.created_at | date(format="%F %T",
|
||||
timezone=user.zoneinfo | default(value="UTC")) }}
|
||||
<div class="mt-1">
|
||||
({{ item.permission.relative }})
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
{{ form::disable_button_delay_submit(form_id="permissions") }}
|
||||
|
||||
{% endblock content %}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
{% extends "shell" %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Permissions
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="row row-cards">
|
||||
|
||||
<!-- Users -->
|
||||
<div class="col-sm-6">
|
||||
<div class="card card-md">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-uppercase font-weight-medium">Users</div>
|
||||
<div class="display-5 fw-bold my-3">
|
||||
{% include "icons/users-large" %}
|
||||
</div>
|
||||
<div class="text-center mt-4">
|
||||
<a href="permissions/users" class="btn btn-primary w-100">Permissions for users</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Roles -->
|
||||
<div class="col-sm-6">
|
||||
<div class="card card-md">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-uppercase font-weight-medium">Roles</div>
|
||||
<div class="display-5 fw-bold my-3">
|
||||
{% include "icons/users-group-large" %}
|
||||
</div>
|
||||
<div class="text-center mt-4">
|
||||
<a href="permissions/roles" class="btn btn-primary w-100">Permissions for roles</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
{% extends "shell" %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<h2 class="page-title">
|
||||
Permissions for users
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
|
||||
{% if flash %}
|
||||
<div class="alert alert-{{flash.0}}" role="alert">
|
||||
<h4 class="alert-title">{{ flash.1 | safe }}</h4>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Users</h3>
|
||||
</div>
|
||||
<div class="list-group list-group-flush overflow-auto">
|
||||
|
||||
{% for user_loop in users %}
|
||||
<div class="list-group-item">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
{{ user::avatar(username=user_loop.username, name=user_loop.name, size="sm", css="") }}
|
||||
</div>
|
||||
<div class="col text-truncate">
|
||||
<div class="text-body">
|
||||
{% if user_loop.name %}
|
||||
{{ user_loop.name }}
|
||||
{% else %}
|
||||
{{ user_loop.username }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="users/{{ user_loop.id }}" class="list-group-item-actions btn btn-outline-primary">Select</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue