1use serde_json;
4use std::sync::Arc;
5use webx_core::auth::IAuthorizationPolicy;
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 auth_required_role: &'static str,
63 pub auth_required_permission: &'static str,
64 pub resource_policy: Option<Arc<crate::authz::ResourceAuthorization>>,
67 pub authorizers: Option<Arc<AuthorizerSet>>,
71}
72
73#[async_trait::async_trait]
74impl IEndpoint for StubEndpoint {
75 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
76 if let Some(dispatch) = self.dispatch_fn {
77 let auth_required =
79 !self.auth_required_role.is_empty() || !self.auth_required_permission.is_empty();
80 if auth_required {
81 match ctx.claims() {
82 None => {
83 write_problem(ctx, 401, "Authentication required").await;
84 return Ok(());
85 }
86 Some(claims) => {
87 if !self.auth_required_role.is_empty()
88 && self.auth_required_role != "authenticated"
89 && !claims
90 .roles()
91 .iter()
92 .any(|r| r.as_str() == self.auth_required_role)
93 {
94 let mut ext = HashMap::new();
95 ext.insert(
96 "required_role".into(),
97 serde_json::Value::String(self.auth_required_role.to_string()),
98 );
99 write_forbidden(ctx, "Forbidden: insufficient permissions", ext).await;
100 return Ok(());
101 }
102
103 if !self.auth_required_permission.is_empty()
104 && !claims
105 .permissions()
106 .iter()
107 .any(|p| p.as_str() == self.auth_required_permission)
108 {
109 let mut ext = HashMap::new();
110 ext.insert(
111 "required_permission".into(),
112 serde_json::Value::String(
113 self.auth_required_permission.to_string(),
114 ),
115 );
116 write_forbidden(ctx, "Forbidden: insufficient permissions", ext).await;
117 return Ok(());
118 }
119 }
120 }
121 }
122
123 if auth_required {
124 if let Some(ref policy) = self.resource_policy {
125 if let Some(claims) = ctx.claims() {
126 let resource_key = ctx.request().route_pattern().unwrap_or(self.path);
127 if policy.covers_route(resource_key) {
128 let method = ctx.request().method();
129 if let Err(err) = policy.authorize(claims, resource_key, method).await {
130 let mut ext = HashMap::new();
131 ext.insert(
132 "resource".into(),
133 serde_json::Value::String(resource_key.to_string()),
134 );
135 write_forbidden(ctx, &err.to_string(), ext).await;
136 return Ok(());
137 }
138 }
139 }
140 }
141 }
142
143 if auth_required {
145 if let Some(ref authorizers) = self.authorizers {
146 if !authorizers.is_empty() {
147 if let Some(claims) = ctx.claims() {
148 let resource_key = ctx.request().route_pattern().unwrap_or(self.path);
149 let method = ctx.request().method();
150 authorizers.authorize(claims, resource_key, method).await?;
151 }
152 }
153 }
154 }
155
156 let response = dispatch(&mut *ctx).await?;
157
158 ctx.response_mut().set_status(response.status);
159 if !response.content_type.is_empty() {
160 ctx.response_mut()
161 .set_header("content-type", &response.content_type);
162 }
163 for (key, value) in &response.headers {
164 ctx.response_mut().set_header(key, value);
165 }
166 ctx.response_mut().write_body(response.body).await?;
167 return Ok(());
168 }
169
170 write_problem(
172 ctx,
173 501,
174 &format!(
175 "Route handler not implemented: {} {} (request type: {})",
176 self.method, self.path, self.handler_type
177 ),
178 )
179 .await;
180 Ok(())
181 }
182}
183
184#[allow(clippy::type_complexity)]
186pub struct ControllerEndpoint {
187 handler: Box<
188 dyn Fn(
189 &mut dyn IHttpContext,
190 )
191 -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + '_>>
192 + Send
193 + Sync,
194 >,
195}
196
197#[async_trait::async_trait]
198impl IEndpoint for ControllerEndpoint {
199 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
200 (self.handler)(ctx).await
201 }
202}
203
204impl ControllerEndpoint {
205 pub fn new<F>(f: F) -> Self
206 where
207 F: for<'a> Fn(
208 &'a mut dyn IHttpContext,
209 ) -> std::pin::Pin<
210 Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>,
211 > + Send
212 + Sync
213 + 'static,
214 {
215 Self {
216 handler: Box::new(f),
217 }
218 }
219}
220
221pub struct StaticJsonEndpoint {
227 pub body: Vec<u8>,
228 pub content_type: &'static str,
229}
230
231impl StaticJsonEndpoint {
232 pub fn new(body: Vec<u8>) -> Self {
234 Self {
235 body,
236 content_type: "application/json",
237 }
238 }
239}
240
241#[async_trait::async_trait]
242impl IEndpoint for StaticJsonEndpoint {
243 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
244 ctx.response_mut().set_status(200);
245 ctx.response_mut()
246 .set_header("content-type", self.content_type);
247 ctx.response_mut().write_bytes(self.body.clone()).await
248 }
249}
250
251pub struct StaticHtmlEndpoint {
255 pub body: &'static str,
256}
257
258#[async_trait::async_trait]
259impl IEndpoint for StaticHtmlEndpoint {
260 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
261 ctx.response_mut().set_status(200);
262 ctx.response_mut()
263 .set_header("content-type", "text/html; charset=utf-8");
264 ctx.response_mut()
265 .write_bytes(self.body.as_bytes().to_vec())
266 .await
267 }
268}