use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Framework {
Express,
Koa,
Fastify,
Gin,
Echo,
Flask,
Django,
Spring,
Rails,
Sinatra,
Axum,
ActixWeb,
Rocket,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HttpMethod {
Get,
Post,
Put,
Delete,
Patch,
All,
Use,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnalysisUnitKind {
RouteHandler,
Function,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum AuthCheckKind {
LoginGuard,
AdminGuard,
Ownership,
Membership,
TokenExpiry,
TokenRecipient,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OperationKind {
Read,
Mutation,
TokenLookup,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SinkClass {
DbMutation,
DbCrossTenantRead,
RealtimePublish,
OutboundNetwork,
CacheCrossTenant,
InMemoryLocal,
}
impl SinkClass {
pub fn is_auth_relevant(&self) -> bool {
!matches!(self, SinkClass::InMemoryLocal)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValueSourceKind {
RequestParam,
RequestBody,
RequestQuery,
Session,
Identifier,
MemberField,
TokenField,
ArrayIndex,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValueRef {
pub source_kind: ValueSourceKind,
pub name: String,
pub base: Option<String>,
pub field: Option<String>,
pub index: Option<String>,
pub span: (usize, usize),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CallSite {
pub name: String,
pub args: Vec<String>,
pub span: (usize, usize),
pub args_value_refs: Vec<Vec<ValueRef>>,
}
#[derive(Debug, Clone)]
pub struct AuthCheck {
pub kind: AuthCheckKind,
pub callee: String,
pub subjects: Vec<ValueRef>,
pub span: (usize, usize),
pub line: usize,
pub args: Vec<String>,
pub condition_text: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SensitiveOperation {
pub kind: OperationKind,
pub sink_class: Option<SinkClass>,
pub callee: String,
pub subjects: Vec<ValueRef>,
pub span: (usize, usize),
pub line: usize,
pub text: String,
}
#[derive(Debug, Clone)]
pub struct AnalysisUnit {
pub kind: AnalysisUnitKind,
pub name: Option<String>,
pub span: (usize, usize),
pub params: Vec<String>,
pub context_inputs: Vec<ValueRef>,
pub call_sites: Vec<CallSite>,
pub auth_checks: Vec<AuthCheck>,
pub operations: Vec<SensitiveOperation>,
pub value_refs: Vec<ValueRef>,
pub condition_texts: Vec<String>,
pub line: usize,
pub row_field_vars: HashMap<String, String>,
pub var_alias_chain: HashMap<String, String>,
pub row_population_data: HashMap<String, (usize, Vec<ValueRef>)>,
pub self_actor_vars: HashSet<String>,
pub self_actor_id_vars: HashSet<String>,
pub authorized_sql_vars: HashSet<String>,
pub const_bound_vars: HashSet<String>,
pub typed_bounded_vars: HashSet<String>,
pub typed_bounded_dto_fields: HashMap<String, Vec<String>>,
pub self_scoped_session_bases: HashSet<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AuthCheckSummary {
#[serde(
serialize_with = "serialize_param_auth_kinds",
deserialize_with = "deserialize_param_auth_kinds"
)]
pub param_auth_kinds: HashMap<usize, AuthCheckKind>,
}
fn serialize_param_auth_kinds<S>(
map: &HashMap<usize, AuthCheckKind>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeSeq;
let mut entries: Vec<(usize, AuthCheckKind)> =
map.iter().map(|(idx, kind)| (*idx, *kind)).collect();
entries.sort_by_key(|(idx, _)| *idx);
let mut seq = serializer.serialize_seq(Some(entries.len()))?;
for entry in entries {
seq.serialize_element(&entry)?;
}
seq.end()
}
fn deserialize_param_auth_kinds<'de, D>(
deserializer: D,
) -> Result<HashMap<usize, AuthCheckKind>, D::Error>
where
D: serde::Deserializer<'de>,
{
let entries: Vec<(usize, AuthCheckKind)> = Vec::deserialize(deserializer)?;
Ok(entries.into_iter().collect())
}
#[derive(Debug, Clone)]
pub struct RouteRegistration {
pub framework: Framework,
pub method: HttpMethod,
pub path: String,
pub middleware: Vec<String>,
pub handler_span: (usize, usize),
pub handler_params: Vec<String>,
pub file: PathBuf,
pub line: usize,
pub unit_idx: usize,
pub middleware_calls: Vec<CallSite>,
}
#[derive(Debug, Clone, Default)]
pub struct AuthorizationModel {
pub routes: Vec<RouteRegistration>,
pub units: Vec<AnalysisUnit>,
}
impl AuthorizationModel {
pub fn extend(&mut self, other: AuthorizationModel) {
let unit_offset = self.units.len();
self.units.extend(other.units);
self.routes
.extend(other.routes.into_iter().map(|mut route| {
route.unit_idx += unit_offset;
route
}));
}
}