🔒️ (admin): Deniega /admin sin páginas accesibles
`render_sections()` devuelve `Option<Container>`, con `None` si no queda ninguna sección visible con páginas accesibles. `dashboard()` y `section_page()` responden con `AccessDenied` en ese caso, sin crear un permiso nuevo.
This commit is contained in:
parent
3f3634fdab
commit
f265f217bd
1 changed files with 55 additions and 47 deletions
|
|
@ -4,27 +4,25 @@ use pagetop::prelude::*;
|
|||
|
||||
use crate::LOCALES_ADMIN;
|
||||
use crate::component::{AdminFrame, ConfigForm};
|
||||
use crate::registry::{self, AdminPageKind, AdminPermission, AdminSection};
|
||||
use crate::registry::{self, AdminPageKind, AdminSection};
|
||||
use crate::settings;
|
||||
|
||||
// **< Dashboard >**********************************************************************************
|
||||
|
||||
/// GET /admin - Dashboard de administración: lista todas las secciones disponibles.
|
||||
///
|
||||
/// Devuelve [`ErrorPage::AccessDenied`] si el usuario actual no tiene acceso a ninguna página.
|
||||
pub async fn dashboard(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||
let reg = registry::global();
|
||||
let cx = Context::new(request.clone());
|
||||
let sections = reg.ordered_sections();
|
||||
let sections = registry::global().ordered_sections();
|
||||
|
||||
let title = Lc::t("dashboard-title", &LOCALES_ADMIN);
|
||||
let content = render_sections(&cx, §ions);
|
||||
let content = render_sections(&cx, §ions)
|
||||
.ok_or_else(|| ErrorPage::AccessDenied(Some(request.clone())))?;
|
||||
|
||||
Page::admin(request)
|
||||
.with_title(title.clone())
|
||||
.with_child(
|
||||
AdminFrame::new()
|
||||
.with_title(title)
|
||||
.with_child(Html::with(move |_| content.clone())),
|
||||
)
|
||||
.with_child(AdminFrame::new().with_title(title).with_child(content))
|
||||
.render()
|
||||
.await
|
||||
}
|
||||
|
|
@ -37,6 +35,9 @@ pub async fn dashboard(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
|||
/// con ninguna [`AdminPage`](crate::registry::AdminPage) registrada explícitamente por una
|
||||
/// extensión; si una extensión reclama esa ruta con su propia página, esa página gana y este
|
||||
/// handler nunca llega a montarse para ella.
|
||||
///
|
||||
/// Devuelve [`ErrorPage::AccessDenied`] si el usuario actual no tiene acceso a ninguna de sus
|
||||
/// páginas.
|
||||
pub async fn section_page(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||
let path = request.path().to_owned();
|
||||
let reg = registry::global();
|
||||
|
|
@ -46,22 +47,14 @@ pub async fn section_page(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
|||
.find(|s| s.path == path)
|
||||
.ok_or_else(|| ErrorPage::NotFound(Some(request.clone())))?;
|
||||
|
||||
require_permission(
|
||||
&request,
|
||||
section.permission.unwrap_or(&AdminPermission::Access),
|
||||
)?;
|
||||
|
||||
let cx = Context::new(request.clone());
|
||||
let title = section.title.clone();
|
||||
let content = render_sections(&cx, &[section]);
|
||||
let content = render_sections(&cx, &[section])
|
||||
.ok_or_else(|| ErrorPage::AccessDenied(Some(request.clone())))?;
|
||||
|
||||
Page::admin(request)
|
||||
.with_title(title.clone())
|
||||
.with_child(
|
||||
AdminFrame::new()
|
||||
.with_title(title)
|
||||
.with_child(Html::with(move |_| content.clone())),
|
||||
)
|
||||
.with_child(AdminFrame::new().with_title(title).with_child(content))
|
||||
.render()
|
||||
.await
|
||||
}
|
||||
|
|
@ -69,41 +62,56 @@ pub async fn section_page(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
|||
// **< Render compartido >**************************************************************************
|
||||
|
||||
// Rejilla de secciones con sus páginas (título + descripción). La usan tanto `dashboard()` (todas
|
||||
// las secciones) como `section_page()` (una sola), filtrando siempre por permiso del usuario actual.
|
||||
fn render_sections(cx: &Context, sections: &[&AdminSection]) -> Markup {
|
||||
// las secciones) como `section_page()` (una sola), filtrando siempre por permiso del usuario
|
||||
// actual. Devuelve `None` si no queda ninguna sección visible con páginas accesibles.
|
||||
fn render_sections(cx: &Context, candidates: &[&'static AdminSection]) -> Option<Container> {
|
||||
let reg = registry::global();
|
||||
html! {
|
||||
div.admin-dashboard-sections {
|
||||
@for section in sections {
|
||||
@if section.is_visible(cx) {
|
||||
@let pages: Vec<_> = reg
|
||||
.pages_for_section(§ion.key)
|
||||
.into_iter()
|
||||
.filter(|p| p.is_accessible(cx))
|
||||
.collect();
|
||||
@if !pages.is_empty() {
|
||||
div.admin-dashboard-section {
|
||||
h2.admin-dashboard-section-title {
|
||||
a href=(cx.route(section.path.as_str())) { (section.title.using(cx)) }
|
||||
}
|
||||
ul.admin-dashboard-section-links {
|
||||
@for page in pages {
|
||||
li.admin-dashboard-link {
|
||||
a href=(cx.route(page.path.as_str())) { (page.title.using(cx)) }
|
||||
@if let Some(description) = &page.description {
|
||||
span.admin-dashboard-link-desc {
|
||||
" - " (description.using(cx))
|
||||
}
|
||||
let sections_with_pages: Vec<_> = candidates
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|section| section.is_visible(cx))
|
||||
.map(|section| {
|
||||
let pages: Vec<_> = reg
|
||||
.pages_for_section(§ion.key)
|
||||
.into_iter()
|
||||
.filter(|p| p.is_accessible(cx))
|
||||
.collect();
|
||||
(section, pages)
|
||||
})
|
||||
.filter(|(_, pages)| !pages.is_empty())
|
||||
.collect();
|
||||
if sections_with_pages.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut sections = Container::new().with_prop(PropsOp::add_classes("admin-dashboard-sections"));
|
||||
for (section, pages) in sections_with_pages {
|
||||
sections = sections.with_child(
|
||||
Container::new()
|
||||
.with_prop(PropsOp::add_classes("admin-dashboard-section"))
|
||||
.with_child(Html::with(move |cx| {
|
||||
html! {
|
||||
h2 class="admin-dashboard-section-title" {
|
||||
a href=(cx.route(section.path.as_str())) { (section.title.using(cx)) }
|
||||
}
|
||||
ul class="admin-dashboard-section-links" {
|
||||
@for page in &pages {
|
||||
li class="admin-dashboard-link" {
|
||||
a href=(cx.route(page.path.as_str())) {
|
||||
(page.title.using(cx))
|
||||
}
|
||||
@if let Some(description) = &page.description {
|
||||
span class="admin-dashboard-link-desc" {
|
||||
" - " (description.using(cx))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})),
|
||||
);
|
||||
}
|
||||
Some(sections)
|
||||
}
|
||||
|
||||
// **< Config form - GET >**************************************************************************
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue