From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id D3AAF610C8 for ; Thu, 19 Nov 2020 15:56:15 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id D1CE08405 for ; Thu, 19 Nov 2020 15:56:15 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id C292C83E5 for ; Thu, 19 Nov 2020 15:56:14 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 86A0D43B94 for ; Thu, 19 Nov 2020 15:56:14 +0100 (CET) From: Wolfgang Bumiller To: pbs-devel@lists.proxmox.com Date: Thu, 19 Nov 2020 15:56:03 +0100 Message-Id: <20201119145608.16866-2-w.bumiller@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201119145608.16866-1-w.bumiller@proxmox.com> References: <20201119145608.16866-1-w.bumiller@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.028 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [tools.rs] Subject: [pbs-devel] [RFC backup 1/6] add tools::serde_filter submodule X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 19 Nov 2020 14:56:15 -0000 can be used to perform filtering at parse time Signed-off-by: Wolfgang Bumiller --- src/tools.rs | 1 + src/tools/serde_filter.rs | 97 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 src/tools/serde_filter.rs diff --git a/src/tools.rs b/src/tools.rs index 08f9d22f..8cc446dd 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -32,6 +32,7 @@ pub mod loopdev; pub mod lru_cache; pub mod nom; pub mod runtime; +pub mod serde_filter; pub mod socket; pub mod statistics; pub mod subscription; diff --git a/src/tools/serde_filter.rs b/src/tools/serde_filter.rs new file mode 100644 index 00000000..b8402696 --- /dev/null +++ b/src/tools/serde_filter.rs @@ -0,0 +1,97 @@ +use std::marker::PhantomData; + +use serde::Deserialize; + +/// Helper to filter data while deserializing it. +/// +/// An example use case is filtering out expired registration challenges at load time of our TFA +/// config: +/// +/// ``` +/// # use proxmox_backup::tools::serde_filter::FilteredVecVisitor; +/// # use serde::{Deserialize, Deserializer, Serialize}; +/// # const CHALLENGE_TIMEOUT: i64 = 2 * 60; +/// #[derive(Deserialize)] +/// struct Challenge { +/// /// Expiration time as unix epoch. +/// expires: i64, +/// +/// // ...other entries... +/// } +/// +/// #[derive(Default, Deserialize)] +/// #[serde(deny_unknown_fields)] +/// #[serde(rename_all = "kebab-case")] +/// pub struct TfaUserData { +/// // ...other entries... +/// +/// #[serde(skip_serializing_if = "Vec::is_empty", default)] +/// #[serde(deserialize_with = "filter_expired_registrations")] +/// registrations: Vec, +/// } +/// +/// fn filter_expired_registrations<'de, D>(deserializer: D) -> Result, D::Error> +/// where +/// D: Deserializer<'de>, +/// { +/// let expire_before = proxmox::tools::time::epoch_i64() - CHALLENGE_TIMEOUT; +/// +/// Ok(deserializer.deserialize_seq( +/// FilteredVecVisitor::new( +/// "a u2f registration challenge entry", +/// move |c: &Challenge| c.expires < expire_before, +/// ) +/// )?) +/// } +/// ``` +pub struct FilteredVecVisitor +where + F: Fn(&T) -> bool +{ + filter: F, + expecting: &'static str, + _ty: PhantomData, +} + +impl FilteredVecVisitor +where + F: Fn(&T) -> bool, +{ + pub fn new(expecting: &'static str, filter: F) -> Self { + Self { + filter, + expecting, + _ty: PhantomData, + } + } +} + +impl<'de, F, T> serde::de::Visitor<'de> for FilteredVecVisitor +where + F: Fn(&T) -> bool, + T: Deserialize<'de>, +{ + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str(self.expecting) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut out = match seq.size_hint() { + Some(hint) => Vec::with_capacity(hint), + None => Vec::new(), + }; + + while let Some(entry) = seq.next_element::()? { + if (self.filter)(&entry) { + out.push(entry); + } + } + + Ok(out) + } +} -- 2.20.1