aboutsummaryrefslogtreecommitdiff
path: root/dylints/non_authenticated_routes/src/lib.rs
diff options
context:
space:
mode:
authorDaniel García <[email protected]>2024-11-09 16:53:10 +0100
committerDaniel García <[email protected]>2024-11-09 17:31:22 +0100
commitf312e00dfaea78a163af45734c2c1b91d1d85bba (patch)
treea9acb55f40a2a1236a62634208ce4f87c8668bab /dylints/non_authenticated_routes/src/lib.rs
parent2f20ad86f9283419845ff1b77c504c3dfd043578 (diff)
downloadvaultwarden-test_dylint.tar.gz
vaultwarden-test_dylint.zip
Test dylinttest_dylint
Diffstat (limited to 'dylints/non_authenticated_routes/src/lib.rs')
-rw-r--r--dylints/non_authenticated_routes/src/lib.rs167
1 files changed, 167 insertions, 0 deletions
diff --git a/dylints/non_authenticated_routes/src/lib.rs b/dylints/non_authenticated_routes/src/lib.rs
new file mode 100644
index 00000000..78bac586
--- /dev/null
+++ b/dylints/non_authenticated_routes/src/lib.rs
@@ -0,0 +1,167 @@
+#![feature(rustc_private)]
+#![feature(let_chains)]
+
+extern crate rustc_arena;
+extern crate rustc_ast;
+extern crate rustc_ast_pretty;
+extern crate rustc_attr;
+extern crate rustc_data_structures;
+extern crate rustc_errors;
+extern crate rustc_hir;
+extern crate rustc_hir_pretty;
+extern crate rustc_index;
+extern crate rustc_infer;
+extern crate rustc_lexer;
+extern crate rustc_middle;
+extern crate rustc_mir_dataflow;
+extern crate rustc_parse;
+extern crate rustc_span;
+extern crate rustc_target;
+extern crate rustc_trait_selection;
+
+use clippy_utils::diagnostics::span_lint;
+use rustc_hir::{def_id::DefId, Item, ItemKind, QPath, TyKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_span::{symbol::Ident, Span, Symbol};
+
+dylint_linting::impl_late_lint! {
+ /// ### What it does
+ ///
+ /// ### Why is this bad?
+ ///
+ /// ### Known problems
+ /// Remove if none.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // example code where a warning is issued
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// // example code that does not raise a warning
+ /// ```
+ pub NON_AUTHENTICATED_ROUTES,
+ Warn,
+ "description goes here",
+ NonAuthenticatedRoutes::default()
+}
+
+#[derive(Default)]
+pub struct NonAuthenticatedRoutes {
+ last_function_item: Option<(Ident, Span, bool)>,
+}
+
+// Collect all the attribute macros that are applied to the given span
+fn attr_def_ids(mut span: rustc_span::Span) -> Vec<(DefId, Symbol, Option<DefId>)> {
+ use rustc_span::hygiene::{walk_chain, ExpnKind, MacroKind};
+ use rustc_span::{ExpnData, SyntaxContext};
+
+ let mut def_ids = Vec::new();
+ while span.ctxt() != SyntaxContext::root() {
+ if let ExpnData {
+ kind: ExpnKind::Macro(MacroKind::Attr, macro_symbol),
+ macro_def_id: Some(def_id),
+ parent_module,
+ ..
+ } = span.ctxt().outer_expn_data()
+ {
+ def_ids.push((def_id, macro_symbol, parent_module));
+ }
+ span = walk_chain(span, SyntaxContext::root());
+ }
+ def_ids
+}
+
+const ROCKET_MACRO_EXCEPTIONS: [(&str, &str); 1] = [("rocket::catch", "catch")];
+
+const VALID_AUTH_HEADERS: [&str; 6] = [
+ "auth::Headers",
+ "auth::OrgHeaders",
+ "auth::AdminHeaders",
+ "auth::ManagerHeaders",
+ "auth::ManagerHeadersLoose",
+ "auth::OwnerHeaders",
+];
+
+impl<'tcx> LateLintPass<'tcx> for NonAuthenticatedRoutes {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item) {
+ if let ItemKind::Fn(sig, ..) = item.kind {
+ let mut has_auth_headers = false;
+
+ for input in sig.decl.inputs {
+ let TyKind::Path(QPath::Resolved(_, path)) = input.kind else {
+ continue;
+ };
+
+ for seg in path.segments {
+ if let Some(def_id) = seg.res.opt_def_id() {
+ let def = cx.tcx.def_path_str(def_id);
+ if VALID_AUTH_HEADERS.contains(&def.as_str()) {
+ has_auth_headers = true;
+ }
+ }
+ }
+ }
+
+ self.last_function_item = Some((item.ident, sig.span, has_auth_headers));
+ return;
+ }
+
+ let ItemKind::Struct(_data, _generics) = item.kind else {
+ return;
+ };
+
+ let def_ids = attr_def_ids(item.span);
+
+ let mut is_rocket_route = false;
+
+ for (def_id, sym, parent) in &def_ids {
+ let def_id = cx.tcx.def_path_str(*def_id);
+ let sym = sym.as_str();
+ let parent = parent.map(|parent| cx.tcx.def_path_str(parent));
+
+ if ROCKET_MACRO_EXCEPTIONS.contains(&(&def_id, sym)) {
+ is_rocket_route = false;
+ break;
+ }
+
+ if def_id.starts_with("rocket::") || parent.as_deref() == Some("rocket_codegen") {
+ is_rocket_route = true;
+ break;
+ }
+ }
+
+ if !is_rocket_route {
+ return;
+ }
+
+ let Some((func_ident, func_span, has_auth_headers)) = self.last_function_item.take() else {
+ span_lint(cx, NON_AUTHENTICATED_ROUTES, item.span, "No function found before the expanded route");
+ return;
+ };
+
+ if func_ident != item.ident {
+ span_lint(
+ cx,
+ NON_AUTHENTICATED_ROUTES,
+ item.span,
+ "The function before the expanded route does not match the route",
+ );
+ return;
+ }
+
+ if !has_auth_headers {
+ span_lint(
+ cx,
+ NON_AUTHENTICATED_ROUTES,
+ func_span,
+ "This Rocket route does not have any authentication headers",
+ );
+ }
+ }
+}
+
+#[test]
+fn ui() {
+ dylint_testing::ui_test(env!("CARGO_PKG_NAME"), "ui");
+}