💥 (admin): Exige un permiso propio en cada página

This commit is contained in:
Manuel Cillero 2026-10-04 23:50:58 +02:00
parent 2299e63585
commit f337119219
6 changed files with 31 additions and 79 deletions

View file

@ -87,9 +87,8 @@ impl ActionBag {
/// bag.add(AdminSection {
/// key: "tools".to_owned(),
/// path: "/admin/tools".to_owned(),
/// title: Lc::n("Tools"),
/// permission: None,
/// weight: 60,
/// title: Lc::n("Tools"),
/// weight: 60,
/// });
/// }
/// // En Extension::actions():
@ -130,10 +129,18 @@ impl DeclareAdminSections {
/// # Ejemplo
///
/// ```rust,no_run
/// use pagetop::locale::Lc;
/// use pagetop::prelude::*;
/// use pagetop_admin::action::{DeclareAdminPages, PageBag};
/// use pagetop_admin::registry::{AdminPage, AdminPageKind};
///
/// struct ExportPermission;
///
/// impl Permission for ExportPermission {
/// fn key(&self) -> CowStr {
/// "tools:export".into()
/// }
/// }
///
/// fn declare_pages(bag: &mut PageBag) {
/// bag.add(AdminPage {
/// path: "/admin/tools/export".to_owned(),
@ -141,7 +148,7 @@ impl DeclareAdminSections {
/// title: Lc::n("Export"),
/// description: Some(Lc::n("Export site data.")),
/// weight: 0,
/// permission: None,
/// permission: &ExportPermission,
/// kind: AdminPageKind::View,
/// });
/// }

View file

@ -69,7 +69,6 @@ fn render_sections(cx: &Context, candidates: &[&'static AdminSection]) -> Option
let sections_with_pages: Vec<_> = candidates
.iter()
.copied()
.filter(|section| section.is_visible(cx))
.map(|section| {
let pages: Vec<_> = reg
.pages_for_section(&section.key)
@ -131,7 +130,7 @@ pub async fn config_form_get(request: HttpRequest) -> Result<Markup, ErrorPage>
return Err(ErrorPage::NotFound(Some(request.clone())));
};
require_permission(&request, page.permission_key())?;
require_permission(&request, page.permission)?;
let title = page.title.clone();
let mut form = ConfigForm::with_schema(schema.clone());
@ -170,7 +169,7 @@ pub async fn config_form_post(
return Err(ErrorPage::NotFound(Some(request.clone())));
};
require_permission(&request, page.permission_key())?;
require_permission(&request, page.permission)?;
let mut save_error = false;

View file

@ -50,7 +50,7 @@ fn declare_pages(bag: &mut PageBag) {
title: Lc::n("My App"),
description: Some(Lc::n("Configure My App.")),
weight: 0,
permission: Some(&MyPermission::Config),
permission: &MyPermission::Config,
kind: AdminPageKind::View,
});
}
@ -63,7 +63,7 @@ enum MyPermission {
impl Permission for MyPermission {
fn key(&self) -> CowStr {
match self {
Self::Config => "myapp.config".into(),
Self::Config => "myapp:config".into(),
}
}
}

View file

@ -14,38 +14,12 @@ use crate::action::{
};
use crate::settings::SettingsSchema;
// **< AdminPermission >****************************************************************************
/// Permisos propios de `pagetop-admin`.
#[derive(Clone, Copy, Debug)]
pub enum AdminPermission {
/// Acceso por defecto a una página de administración que no declara un permiso propio.
Access,
/// Acceso a la sección integrada "people".
AccessPeople,
/// Acceso a la sección integrada "structure".
AccessStructure,
/// Acceso a la sección integrada "config".
AccessConfig,
/// Acceso a la sección integrada "reports".
AccessReports,
}
impl Permission for AdminPermission {
fn key(&self) -> CowStr {
match self {
Self::Access => "admin:access".into(),
Self::AccessPeople => "admin.access_people".into(),
Self::AccessStructure => "admin.access_structure".into(),
Self::AccessConfig => "admin.access_config".into(),
Self::AccessReports => "admin.access_reports".into(),
}
}
}
// **< Tipos del registro >*************************************************************************
/// Sección del panel de administración (agrupación en el sidebar).
/// Sección del panel de administración (agrupación de páginas en el menú y en el *dashboard*).
///
/// Una sección no tiene permiso propio: se muestra a quien pueda acceder a alguna de sus páginas, y
/// se oculta si no le queda ninguna.
#[derive(Clone)]
pub struct AdminSection {
/// Identificador único de la sección (p. ej. `"config"`).
@ -54,24 +28,10 @@ pub struct AdminSection {
pub path: String,
/// Título visible en el sidebar.
pub title: Lc,
/// Permiso requerido para ver la sección (`None` = siempre visible).
pub permission: Option<PermissionRef>,
/// Peso para ordenar en el sidebar (menor = antes).
pub weight: i32,
}
impl AdminSection {
/// Devuelve `true` si el usuario actual puede ver esta sección.
pub fn is_visible(&self, cx: &Context) -> bool {
match self.permission {
None => true,
Some(permission) => cx
.request()
.is_some_and(|request| has_permission(request, permission)),
}
}
}
/// Página del panel de administración.
#[derive(Clone)]
pub struct AdminPage {
@ -85,22 +45,18 @@ pub struct AdminPage {
pub description: Option<Lc>,
/// Peso dentro de la sección (menor = antes).
pub weight: i32,
/// Permiso requerido para acceder (`None` = requiere [`AdminPermission::Access`]).
pub permission: Option<PermissionRef>,
/// Permiso requerido para acceder. Es obligatorio: cada página declara el suyo, y es también
/// el que decide si su sección se muestra.
pub permission: PermissionRef,
/// Tipo de página y datos asociados.
pub kind: AdminPageKind,
}
impl AdminPage {
/// Permiso efectivo: el declarado, o [`AdminPermission::Access`] si no se especificó ninguno.
pub fn permission_key(&self) -> PermissionRef {
self.permission.unwrap_or(&AdminPermission::Access)
}
/// Devuelve `true` si el usuario actual puede acceder a esta página.
pub fn is_accessible(&self, cx: &Context) -> bool {
cx.request()
.is_some_and(|request| has_permission(request, self.permission_key()))
.is_some_and(|request| has_permission(request, self.permission))
}
}
@ -277,11 +233,9 @@ pub fn global() -> &'static AdminRegistry {
pub fn can_access_admin(cx: &Context) -> bool {
let reg = global();
reg.ordered_sections().into_iter().any(|section| {
section.is_visible(cx)
&& reg
.pages_for_section(&section.key)
.into_iter()
.any(|page| page.is_accessible(cx))
reg.pages_for_section(&section.key)
.into_iter()
.any(|page| page.is_accessible(cx))
})
}
@ -306,9 +260,6 @@ pub fn admin_navbar(cx: &Context) -> Navbar {
));
for section in reg.ordered_sections() {
if !section.is_visible(cx) {
continue;
}
let pages: Vec<_> = reg
.pages_for_section(&section.key)
.into_iter()

View file

@ -3,7 +3,7 @@ use pagetop::prelude::*;
use crate::ADMIN_BASE_PATH;
use crate::LOCALES_ADMIN;
use crate::action::SectionBag;
use crate::registry::{AdminPermission, AdminSection};
use crate::registry::AdminSection;
/// Declara las secciones incorporadas del panel de administración.
///
@ -16,35 +16,30 @@ pub(crate) fn declare_default_sections(bag: &mut SectionBag) {
key: "people".to_owned(),
path: format!("{}/people", base),
title: Lc::t("section-people", &LOCALES_ADMIN),
permission: Some(&AdminPermission::AccessPeople),
weight: 10,
});
bag.add(AdminSection {
key: "structure".to_owned(),
path: format!("{}/structure", base),
title: Lc::t("section-structure", &LOCALES_ADMIN),
permission: Some(&AdminPermission::AccessStructure),
weight: 20,
});
bag.add(AdminSection {
key: "config".to_owned(),
path: format!("{}/config", base),
title: Lc::t("section-config", &LOCALES_ADMIN),
permission: Some(&AdminPermission::AccessConfig),
weight: 30,
});
bag.add(AdminSection {
key: "reports".to_owned(),
path: format!("{}/reports", base),
title: Lc::t("section-reports", &LOCALES_ADMIN),
permission: Some(&AdminPermission::AccessReports),
weight: 40,
});
bag.add(AdminSection {
key: "help".to_owned(),
path: format!("{}/help", base),
title: Lc::t("section-help", &LOCALES_ADMIN),
permission: None,
weight: 50,
});
}

View file

@ -155,7 +155,7 @@ fn declare_admin_pages(bag: &mut PageBag) {
title: Lc::t("title-admin-users", &LOCALES_USER),
description: Some(Lc::t("description-admin-users", &LOCALES_USER)),
weight: 0,
permission: Some(&permission::UserPermission::AdminUsers),
permission: &permission::UserPermission::AdminUsers,
kind: AdminPageKind::View,
});
bag.add(AdminPage {
@ -164,7 +164,7 @@ fn declare_admin_pages(bag: &mut PageBag) {
title: Lc::t("title-admin-roles", &LOCALES_USER),
description: Some(Lc::t("description-admin-roles", &LOCALES_USER)),
weight: 10,
permission: Some(&permission::UserPermission::AdminRoles),
permission: &permission::UserPermission::AdminRoles,
kind: AdminPageKind::View,
});
bag.add(AdminPage {
@ -173,7 +173,7 @@ fn declare_admin_pages(bag: &mut PageBag) {
title: Lc::t("title-admin-permissions", &LOCALES_USER),
description: Some(Lc::t("description-admin-permissions", &LOCALES_USER)),
weight: 20,
permission: Some(&permission::UserPermission::AdminPermissions),
permission: &permission::UserPermission::AdminPermissions,
kind: AdminPageKind::View,
});
}