Skip to content

Commit e58b127

Browse files
committed
feat: Implement path-based and pattern-based filter rules
Add advanced matching capabilities for file transfer filtering: New Matchers: - MultiExtensionMatcher: Match multiple file extensions with case sensitivity option - SizeMatcher: Match files by size (min/max/between) - AllMatcher: AND logic for combining matchers - CompositeMatcher: Unified AND/OR/NOT composite matching Enhanced Configuration: - extensions: Match by file extensions ["exe", "bat", "ps1"] - directory: Match by path component (e.g., ".git") - min_size/max_size: File size filtering - composite: Complex AND/OR/NOT rules with nested matchers SizeAwareFilter Trait: - check_with_size() for size-based filtering decisions - check_with_size_dest() for rename/copy operations with size New Configuration Types: - CompositeRuleConfig: Define composite rules - CompositeLogicType: and/or/not logic - MatcherConfig: Individual matcher in composite rules Example YAML configuration: ```yaml filter: enabled: true rules: - name: "block-executables" extensions: [exe, sh, bat] action: deny - name: "protect-env" composite: type: and matchers: - pattern: "*.env" - not: path_prefix: /home/ action: deny ``` Closes #139
1 parent 9a0f7e0 commit e58b127

5 files changed

Lines changed: 1072 additions & 26 deletions

File tree

src/server/config/types.rs

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ pub struct FilterConfig {
289289
}
290290

291291
/// A single file transfer filter rule.
292-
#[derive(Debug, Clone, Deserialize, Serialize)]
292+
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
293293
pub struct FilterRule {
294294
/// Rule name (for logging and debugging).
295295
///
@@ -309,6 +309,37 @@ pub struct FilterRule {
309309
#[serde(default)]
310310
pub path_prefix: Option<String>,
311311

312+
/// File extensions to match.
313+
///
314+
/// Example: ["exe", "sh", "bat"] blocks executable files
315+
#[serde(default)]
316+
pub extensions: Option<Vec<String>>,
317+
318+
/// Directory component to match.
319+
///
320+
/// Matches if any path component equals this value.
321+
/// Example: ".git" matches /project/.git/config
322+
#[serde(default)]
323+
pub directory: Option<String>,
324+
325+
/// Minimum file size in bytes.
326+
///
327+
/// Files smaller than this size will not match.
328+
#[serde(default)]
329+
pub min_size: Option<u64>,
330+
331+
/// Maximum file size in bytes.
332+
///
333+
/// Files larger than this size will not match.
334+
#[serde(default)]
335+
pub max_size: Option<u64>,
336+
337+
/// Composite rule configuration.
338+
///
339+
/// Allows combining multiple matchers with AND/OR/NOT logic.
340+
#[serde(default)]
341+
pub composite: Option<CompositeRuleConfig>,
342+
312343
/// Action to take when rule matches.
313344
pub action: FilterAction,
314345

@@ -326,6 +357,59 @@ pub struct FilterRule {
326357
pub users: Option<Vec<String>>,
327358
}
328359

360+
/// Configuration for composite filter rules.
361+
#[derive(Debug, Clone, Deserialize, Serialize)]
362+
pub struct CompositeRuleConfig {
363+
/// Type of composite logic: "and", "or", or "not"
364+
#[serde(rename = "type")]
365+
pub logic_type: CompositeLogicType,
366+
367+
/// List of matchers for AND/OR logic.
368+
#[serde(default)]
369+
pub matchers: Vec<MatcherConfig>,
370+
371+
/// Single matcher for NOT logic.
372+
#[serde(default)]
373+
pub matcher: Option<Box<MatcherConfig>>,
374+
}
375+
376+
/// Type of composite logic.
377+
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
378+
#[serde(rename_all = "lowercase")]
379+
pub enum CompositeLogicType {
380+
/// All matchers must match
381+
And,
382+
/// Any matcher must match
383+
Or,
384+
/// Invert the matcher result
385+
Not,
386+
}
387+
388+
/// Configuration for a single matcher within a composite rule.
389+
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
390+
#[serde(default)]
391+
pub struct MatcherConfig {
392+
/// Glob pattern
393+
#[serde(default)]
394+
pub pattern: Option<String>,
395+
396+
/// Path prefix
397+
#[serde(default)]
398+
pub path_prefix: Option<String>,
399+
400+
/// File extensions
401+
#[serde(default)]
402+
pub extensions: Option<Vec<String>>,
403+
404+
/// Directory component
405+
#[serde(default)]
406+
pub directory: Option<String>,
407+
408+
/// Nested NOT matcher
409+
#[serde(default)]
410+
pub not: Option<Box<MatcherConfig>>,
411+
}
412+
329413
/// Action to take when a filter rule matches.
330414
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
331415
#[serde(rename_all = "lowercase")]

src/server/filter/mod.rs

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,14 @@ pub mod policy;
6060
use std::fmt;
6161
use std::path::Path;
6262

63-
pub use self::path::{ExactMatcher, PrefixMatcher};
64-
pub use self::pattern::{GlobMatcher, RegexMatcher};
65-
pub use self::policy::{FilterPolicy, FilterRule, Matcher};
63+
pub use self::path::{
64+
normalize_path, ComponentMatcher, ExactMatcher, ExtensionMatcher, MultiExtensionMatcher,
65+
PrefixMatcher, SizeMatcher,
66+
};
67+
pub use self::pattern::{
68+
AllMatcher, CombinedMatcher, CompositeMatcher, GlobMatcher, NotMatcher, RegexMatcher,
69+
};
70+
pub use self::policy::{FilterPolicy, FilterRule, Matcher, SharedFilterPolicy};
6671

6772
/// File transfer operation type.
6873
///
@@ -244,6 +249,90 @@ impl TransferFilter for NoOpFilter {
244249
}
245250
}
246251

252+
/// Trait for size-aware file transfer filters.
253+
///
254+
/// This extends the basic `TransferFilter` trait to include file size
255+
/// in the filtering decision. Use this when you need to filter based
256+
/// on file size (e.g., block uploads larger than 100MB).
257+
///
258+
/// # Example
259+
///
260+
/// ```rust
261+
/// use bssh::server::filter::{FilterResult, Operation, SizeAwareFilter, TransferFilter};
262+
/// use bssh::server::filter::path::SizeMatcher;
263+
/// use std::path::Path;
264+
///
265+
/// struct MaxUploadSizeFilter {
266+
/// max_bytes: u64,
267+
/// }
268+
///
269+
/// impl TransferFilter for MaxUploadSizeFilter {
270+
/// fn check(&self, _path: &Path, _operation: Operation, _user: &str) -> FilterResult {
271+
/// // Without size info, we allow by default
272+
/// FilterResult::Allow
273+
/// }
274+
/// }
275+
///
276+
/// impl SizeAwareFilter for MaxUploadSizeFilter {
277+
/// fn check_with_size(
278+
/// &self,
279+
/// _path: &Path,
280+
/// size: u64,
281+
/// operation: Operation,
282+
/// _user: &str,
283+
/// ) -> FilterResult {
284+
/// if operation == Operation::Upload && size > self.max_bytes {
285+
/// FilterResult::Deny
286+
/// } else {
287+
/// FilterResult::Allow
288+
/// }
289+
/// }
290+
/// }
291+
/// ```
292+
pub trait SizeAwareFilter: TransferFilter {
293+
/// Check if an operation is allowed, taking file size into account.
294+
///
295+
/// # Arguments
296+
///
297+
/// * `path` - The file path being operated on
298+
/// * `size` - The file size in bytes
299+
/// * `operation` - The type of operation
300+
/// * `user` - The username performing the operation
301+
///
302+
/// # Returns
303+
///
304+
/// A `FilterResult` indicating whether to allow, deny, or log the operation.
305+
fn check_with_size(
306+
&self,
307+
path: &Path,
308+
size: u64,
309+
operation: Operation,
310+
user: &str,
311+
) -> FilterResult;
312+
313+
/// Check a two-path operation with size information.
314+
///
315+
/// Used for rename/copy operations where both source and destination
316+
/// are considered.
317+
fn check_with_size_dest(
318+
&self,
319+
src: &Path,
320+
src_size: u64,
321+
dest: &Path,
322+
operation: Operation,
323+
user: &str,
324+
) -> FilterResult {
325+
let src_result = self.check_with_size(src, src_size, operation, user);
326+
let dest_result = self.check(dest, operation, user);
327+
328+
match (src_result, dest_result) {
329+
(FilterResult::Deny, _) | (_, FilterResult::Deny) => FilterResult::Deny,
330+
(FilterResult::Log, _) | (_, FilterResult::Log) => FilterResult::Log,
331+
_ => FilterResult::Allow,
332+
}
333+
}
334+
}
335+
247336
#[cfg(test)]
248337
mod tests {
249338
use super::*;

0 commit comments

Comments
 (0)