Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref(filters): Access fields through trait #3397

Merged
merged 8 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 17 additions & 14 deletions relay-filter/src/browser_extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

use once_cell::sync::Lazy;
use regex::Regex;
use relay_event_schema::protocol::{Event, Exception};
use relay_event_schema::protocol::Exception;

use crate::{FilterConfig, FilterStatKey};
use crate::{FilterConfig, FilterStatKey, Filterable};

static EXTENSION_EXC_VALUES: Lazy<Regex> = Lazy::new(|| {
Regex::new(
Expand Down Expand Up @@ -81,13 +81,13 @@ static EXTENSION_EXC_SOURCES: Lazy<Regex> = Lazy::new(|| {
const ANONYMOUS_FRAMES: [&str; 2] = ["<anonymous>", "[native code]"];

/// Check if the event originates from known problematic browser extensions.
pub fn matches(event: &Event) -> bool {
if let Some(ex_val) = get_exception_value(event) {
pub fn matches<F: Filterable>(item: &F) -> bool {
if let Some(ex_val) = get_exception_value(item) {
if EXTENSION_EXC_VALUES.is_match(ex_val) {
return true;
}
}
if let Some(ex_source) = get_exception_source(event) {
if let Some(ex_source) = get_exception_source(item) {
if EXTENSION_EXC_SOURCES.is_match(ex_source) {
return true;
}
Expand All @@ -96,31 +96,34 @@ pub fn matches(event: &Event) -> bool {
}

/// Filters events originating from known problematic browser extensions.
pub fn should_filter(event: &Event, config: &FilterConfig) -> Result<(), FilterStatKey> {
pub fn should_filter<F>(item: &F, config: &FilterConfig) -> Result<(), FilterStatKey>
where
F: Filterable,
{
if !config.is_enabled {
return Ok(());
}

if matches(event) {
if matches(item) {
Err(FilterStatKey::BrowserExtensions)
} else {
Ok(())
}
}

fn get_first_exception(event: &Event) -> Option<&Exception> {
let values = event.exceptions.value()?;
fn get_first_exception<F: Filterable>(item: &F) -> Option<&Exception> {
let values = item.exceptions()?;
let exceptions = values.values.value()?;
exceptions.first()?.value()
}

fn get_exception_value(event: &Event) -> Option<&str> {
let exception = get_first_exception(event)?;
fn get_exception_value<F: Filterable>(item: &F) -> Option<&str> {
let exception = get_first_exception(item)?;
Some(exception.value.value()?.as_str())
}

fn get_exception_source(event: &Event) -> Option<&str> {
let exception = get_first_exception(event)?;
fn get_exception_source<F: Filterable>(item: &F) -> Option<&str> {
let exception = get_first_exception(item)?;
let frames = exception.stacktrace.value()?.frames.value()?;
// Iterate from the tail and get the first frame which is not anonymous.
for f in frames.iter().rev() {
Expand All @@ -136,7 +139,7 @@ fn get_exception_source(event: &Event) -> Option<&str> {
#[cfg(test)]
mod tests {
use relay_event_schema::protocol::{
Frame, JsonLenientString, RawStacktrace, Stacktrace, Values,
Event, Frame, JsonLenientString, RawStacktrace, Stacktrace, Values,
};
use relay_protocol::Annotated;

Expand Down
21 changes: 10 additions & 11 deletions relay-filter/src/csp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,23 @@
//!
//! Events originating from a CSP message can be filtered based on the source URL

use relay_event_schema::protocol::{Event, EventType};
use relay_event_schema::protocol::Csp;

use crate::{CspFilterConfig, FilterStatKey};
use crate::{CspFilterConfig, FilterStatKey, Filterable};

/// Checks if the event is a CSP Event from one of the disallowed sources.
pub fn matches<It, S>(event: &Event, disallowed_sources: It) -> bool
pub fn matches<It, S>(csp: Option<&Csp>, disallowed_sources: It) -> bool
where
It: IntoIterator<Item = S>,
S: AsRef<str>,
{
if event.ty.value() != Some(&EventType::Csp) {
return false;
}

// parse the sources for easy processing
let disallowed_sources: Vec<SchemeDomainPort> = disallowed_sources
.into_iter()
.map(|origin| -> SchemeDomainPort { origin.as_ref().into() })
.collect();

if let Some(csp) = event.csp.value() {
if let Some(csp) = csp {
if matches_any_origin(csp.blocked_uri.as_str(), &disallowed_sources) {
return true;
}
Expand All @@ -37,8 +33,11 @@ where
}

/// Filters CSP events based on disallowed sources.
pub fn should_filter(event: &Event, config: &CspFilterConfig) -> Result<(), FilterStatKey> {
if matches(event, &config.disallowed_sources) {
pub fn should_filter<F>(item: &F, config: &CspFilterConfig) -> Result<(), FilterStatKey>
where
F: Filterable,
{
if matches(item.csp(), &config.disallowed_sources) {
Err(FilterStatKey::InvalidCsp)
} else {
Ok(())
Expand Down Expand Up @@ -159,7 +158,7 @@ pub fn matches_any_origin(url: Option<&str>, origins: &[SchemeDomainPort]) -> bo

#[cfg(test)]
mod tests {
use relay_event_schema::protocol::Csp;
use relay_event_schema::protocol::{Csp, Event, EventType};
use relay_protocol::Annotated;

use super::*;
Expand Down
17 changes: 8 additions & 9 deletions relay-filter/src/error_messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
use std::borrow::Cow;

use relay_common::glob3::GlobPatterns;
use relay_event_schema::protocol::Event;

use crate::{ErrorMessagesFilterConfig, FilterStatKey};
use crate::{ErrorMessagesFilterConfig, FilterStatKey, Filterable};

/// Checks events by patterns in their error messages.
pub fn matches(event: &Event, patterns: &GlobPatterns) -> bool {
if let Some(logentry) = event.logentry.value() {
pub fn matches<F: Filterable>(item: &F, patterns: &GlobPatterns) -> bool {
if let Some(logentry) = item.logentry() {
if let Some(message) = logentry.formatted.value() {
if patterns.is_match(message.as_ref()) {
return true;
Expand All @@ -24,7 +23,7 @@ pub fn matches(event: &Event, patterns: &GlobPatterns) -> bool {
}
}

if let Some(exception_values) = event.exceptions.value() {
if let Some(exception_values) = item.exceptions() {
if let Some(exceptions) = exception_values.values.value() {
for exception in exceptions {
if let Some(exception) = exception.value() {
Expand All @@ -46,11 +45,11 @@ pub fn matches(event: &Event, patterns: &GlobPatterns) -> bool {
}

/// Filters events by patterns in their error messages.
pub fn should_filter(
event: &Event,
pub fn should_filter<F: Filterable>(
item: &F,
config: &ErrorMessagesFilterConfig,
) -> Result<(), FilterStatKey> {
if matches(event, &config.patterns) {
if matches(item, &config.patterns) {
Err(FilterStatKey::ErrorMessage)
} else {
Ok(())
Expand All @@ -59,7 +58,7 @@ pub fn should_filter(

#[cfg(test)]
mod tests {
use relay_event_schema::protocol::{Exception, LogEntry, Values};
use relay_event_schema::protocol::{Event, Exception, LogEntry, Values};
use relay_protocol::Annotated;

use super::*;
Expand Down
22 changes: 13 additions & 9 deletions relay-filter/src/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
use std::iter::FusedIterator;

use crate::{FilterStatKey, GenericFilterConfig, GenericFiltersConfig, GenericFiltersMap};
use relay_event_schema::protocol::Event;
use relay_protocol::RuleCondition;

use relay_protocol::{Getter, RuleCondition};

/// Maximum supported version of the generic filters schema.
///
Expand All @@ -25,15 +25,19 @@ pub fn are_generic_filters_supported(
}

/// Checks events by patterns in their error messages.
fn matches(event: &Event, condition: Option<&RuleCondition>) -> bool {
fn matches<F: Getter>(item: &F, condition: Option<&RuleCondition>) -> bool {
// TODO: the condition DSL needs to be extended to support more complex semantics, such as
// collections operations.
condition.map_or(false, |condition| condition.matches(event))
condition.map_or(false, |condition| condition.matches(item))
}

/// Filters events by patterns in their error messages.
pub(crate) fn should_filter(
event: &Event,
/// Filters events by any generic condition.
///
/// Note that conditions may have type-specific getter strings, e.g. `"event.some_field"`. In order
/// to make such a generic filter apply to non-Event types, make sure that the [`Getter`] implementation
/// for that type maps `"event.some_field"` to the corresponding field on that type.
pub(crate) fn should_filter<F: Getter>(
item: &F,
project_filters: &GenericFiltersConfig,
global_filters: Option<&GenericFiltersConfig>,
) -> Result<(), FilterStatKey> {
Expand All @@ -45,7 +49,7 @@ pub(crate) fn should_filter(
);

for filter_config in filters {
if filter_config.is_enabled && matches(event, filter_config.condition) {
if filter_config.is_enabled && matches(item, filter_config.condition) {
return Err(FilterStatKey::GenericFilter(filter_config.id.to_owned()));
}
}
Expand Down Expand Up @@ -173,7 +177,7 @@ mod tests {

use super::*;

use relay_event_schema::protocol::LenientString;
use relay_event_schema::protocol::{Event, LenientString};
use relay_protocol::Annotated;

fn mock_filters() -> GenericFiltersMap {
Expand Down
74 changes: 74 additions & 0 deletions relay-filter/src/interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//! This module contains the trait for items that can be filtered by Inbound Filters, plus
//! the implementation for [`Event`].

use relay_event_schema::protocol::{Csp, Event, EventType, Exception, LogEntry, Values};
use url::Url;

/// A data item to which filters can be applied.
pub trait Filterable {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open for ideas on how to name this trait.

/// The CSP report contained in the item. Only for CSP reports.
fn csp(&self) -> Option<&Csp>;

/// The exception values of the item. Only for error events.
fn exceptions(&self) -> Option<&Values<Exception>>;

/// The IP address of the client that sent the data.
fn ip_addr(&self) -> Option<&str>;

/// The logentry message. Only for error events.
fn logentry(&self) -> Option<&LogEntry>;

/// The release string of the data item.
fn release(&self) -> Option<&str>;

/// The transaction name. Only for transaction events.
fn transaction(&self) -> Option<&str>;

/// The URL from which the data originates. Used for localhost filtering.
jjbayer marked this conversation as resolved.
Show resolved Hide resolved
fn url(&self) -> Option<Url>;

/// The user agent of the client that sent the data.
fn user_agent(&self) -> Option<&str>;
}

impl Filterable for Event {
fn csp(&self) -> Option<&Csp> {
if self.ty.value() != Some(&EventType::Csp) {
return None;
}
self.csp.value()
}

fn exceptions(&self) -> Option<&Values<Exception>> {
self.exceptions.value()
}

fn ip_addr(&self) -> Option<&str> {
let user = self.user.value()?;
Some(user.ip_address.value()?.as_ref())
iker-barriocanal marked this conversation as resolved.
Show resolved Hide resolved
}

fn logentry(&self) -> Option<&LogEntry> {
self.logentry.value()
}

fn release(&self) -> Option<&str> {
self.release.as_str()
}

fn transaction(&self) -> Option<&str> {
if self.ty.value() != Some(&EventType::Transaction) {
return None;
}
self.transaction.as_str()
}

fn url(&self) -> Option<Url> {
let url_str = self.request.value()?.url.value()?;
Url::parse(url_str).ok()
}

fn user_agent(&self) -> Option<&str> {
self.user_agent()
}
}
Loading
Loading