diff options
Diffstat (limited to 'dylints/non_authenticated_routes/src/lib.rs')
-rw-r--r-- | dylints/non_authenticated_routes/src/lib.rs | 167 |
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"); +} |