use http::Method;
use parlov_core::{
Applicability, NormativeStrength, OracleClass, ResponseSurface, SignalSurface, Technique,
Vector,
};
use crate::strategy::Strategy;
use crate::types::{ProbeSpec, RiskLevel, StrategyMetadata};
use crate::util::build_pair;
use crate::ScanContext;
static METADATA: StrategyMetadata = StrategyMetadata {
strategy_id: "dependency-delete-elicit",
strategy_name: "Dependency Delete Elicitation",
risk: RiskLevel::OperationDestructive,
};
fn dependency_delete_applicable(
baseline: &ResponseSurface,
probe: &ResponseSurface,
) -> Applicability {
let is_strong = |s: u16| matches!(s, 409 | 404 | 422);
if is_strong(baseline.status.as_u16()) || is_strong(probe.status.as_u16()) {
return Applicability::Strong;
}
Applicability::Weak
}
static TECHNIQUE: Technique = Technique {
id: "dependency-delete",
name: "Dependency-blocked DELETE probe",
oracle_class: OracleClass::Existence,
vector: Vector::StatusCodeDiff,
strength: NormativeStrength::May,
normalization_weight: Some(0.02),
inverted_signal_weight: None,
method_relevant: false,
parser_relevant: true,
applicability: dependency_delete_applicable,
contradiction_surface: SignalSurface::Status,
};
pub struct DependencyDeleteElicitation;
impl Strategy for DependencyDeleteElicitation {
fn metadata(&self) -> &'static StrategyMetadata {
&METADATA
}
fn technique_def(&self) -> &'static Technique {
&TECHNIQUE
}
fn methods(&self) -> &[Method] {
&[Method::DELETE]
}
fn is_applicable(&self, _ctx: &ScanContext) -> bool {
true
}
fn generate(&self, ctx: &ScanContext) -> Vec<ProbeSpec> {
let pair = build_pair(
ctx,
Method::DELETE,
ctx.headers.clone(),
ctx.headers.clone(),
None,
METADATA.clone(),
TECHNIQUE,
);
vec![ProbeSpec::Pair(pair)]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::ctx_operation_destructive;
#[test]
fn risk_is_operation_destructive() {
assert_eq!(
DependencyDeleteElicitation.risk(),
RiskLevel::OperationDestructive
);
}
#[test]
fn generate_returns_one_item() {
assert_eq!(
DependencyDeleteElicitation
.generate(&ctx_operation_destructive())
.len(),
1
);
}
#[test]
fn item_is_pair_variant() {
let specs = DependencyDeleteElicitation.generate(&ctx_operation_destructive());
assert!(matches!(specs[0], ProbeSpec::Pair(_)));
}
#[test]
fn probe_body_is_none() {
let specs = DependencyDeleteElicitation.generate(&ctx_operation_destructive());
let ProbeSpec::Pair(pair) = &specs[0] else {
panic!("expected Pair")
};
assert!(pair.probe.body.is_none());
}
#[test]
fn technique_strength_is_may() {
let specs = DependencyDeleteElicitation.generate(&ctx_operation_destructive());
assert_eq!(specs[0].technique().strength, NormativeStrength::May);
}
#[test]
fn normalization_weight_is_0_02() {
assert_eq!(TECHNIQUE.normalization_weight, Some(0.02));
}
#[test]
fn inverted_signal_weight_is_none() {
assert_eq!(TECHNIQUE.inverted_signal_weight, None);
}
}