1use serde_json;
4use std::sync::Arc;
5use webx_core::auth::DeclaredRules;
6use webx_core::error::Result;
7use webx_core::http::IHttpContext;
8use webx_core::routing::IEndpoint;
9
10use crate::authz::AuthorizerSet;
11use crate::problem_response::{write_forbidden, write_problem};
12use std::collections::HashMap;
13
14#[allow(clippy::type_complexity)]
16pub struct RequestEndpoint {
17 handler: Box<
18 dyn Fn(
19 &mut dyn IHttpContext,
20 )
21 -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + '_>>
22 + Send
23 + Sync,
24 >,
25}
26
27#[async_trait::async_trait]
28impl IEndpoint for RequestEndpoint {
29 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
30 (self.handler)(ctx).await
31 }
32}
33
34impl RequestEndpoint {
35 pub fn new<F>(f: F) -> Self
36 where
37 F: for<'a> Fn(
38 &'a mut dyn IHttpContext,
39 ) -> std::pin::Pin<
40 Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>,
41 > + Send
42 + Sync
43 + 'static,
44 {
45 Self {
46 handler: Box::new(f),
47 }
48 }
49}
50
51pub struct StubEndpoint {
58 pub method: &'static str,
59 pub path: &'static str,
60 pub handler_type: &'static str,
61 pub dispatch_fn: Option<webx_core::route::scan::RouteDispatchFn>,
62 pub authorization: &'static [webx_core::route::scan::AuthorizeRule],
65 pub authorizers: Option<Arc<AuthorizerSet>>,
69}
70
71#[async_trait::async_trait]
72impl IEndpoint for StubEndpoint {
73 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
74 if let Some(dispatch) = self.dispatch_fn {
75 if !self.authorization.is_empty() {
77 let denied = match ctx.claims() {
78 None => {
79 return Err(webx_core::error::Error::Unauthorized(
85 "authentication required".to_string(),
86 ));
87 }
88 Some(claims) => self
89 .authorization
90 .iter()
91 .find(|rule| !rule.matches(claims.roles(), claims.permissions()))
92 .map(|rule| (rule.roles, rule.permissions)),
93 };
94
95 if let Some((roles, permissions)) = denied {
96 let mut ext = HashMap::new();
97 ext.insert("required_roles".into(), serde_json::json!(roles));
98 ext.insert(
99 "required_permissions".into(),
100 serde_json::json!(permissions),
101 );
102 write_forbidden(ctx, "Forbidden: insufficient permissions", ext).await;
103 return Ok(());
104 }
105
106 if let Some(ref authorizers) = self.authorizers {
108 if !authorizers.is_empty() {
109 if let Some(claims) = ctx.claims() {
110 let resource_key = ctx.request().route_pattern().unwrap_or(self.path);
111 let method = ctx.request().method();
112 let declared = DeclaredRules::new(self.authorization);
117 authorizers
118 .authorize_with_rules(claims, resource_key, method, &declared)
119 .await?;
120 }
121 }
122 }
123 }
124
125 let response = dispatch(&mut *ctx).await?;
126 response.execute(&mut *ctx).await?;
127 return Ok(());
128 }
129
130 write_problem(
132 ctx,
133 501,
134 &format!(
135 "Route handler not implemented: {} {} (request type: {})",
136 self.method, self.path, self.handler_type
137 ),
138 )
139 .await;
140 Ok(())
141 }
142}
143
144#[allow(clippy::type_complexity)]
146pub struct ControllerEndpoint {
147 handler: Box<
148 dyn Fn(
149 &mut dyn IHttpContext,
150 )
151 -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + '_>>
152 + Send
153 + Sync,
154 >,
155}
156
157#[async_trait::async_trait]
158impl IEndpoint for ControllerEndpoint {
159 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
160 (self.handler)(ctx).await
161 }
162}
163
164impl ControllerEndpoint {
165 pub fn new<F>(f: F) -> Self
166 where
167 F: for<'a> Fn(
168 &'a mut dyn IHttpContext,
169 ) -> std::pin::Pin<
170 Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>,
171 > + Send
172 + Sync
173 + 'static,
174 {
175 Self {
176 handler: Box::new(f),
177 }
178 }
179}
180
181pub struct StaticJsonEndpoint {
187 pub body: Vec<u8>,
188 pub content_type: &'static str,
189}
190
191impl StaticJsonEndpoint {
192 pub fn new(body: Vec<u8>) -> Self {
194 Self {
195 body,
196 content_type: "application/json",
197 }
198 }
199}
200
201#[async_trait::async_trait]
202impl IEndpoint for StaticJsonEndpoint {
203 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
204 ctx.response_mut().set_status(200);
205 ctx.response_mut()
206 .set_header("content-type", self.content_type);
207 ctx.response_mut().write_bytes(self.body.clone()).await
208 }
209}
210
211pub struct StaticHtmlEndpoint {
215 pub body: &'static str,
216}
217
218#[async_trait::async_trait]
219impl IEndpoint for StaticHtmlEndpoint {
220 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
221 ctx.response_mut().set_status(200);
222 ctx.response_mut()
223 .set_header("content-type", "text/html; charset=utf-8");
224 ctx.response_mut()
225 .write_bytes(self.body.as_bytes().to_vec())
226 .await
227 }
228}