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),
|
||||
AdminPermissionsUsers(AdminPermissionsUsers),
|
||||
AdminPermissionsForUser(AdminPermissionsForUser),
|
||||
AdminPermissionsRoles(AdminPermissionsRoles),
|
||||
AdminPermissionsForRole(AdminPermissionsForRole),
|
||||
}
|
||||
|
||||
impl Page {
|
||||
|
|
@ -72,6 +74,8 @@ impl Page {
|
|||
Page::AdminPermissionsHome(_) => "pages/admin/permissions/home",
|
||||
Page::AdminPermissionsUsers(_) => "pages/admin/permissions/users",
|
||||
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::AdminPermissionsUsers(_) => "Users permissions",
|
||||
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::AdminPermissionsUsers(_) => 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::AdminPermissionsUsers(users) => Box::new(users),
|
||||
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_for_user,
|
||||
admin_permissions_for_user_form,
|
||||
admin_permissions_roles,
|
||||
admin_permissions_for_role,
|
||||
admin_permissions_for_role_form,
|
||||
]
|
||||
}
|
||||
|
||||
pub mod content {
|
||||
use super::RolePermission;
|
||||
use super::UserPermission;
|
||||
use apps::App;
|
||||
use jwt::JwtClaims;
|
||||
use rocket::serde::Serialize;
|
||||
|
|
@ -186,4 +190,21 @@ pub mod content {
|
|||
pub local: User,
|
||||
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