✨ (auth): Añade permisos tipados
Sustituye las claves de permiso `&str` sueltas por el trait `Permission` (clave, etiqueta y grupo), y añade `require_permission()` para cortar un handler con `ErrorPage::AccessDenied` antes de construir nada. Para que `require_permission()` pueda fallar sin un `Context` previo, `ErrorPage` pasa a envolver `Option<HttpRequest>`, `Context::new()` recibe ahora `HttpRequest` sin `Option`, y se añade `Context::admin()` para la plantilla de administración.
This commit is contained in:
parent
ea0dc021ce
commit
747e64ce34
10 changed files with 297 additions and 120 deletions
|
|
@ -23,7 +23,7 @@ use crate::base::action;
|
|||
use crate::base::component::layout;
|
||||
use crate::core::component::{AssetsOp, ChildOp, ComponentRender};
|
||||
use crate::core::component::{Context, ContextError, Contextual};
|
||||
use crate::core::theme::{CoreRegion, CoreTemplate, RegionName, RegionRef, TemplateRef, ThemeRef};
|
||||
use crate::core::theme::{CoreRegion, RegionName, RegionRef, TemplateRef, ThemeRef};
|
||||
use crate::html::{Assets, Favicon, JavaScript, StyleSheet};
|
||||
use crate::html::{Attr, Props, PropsOp};
|
||||
use crate::html::{DOCTYPE, Markup, html};
|
||||
|
|
@ -104,7 +104,7 @@ impl Page {
|
|||
/// usuario actual desde el momento en que se crea la página, sin llamadas adicionales.
|
||||
pub fn new(request: HttpRequest) -> Self {
|
||||
Page {
|
||||
context: Context::new(Some(request)),
|
||||
context: Context::new(request),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
@ -114,9 +114,11 @@ impl Page {
|
|||
/// Cada tema puede maquetarla de forma distinta capturando
|
||||
/// [`Template`](crate::base::component::layout::Template) en `handle_component()`, pero la
|
||||
/// plantilla en sí es la misma constante para cualquier tema.
|
||||
///
|
||||
/// [`CoreTemplate::Admin`]: crate::core::theme::CoreTemplate::Admin
|
||||
pub fn admin(request: HttpRequest) -> Self {
|
||||
Page {
|
||||
context: Context::new(Some(request)).with_template(&CoreTemplate::Admin),
|
||||
context: Context::admin(request),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
@ -154,22 +156,22 @@ impl Page {
|
|||
// **< Page GETTERS >***************************************************************************
|
||||
|
||||
/// Devuelve el título traducido para el idioma de la página, si existe.
|
||||
pub fn title(&mut self) -> Option<String> {
|
||||
pub fn title(&self) -> Option<String> {
|
||||
self.title.lookup(&self.context)
|
||||
}
|
||||
|
||||
/// Devuelve la descripción traducida para el idioma de la página, si existe.
|
||||
pub fn description(&mut self) -> Option<String> {
|
||||
pub fn description(&self) -> Option<String> {
|
||||
self.description.lookup(&self.context)
|
||||
}
|
||||
|
||||
/// Devuelve la lista de metadatos `<meta name=...>`.
|
||||
pub fn metadata(&self) -> &Vec<(&str, &str)> {
|
||||
pub fn metadata(&self) -> &Vec<(&'static str, &'static str)> {
|
||||
&self.metadata
|
||||
}
|
||||
|
||||
/// Devuelve la lista de propiedades `<meta property=...>`.
|
||||
pub fn properties(&self) -> &Vec<(&str, &str)> {
|
||||
pub fn properties(&self) -> &Vec<(&'static str, &'static str)> {
|
||||
&self.properties
|
||||
}
|
||||
|
||||
|
|
@ -281,14 +283,14 @@ impl Contextual for Page {
|
|||
}
|
||||
|
||||
#[builder_fn]
|
||||
fn with_theme(mut self, theme: ThemeRef) -> Self {
|
||||
self.context.alter_theme(theme);
|
||||
fn with_template(mut self, template: TemplateRef) -> Self {
|
||||
self.context.alter_template(template);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
fn with_template(mut self, template: TemplateRef) -> Self {
|
||||
self.context.alter_template(template);
|
||||
fn with_theme(mut self, theme: ThemeRef) -> Self {
|
||||
self.context.alter_theme(theme);
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -332,14 +334,14 @@ impl Contextual for Page {
|
|||
self.context.current_user()
|
||||
}
|
||||
|
||||
fn theme(&self) -> ThemeRef {
|
||||
self.context.theme()
|
||||
}
|
||||
|
||||
fn template(&self) -> TemplateRef {
|
||||
self.context.template()
|
||||
}
|
||||
|
||||
fn theme(&self) -> ThemeRef {
|
||||
self.context.theme()
|
||||
}
|
||||
|
||||
fn param<T: 'static>(&self, key: &'static str) -> Result<&T, ContextError> {
|
||||
self.context.param(key)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,22 +16,41 @@ use std::any::Any;
|
|||
/// Página de error asociada a un código de estado HTTP.
|
||||
///
|
||||
/// Este enumerado agrupa tipos esenciales de error que pueden devolverse como página HTML completa.
|
||||
/// Cada variante encapsula la solicitud original ([`HttpRequest`]) y se corresponde con un código
|
||||
/// de estado concreto.
|
||||
/// Cada variante encapsula la petición original si está disponible ([`HttpRequest`]), y se asocia a
|
||||
/// un código de estado concreto.
|
||||
///
|
||||
/// Para cada error se construye una [`Page`] usando el tema activo, lo que permite personalizar la
|
||||
/// plantilla y el contenido del mensaje mediante los métodos específicos del tema (como
|
||||
/// [`Theme::error_403()`](crate::core::theme::Theme::error_403),
|
||||
/// [`Theme::error_404()`](crate::core::theme::Theme::error_404) o
|
||||
/// [`Theme::error_fatal()`](crate::core::theme::Theme::error_fatal)).
|
||||
/// plantilla y el contenido del mensaje con los métodos específicos del tema, como
|
||||
/// [`Theme::error_403()`], [`Theme::error_404()`] o [`Theme::error_fatal()`].
|
||||
///
|
||||
/// Sin `request` (`None`), la página se renderiza igualmente, pero sin el idioma negociado ni el
|
||||
/// usuario actual, que dependen de la petición original.
|
||||
///
|
||||
/// [`Theme::error_403()`]: crate::core::theme::Theme::error_403
|
||||
/// [`Theme::error_404()`]: crate::core::theme::Theme::error_404
|
||||
/// [`Theme::error_fatal()`]: crate::core::theme::Theme::error_fatal
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ErrorPage {
|
||||
BadRequest(HttpRequest),
|
||||
AccessDenied(HttpRequest),
|
||||
NotFound(HttpRequest),
|
||||
InternalError(HttpRequest),
|
||||
ServiceUnavailable(HttpRequest),
|
||||
GatewayTimeout(HttpRequest),
|
||||
/// Petición incorrecta (400). El servidor no puede procesar la petición tal y como está
|
||||
/// formulada (datos malformados, parámetros inválidos, etc.).
|
||||
BadRequest(Option<HttpRequest>),
|
||||
/// Acceso denegado (403). El usuario actual no tiene permiso para acceder al recurso.
|
||||
///
|
||||
/// Se renderiza con [`Theme::error_403()`](crate::core::theme::Theme::error_403).
|
||||
AccessDenied(Option<HttpRequest>),
|
||||
/// Recurso no encontrado (404). La ruta solicitada no existe o no coincide con ningún handler.
|
||||
///
|
||||
/// Se renderiza con [`Theme::error_404()`](crate::core::theme::Theme::error_404).
|
||||
NotFound(Option<HttpRequest>),
|
||||
/// Error interno del servidor (500). Un fallo controlado (no un `panic!`) impide completar la
|
||||
/// petición.
|
||||
InternalError(Option<HttpRequest>),
|
||||
/// Servicio no disponible (503). El servidor no puede atender la petición temporalmente
|
||||
/// (mantenimiento, sobrecarga, etc.).
|
||||
ServiceUnavailable(Option<HttpRequest>),
|
||||
/// Tiempo de espera agotado (504). Una dependencia externa (proxy, servicio remoto) no ha
|
||||
/// respondido a tiempo.
|
||||
GatewayTimeout(Option<HttpRequest>),
|
||||
}
|
||||
|
||||
impl ErrorPage {
|
||||
|
|
@ -53,12 +72,12 @@ impl ErrorPage {
|
|||
let status = self.status_code();
|
||||
let mut page = match self {
|
||||
Self::AccessDenied(request) => {
|
||||
let mut page = Page::new(request);
|
||||
let mut page = Page::default().with_request(request);
|
||||
page.theme().error_403(&mut page);
|
||||
page
|
||||
}
|
||||
Self::NotFound(request) => {
|
||||
let mut page = Page::new(request);
|
||||
let mut page = Page::default().with_request(request);
|
||||
page.theme().error_404(&mut page);
|
||||
page
|
||||
}
|
||||
|
|
@ -66,7 +85,7 @@ impl ErrorPage {
|
|||
| Self::InternalError(request)
|
||||
| Self::ServiceUnavailable(request)
|
||||
| Self::GatewayTimeout(request) => {
|
||||
let mut page = Page::new(request);
|
||||
let mut page = Page::default().with_request(request);
|
||||
page.theme().error_fatal(
|
||||
&mut page,
|
||||
status,
|
||||
|
|
@ -84,6 +103,9 @@ impl ErrorPage {
|
|||
rendered.into_string(),
|
||||
)
|
||||
.into_response(),
|
||||
// Si renderizar la propia página de error falla, se descarta el `ErrorPage` resultante
|
||||
// en vez de intentar renderizarlo de nuevo, para no arriesgarse a una recursión si el
|
||||
// fallo persiste.
|
||||
Err(_) => status.into_response(),
|
||||
}
|
||||
}
|
||||
|
|
@ -104,7 +126,7 @@ impl IntoResponse for ErrorPage {
|
|||
//
|
||||
// Se registra como `.fallback()` del router principal desde [`Application`](crate::Application).
|
||||
pub(crate) async fn route_not_found(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||
Err(ErrorPage::NotFound(request))
|
||||
Err(ErrorPage::NotFound(Some(request)))
|
||||
}
|
||||
|
||||
// Intercepta respuestas con un [`ErrorPage`] pendiente y las convierte en páginas HTML.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue