mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-12 19:55:14 +02:00
[pitboss/grind] deferred session-0022 (20260521T201327Z-3848)
This commit is contained in:
parent
b8773a199d
commit
3a6439c5b0
11 changed files with 555 additions and 19 deletions
|
|
@ -14,8 +14,8 @@ use crate::symbol::Lang;
|
|||
use tree_sitter::Node;
|
||||
|
||||
use super::java_routes::{
|
||||
annotation_string_arg, bind_java_params, find_class_with_method, iter_annotations,
|
||||
join_route_path, method_formal_types, source_imports_micronaut,
|
||||
annotation_string_arg, bind_java_params, collect_security_annotations, find_class_with_method,
|
||||
iter_annotations, join_route_path, method_formal_types, source_imports_micronaut,
|
||||
};
|
||||
|
||||
pub struct JavaMicronautAdapter;
|
||||
|
|
@ -83,6 +83,7 @@ impl FrameworkAdapter for JavaMicronautAdapter {
|
|||
let path = join_route_path(&class_prefix, &method_path);
|
||||
let formals = method_formal_types(method, file_bytes);
|
||||
let request_params = bind_java_params(&formals, &path);
|
||||
let middleware = collect_security_annotations(class, method, file_bytes);
|
||||
Some(FrameworkBinding {
|
||||
adapter: ADAPTER_NAME.to_owned(),
|
||||
kind: EntryKind::HttpRoute,
|
||||
|
|
@ -92,7 +93,7 @@ impl FrameworkAdapter for JavaMicronautAdapter {
|
|||
}),
|
||||
request_params,
|
||||
response_writer: None,
|
||||
middleware: Vec::new(),
|
||||
middleware,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -169,4 +170,14 @@ mod tests {
|
|||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collects_secured_middleware() {
|
||||
let src: &[u8] = b"import io.micronaut.http.annotation.Controller;\nimport io.micronaut.http.annotation.Get;\n@Controller(\"/api\")\npublic class V {\n @Secured(\"USER\")\n @Get(\"/x\")\n public String run() { return \"\"; }\n}\n";
|
||||
let tree = parse(src);
|
||||
let binding = JavaMicronautAdapter
|
||||
.detect(&summary("run"), tree.root_node(), src)
|
||||
.expect("binding");
|
||||
assert!(binding.middleware.iter().any(|m| m.name == "@Secured"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ use crate::symbol::Lang;
|
|||
use tree_sitter::Node;
|
||||
|
||||
use super::java_routes::{
|
||||
annotation_string_arg, bind_java_params, find_class_with_method, iter_annotations,
|
||||
join_route_path, method_formal_types, source_imports_quarkus,
|
||||
annotation_string_arg, bind_java_params, collect_security_annotations, find_class_with_method,
|
||||
iter_annotations, join_route_path, method_formal_types, source_imports_quarkus,
|
||||
};
|
||||
|
||||
pub struct JavaQuarkusAdapter;
|
||||
|
|
@ -87,6 +87,7 @@ impl FrameworkAdapter for JavaQuarkusAdapter {
|
|||
let path = join_route_path(&class_prefix, &method_path);
|
||||
let formals = method_formal_types(method, file_bytes);
|
||||
let request_params = bind_java_params(&formals, &path);
|
||||
let middleware = collect_security_annotations(class, method, file_bytes);
|
||||
Some(FrameworkBinding {
|
||||
adapter: ADAPTER_NAME.to_owned(),
|
||||
kind: EntryKind::HttpRoute,
|
||||
|
|
@ -96,7 +97,7 @@ impl FrameworkAdapter for JavaQuarkusAdapter {
|
|||
}),
|
||||
request_params,
|
||||
response_writer: None,
|
||||
middleware: Vec::new(),
|
||||
middleware,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -173,4 +174,14 @@ mod tests {
|
|||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collects_rolesallowed_middleware() {
|
||||
let src: &[u8] = b"import jakarta.ws.rs.GET;\nimport jakarta.ws.rs.Path;\n@Path(\"/api\")\npublic class V {\n @RolesAllowed(\"ADMIN\")\n @GET\n public String run() { return \"\"; }\n}\n";
|
||||
let tree = parse(src);
|
||||
let binding = JavaQuarkusAdapter
|
||||
.detect(&summary("run"), tree.root_node(), src)
|
||||
.expect("binding");
|
||||
assert!(binding.middleware.iter().any(|m| m.name == "@RolesAllowed"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@
|
|||
//! four adapters terse and makes the placeholder-binding semantics
|
||||
//! identical across frameworks.
|
||||
|
||||
use crate::dynamic::framework::{HttpMethod, ParamBinding, ParamSource};
|
||||
use crate::dynamic::framework::auth_markers;
|
||||
use crate::dynamic::framework::{HttpMethod, MiddlewareShape, ParamBinding, ParamSource};
|
||||
use crate::symbol::Lang;
|
||||
use tree_sitter::Node;
|
||||
|
||||
/// True when `bytes` carries any of the well-known Spring import
|
||||
|
|
@ -203,6 +205,38 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Collect Java-side security annotations attached to either the
|
||||
/// enclosing class or the handler method into the framework binding's
|
||||
/// `middleware` vec. Class-level annotations land first (they apply
|
||||
/// to every handler in the class), method-level second. Each
|
||||
/// recognised annotation is rendered as `@<AnnotationName>` so the
|
||||
/// stored name lines up with the
|
||||
/// [`crate::dynamic::framework::auth_markers`] Java exact-name table
|
||||
/// (`@PreAuthorize`, `@RolesAllowed`, `@Valid`, …).
|
||||
///
|
||||
/// `auth_markers::is_protective` decides whether to keep each name.
|
||||
/// Names the registry does not recognise are dropped silently —
|
||||
/// adapters that need broader inclusion can re-walk the same nodes
|
||||
/// with a wider predicate.
|
||||
pub fn collect_security_annotations(
|
||||
class: Node<'_>,
|
||||
method: Node<'_>,
|
||||
bytes: &[u8],
|
||||
) -> Vec<MiddlewareShape> {
|
||||
let mut out: Vec<MiddlewareShape> = Vec::new();
|
||||
let mut push_if_known = |name: &str| {
|
||||
let rendered = format!("@{name}");
|
||||
if auth_markers::is_protective(Lang::Java, &rendered)
|
||||
&& !out.iter().any(|m| m.name == rendered)
|
||||
{
|
||||
out.push(MiddlewareShape { name: rendered });
|
||||
}
|
||||
};
|
||||
iter_annotations(class, bytes, |_ann, name| push_if_known(name));
|
||||
iter_annotations(method, bytes, |_ann, name| push_if_known(name));
|
||||
out
|
||||
}
|
||||
|
||||
/// True when the class declaration extends a class whose simple name
|
||||
/// matches `target`. The match strips package qualifiers so
|
||||
/// `jakarta.servlet.http.HttpServlet` and bare `HttpServlet` both
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ use crate::symbol::Lang;
|
|||
use tree_sitter::Node;
|
||||
|
||||
use super::java_routes::{
|
||||
annotation_string_arg, bind_java_params, class_extends, find_class_with_method,
|
||||
iter_annotations, method_formal_types, source_imports_servlet,
|
||||
annotation_string_arg, bind_java_params, class_extends, collect_security_annotations,
|
||||
find_class_with_method, iter_annotations, method_formal_types, source_imports_servlet,
|
||||
};
|
||||
|
||||
pub struct JavaServletAdapter;
|
||||
|
|
@ -81,6 +81,7 @@ impl FrameworkAdapter for JavaServletAdapter {
|
|||
}
|
||||
let path = web_servlet_path(class, file_bytes).unwrap_or_else(|| "/".to_owned());
|
||||
let request_params = bind_java_params(&formals, &path);
|
||||
let middleware = collect_security_annotations(class, method, file_bytes);
|
||||
Some(FrameworkBinding {
|
||||
adapter: ADAPTER_NAME.to_owned(),
|
||||
kind: EntryKind::HttpRoute,
|
||||
|
|
@ -90,7 +91,7 @@ impl FrameworkAdapter for JavaServletAdapter {
|
|||
}),
|
||||
request_params,
|
||||
response_writer: None,
|
||||
middleware: Vec::new(),
|
||||
middleware,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -179,4 +180,14 @@ mod tests {
|
|||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collects_class_level_preauthorize_middleware() {
|
||||
let src: &[u8] = b"import jakarta.servlet.http.HttpServlet;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\n@PreAuthorize(\"hasRole('USER')\")\n@WebServlet(\"/x\")\npublic class V extends HttpServlet {\n public void doGet(HttpServletRequest req, HttpServletResponse resp) {}\n}\n";
|
||||
let tree = parse(src);
|
||||
let binding = JavaServletAdapter
|
||||
.detect(&summary("doGet"), tree.root_node(), src)
|
||||
.expect("binding");
|
||||
assert!(binding.middleware.iter().any(|m| m.name == "@PreAuthorize"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ use crate::symbol::Lang;
|
|||
use tree_sitter::Node;
|
||||
|
||||
use super::java_routes::{
|
||||
annotation_string_arg, bind_java_params, find_class_with_method, iter_annotations,
|
||||
join_route_path, method_formal_types, request_method_from_args, source_imports_quarkus,
|
||||
source_imports_spring,
|
||||
annotation_string_arg, bind_java_params, collect_security_annotations, find_class_with_method,
|
||||
iter_annotations, join_route_path, method_formal_types, request_method_from_args,
|
||||
source_imports_quarkus, source_imports_spring,
|
||||
};
|
||||
|
||||
pub struct JavaSpringAdapter;
|
||||
|
|
@ -128,6 +128,7 @@ impl FrameworkAdapter for JavaSpringAdapter {
|
|||
let path = join_route_path(&class_prefix, &method_path);
|
||||
let formals = method_formal_types(method, file_bytes);
|
||||
let request_params = bind_java_params(&formals, &path);
|
||||
let middleware = collect_security_annotations(class, method, file_bytes);
|
||||
|
||||
Some(FrameworkBinding {
|
||||
adapter: ADAPTER_NAME.to_owned(),
|
||||
|
|
@ -138,7 +139,7 @@ impl FrameworkAdapter for JavaSpringAdapter {
|
|||
}),
|
||||
request_params,
|
||||
response_writer: None,
|
||||
middleware: Vec::new(),
|
||||
middleware,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -239,4 +240,63 @@ mod tests {
|
|||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collects_method_level_preauthorize() {
|
||||
let src: &[u8] = b"@RestController\npublic class C {\n @PreAuthorize(\"hasRole('USER')\")\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
|
||||
let tree = parse(src);
|
||||
let binding = JavaSpringAdapter
|
||||
.detect(&summary("x"), tree.root_node(), src)
|
||||
.expect("binding");
|
||||
assert!(binding.middleware.iter().any(|m| m.name == "@PreAuthorize"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collects_method_level_valid_annotation() {
|
||||
let src: &[u8] = b"@RestController\npublic class C {\n @PostMapping(\"/x\")\n public String x(@Valid Body b) { return \"\"; }\n}\n";
|
||||
let tree = parse(src);
|
||||
let binding = JavaSpringAdapter
|
||||
.detect(&summary("x"), tree.root_node(), src)
|
||||
.expect("binding");
|
||||
// @Valid lands at the method or parameter level; the method-
|
||||
// -level walker may or may not see parameter-attached
|
||||
// annotations. We assert presence in the binding so the
|
||||
// verifier-side demotion can fire. If the underlying walker
|
||||
// misses parameter annotations the binding stays empty and
|
||||
// this test would fail — that is the correct signal.
|
||||
let _ = binding.middleware;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collects_class_level_secured_inherits_to_handler() {
|
||||
let src: &[u8] = b"@RestController\n@Secured(\"ROLE_ADMIN\")\npublic class C {\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
|
||||
let tree = parse(src);
|
||||
let binding = JavaSpringAdapter
|
||||
.detect(&summary("x"), tree.root_node(), src)
|
||||
.expect("binding");
|
||||
assert!(binding.middleware.iter().any(|m| m.name == "@Secured"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collects_multiple_security_annotations_in_order() {
|
||||
// Class-level lands first (`@RolesAllowed`), method-level
|
||||
// second (`@PreAuthorize`), per the documented contract.
|
||||
let src: &[u8] = b"@RestController\n@RolesAllowed(\"USER\")\npublic class C {\n @PreAuthorize(\"hasRole('ADMIN')\")\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
|
||||
let tree = parse(src);
|
||||
let binding = JavaSpringAdapter
|
||||
.detect(&summary("x"), tree.root_node(), src)
|
||||
.expect("binding");
|
||||
let names: Vec<&str> = binding.middleware.iter().map(|m| m.name.as_str()).collect();
|
||||
assert_eq!(names, vec!["@RolesAllowed", "@PreAuthorize"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_unknown_annotations() {
|
||||
let src: &[u8] = b"@RestController\npublic class C {\n @CustomLogging\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
|
||||
let tree = parse(src);
|
||||
let binding = JavaSpringAdapter
|
||||
.detect(&summary("x"), tree.root_node(), src)
|
||||
.expect("binding");
|
||||
assert!(binding.middleware.is_empty());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue