permissions: add/remove/view for roles
This commit is contained in:
parent
440f42ed2e
commit
ba2bb90852
5 changed files with 369 additions and 0 deletions
|
|
@ -38,6 +38,8 @@ pub enum Page {
|
||||||
AdminPermissionsHome(AdminPermissionsHome),
|
AdminPermissionsHome(AdminPermissionsHome),
|
||||||
AdminPermissionsUsers(AdminPermissionsUsers),
|
AdminPermissionsUsers(AdminPermissionsUsers),
|
||||||
AdminPermissionsForUser(AdminPermissionsForUser),
|
AdminPermissionsForUser(AdminPermissionsForUser),
|
||||||
|
AdminPermissionsRoles(AdminPermissionsRoles),
|
||||||
|
AdminPermissionsForRole(AdminPermissionsForRole),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
|
|
@ -72,6 +74,8 @@ impl Page {
|
||||||
Page::AdminPermissionsHome(_) => "pages/admin/permissions/home",
|
Page::AdminPermissionsHome(_) => "pages/admin/permissions/home",
|
||||||
Page::AdminPermissionsUsers(_) => "pages/admin/permissions/users",
|
Page::AdminPermissionsUsers(_) => "pages/admin/permissions/users",
|
||||||
Page::AdminPermissionsForUser(_) => "pages/admin/permissions/for-user",
|
Page::AdminPermissionsForUser(_) => "pages/admin/permissions/for-user",
|
||||||
|
Page::AdminPermissionsRoles(_) => "pages/admin/permissions/roles",
|
||||||
|
Page::AdminPermissionsForRole(_) => "pages/admin/permissions/for-role",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,6 +110,8 @@ impl Page {
|
||||||
Page::AdminPermissionsHome(_) => "Permissions",
|
Page::AdminPermissionsHome(_) => "Permissions",
|
||||||
Page::AdminPermissionsUsers(_) => "Users permissions",
|
Page::AdminPermissionsUsers(_) => "Users permissions",
|
||||||
Page::AdminPermissionsForUser(_) => "Permissions for user",
|
Page::AdminPermissionsForUser(_) => "Permissions for user",
|
||||||
|
Page::AdminPermissionsRoles(_) => "Roles permissions",
|
||||||
|
Page::AdminPermissionsForRole(_) => "Permissions for role",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,6 +148,8 @@ impl Page {
|
||||||
Page::AdminPermissionsHome(_) => Some(AdminMenu::Permissions.into()),
|
Page::AdminPermissionsHome(_) => Some(AdminMenu::Permissions.into()),
|
||||||
Page::AdminPermissionsUsers(_) => Some(AdminMenu::Permissions.into()),
|
Page::AdminPermissionsUsers(_) => Some(AdminMenu::Permissions.into()),
|
||||||
Page::AdminPermissionsForUser(_) => Some(AdminMenu::Permissions.into()),
|
Page::AdminPermissionsForUser(_) => Some(AdminMenu::Permissions.into()),
|
||||||
|
Page::AdminPermissionsRoles(_) => Some(AdminMenu::Permissions.into()),
|
||||||
|
Page::AdminPermissionsForRole(_) => Some(AdminMenu::Permissions.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,6 +184,8 @@ impl Page {
|
||||||
Page::AdminPermissionsHome(home) => Box::new(home),
|
Page::AdminPermissionsHome(home) => Box::new(home),
|
||||||
Page::AdminPermissionsUsers(users) => Box::new(users),
|
Page::AdminPermissionsUsers(users) => Box::new(users),
|
||||||
Page::AdminPermissionsForUser(user) => Box::new(user),
|
Page::AdminPermissionsForUser(user) => Box::new(user),
|
||||||
|
Page::AdminPermissionsRoles(roles) => Box::new(roles),
|
||||||
|
Page::AdminPermissionsForRole(role) => Box::new(role),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,11 +49,15 @@ pub fn routes() -> Vec<Route> {
|
||||||
admin_permissions_users,
|
admin_permissions_users,
|
||||||
admin_permissions_for_user,
|
admin_permissions_for_user,
|
||||||
admin_permissions_for_user_form,
|
admin_permissions_for_user_form,
|
||||||
|
admin_permissions_roles,
|
||||||
|
admin_permissions_for_role,
|
||||||
|
admin_permissions_for_role_form,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod content {
|
pub mod content {
|
||||||
use super::RolePermission;
|
use super::RolePermission;
|
||||||
|
use super::UserPermission;
|
||||||
use apps::App;
|
use apps::App;
|
||||||
use jwt::JwtClaims;
|
use jwt::JwtClaims;
|
||||||
use rocket::serde::Serialize;
|
use rocket::serde::Serialize;
|
||||||
|
|
@ -186,4 +190,21 @@ pub mod content {
|
||||||
pub local: User,
|
pub local: User,
|
||||||
pub roles_permissions: Vec<RolePermission>,
|
pub roles_permissions: Vec<RolePermission>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AdminPermissionsRoles {
|
||||||
|
pub user: JwtClaims,
|
||||||
|
pub roles: Vec<Role>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AdminPermissionsForRole {
|
||||||
|
pub user: JwtClaims,
|
||||||
|
pub role: Role,
|
||||||
|
pub users_permissions: Vec<UserPermission>,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -191,3 +191,170 @@ pub async fn admin_permissions_for_user_form(
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/admin/permissions/roles")]
|
||||||
|
pub async fn admin_permissions_roles(
|
||||||
|
mut db: Connection<Database>,
|
||||||
|
admin: JwtAdmin,
|
||||||
|
flash: Option<FlashMessage<'_>>,
|
||||||
|
) -> Result<Template> {
|
||||||
|
let mut transaction = db.begin().await?;
|
||||||
|
|
||||||
|
let roles = Role::get_all(&mut transaction)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|role| !role.is_archived())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
let page = Page::AdminPermissionsRoles(super::content::AdminPermissionsRoles {
|
||||||
|
user: admin.0,
|
||||||
|
roles,
|
||||||
|
});
|
||||||
|
|
||||||
|
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 UserPermission {
|
||||||
|
user: User,
|
||||||
|
permission: Option<PermissionTiming>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/admin/permissions/roles/<id>")]
|
||||||
|
pub async fn admin_permissions_for_role(
|
||||||
|
mut db: Connection<Database>,
|
||||||
|
admin: JwtAdmin,
|
||||||
|
id: RocketRoleID,
|
||||||
|
flash: Option<FlashMessage<'_>>,
|
||||||
|
) -> Result<Template> {
|
||||||
|
let mut transaction = db.begin().await?;
|
||||||
|
|
||||||
|
// Get role
|
||||||
|
let role = Role::get_by_name(&mut transaction, &id.0)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| Error::not_found("Failed to find role"))?;
|
||||||
|
|
||||||
|
if role.is_archived() {
|
||||||
|
return Err(Error::forbidden("Role is archived"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get users
|
||||||
|
let users = User::get_all(&mut transaction)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|user| !user.is_archived())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Get permissions for role
|
||||||
|
let permissions = Permission::get_all(&mut transaction, None, Some(role.name())).await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
// For every user, attempt to get existing permission
|
||||||
|
let users_permissions = users
|
||||||
|
.iter()
|
||||||
|
.map(|user| UserPermission {
|
||||||
|
user: user.clone(),
|
||||||
|
permission: permissions
|
||||||
|
.iter()
|
||||||
|
.find(|perm| perm.user() == user.id())
|
||||||
|
.map(|perm| PermissionTiming {
|
||||||
|
created_at: perm.created_at(),
|
||||||
|
relative: perm.created_at().humanize(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let page = Page::AdminPermissionsForRole(super::content::AdminPermissionsForRole {
|
||||||
|
user: admin.0,
|
||||||
|
role,
|
||||||
|
users_permissions,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(flash
|
||||||
|
.map(|flash| Page::with_flash(page.clone(), flash))
|
||||||
|
.unwrap_or_else(|| page.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromForm)]
|
||||||
|
pub struct PermissionsForRoleForm<'r> {
|
||||||
|
pub users: Vec<&'r str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/admin/permissions/roles/<id>", data = "<form>")]
|
||||||
|
pub async fn admin_permissions_for_role_form(
|
||||||
|
_admin: JwtAdmin,
|
||||||
|
mut db: Connection<Database>,
|
||||||
|
id: RocketRoleID,
|
||||||
|
form: Form<PermissionsForRoleForm<'_>>,
|
||||||
|
) -> Result<Flash<Redirect>> {
|
||||||
|
// Parse ids
|
||||||
|
let users_to_use = form
|
||||||
|
.users
|
||||||
|
.iter()
|
||||||
|
.map(|user| UserID::from_str(user))
|
||||||
|
.collect::<std::result::Result<Vec<_>, _>>()
|
||||||
|
.map_err(|_| Error::bad_request(format!("Invalid user detected")))?;
|
||||||
|
|
||||||
|
let mut transaction = db.begin().await?;
|
||||||
|
|
||||||
|
// Get role
|
||||||
|
let role = Role::get_by_name(&mut transaction, &id.0)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| Error::not_found("Failed to find role"))?;
|
||||||
|
|
||||||
|
if role.is_archived() {
|
||||||
|
return Err(Error::forbidden("Role is archived"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get users
|
||||||
|
let users = User::get_all(&mut transaction)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|user| !user.is_archived())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Get permissions for role
|
||||||
|
let permissions = Permission::get_all(&mut transaction, None, Some(role.name())).await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
let mut transaction = db.begin().await?;
|
||||||
|
|
||||||
|
// For every user, check to add or remove permissions
|
||||||
|
for user in &users {
|
||||||
|
if users_to_use.contains(user.id()) {
|
||||||
|
// Intent is to add new permission
|
||||||
|
|
||||||
|
if permissions.iter().all(|perm| perm.user() != user.id()) {
|
||||||
|
// 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.user() == user.id()) {
|
||||||
|
// 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_roles)),
|
||||||
|
FlashKind::Success,
|
||||||
|
format!(
|
||||||
|
"Permissions have been updated for {}.\
|
||||||
|
<br>Changes can take up to {JWT_DURATION_MINUTES} minutes to appear.",
|
||||||
|
role.name()
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
{% 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">
|
||||||
|
Users for {{ role.name }}
|
||||||
|
</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">Users</h3>
|
||||||
|
</div>
|
||||||
|
<div class="list-group list-group-flush overflow-auto">
|
||||||
|
|
||||||
|
{% for item in users_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="users"
|
||||||
|
value="{{ item.user.id }}"
|
||||||
|
id="select-{{ item.user.id }}"
|
||||||
|
type="checkbox"
|
||||||
|
class="form-check-input"
|
||||||
|
checked
|
||||||
|
>
|
||||||
|
{% else %}
|
||||||
|
<input
|
||||||
|
name="users"
|
||||||
|
value="{{ item.user.id }}"
|
||||||
|
id="select-{{ item.user.id }}"
|
||||||
|
type="checkbox"
|
||||||
|
class="form-check-input"
|
||||||
|
>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col text-truncate">
|
||||||
|
|
||||||
|
<!-- User -->
|
||||||
|
<label for="select-{{ item.user.id }}" class="text-body">
|
||||||
|
{% if item.user.name %}
|
||||||
|
{{ item.user.name }}
|
||||||
|
{% else %}
|
||||||
|
{{ item.user.username }}
|
||||||
|
{% endif %}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{# If permission exists, show how long the role belongs to the user #}
|
||||||
|
{% 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,51 @@
|
||||||
|
{% 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 roles
|
||||||
|
</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">Roles</h3>
|
||||||
|
</div>
|
||||||
|
<div class="list-group list-group-flush overflow-auto">
|
||||||
|
|
||||||
|
{% for role in roles %}
|
||||||
|
<div class="list-group-item">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col text-truncate">
|
||||||
|
<div class="text-body">
|
||||||
|
{{ role.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<a href="roles/{{ role.name }}" 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