From 2e4b2eb12f9de7613de8d51e7db4500262996bc9 Mon Sep 17 00:00:00 2001 From: henry0715-dev Date: Wed, 28 Aug 2024 15:20:03 +0900 Subject: [PATCH] Added `PasswordPolicy` for managing password expiry period data Close #326 --- CHANGELOG.md | 6 ++- src/lib.rs | 14 +++++-- src/tables.rs | 8 +++- src/tables/account_policy.rs | 78 +++++++++++++++++++++++++++++++++++- 4 files changed, 99 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42478c6..4fd6352 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added + +- Added `PasswordPolicy` for managing password expiry period data. + ### Changed - Extended the `Table<'d, Account>::update` method signature to include the @@ -113,7 +117,7 @@ Versioning](https://semver.org/spec/v2.0.0.html). - Changed return type of `Store::outlier_map` to `Table`. - Moved `OutlierInfo` from `crate::outlier` to `crate`. - Included `model_id`, `timestamp`, and `is_saved` fields in `OutlierInfo`. -- Changed return type of `Store::account_policy_map` to `Table`. +- Changed return type of `Store::account_policy_map` to `Table`. - Removed redundant log messages in the backup module. ### Removed diff --git a/src/lib.rs b/src/lib.rs index d9a6378..e4ce86f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,10 +69,10 @@ pub use self::tables::{ CsvColumnExtra as CsvColumnExtraConfig, Customer, CustomerNetwork, CustomerUpdate, DataSource, DataSourceUpdate, DataType, Filter, Giganto, IndexedTable, Iterable, ModelIndicator, Network, NetworkUpdate, Node, NodeProfile, NodeTable, NodeUpdate, OutlierInfo, OutlierInfoKey, - OutlierInfoValue, PacketAttr, ProtocolPorts, Response, ResponseKind, SamplingInterval, - SamplingKind, SamplingPeriod, SamplingPolicy, SamplingPolicyUpdate, Structured, - StructuredClusteringAlgorithm, Table, Template, Ti, TiCmpKind, Tidb, TidbKind, TidbRule, - TorExitNode, TrafficFilter, TriagePolicy, TriagePolicyUpdate, TriageResponse, + OutlierInfoValue, PacketAttr, PasswordPolicy, ProtocolPorts, Response, ResponseKind, + SamplingInterval, SamplingKind, SamplingPeriod, SamplingPolicy, SamplingPolicyUpdate, + Structured, StructuredClusteringAlgorithm, Table, Template, Ti, TiCmpKind, Tidb, TidbKind, + TidbRule, TorExitNode, TrafficFilter, TriagePolicy, TriagePolicyUpdate, TriageResponse, TriageResponseUpdate, TrustedDomain, TrustedUserAgent, UniqueKey, Unstructured, UnstructuredClusteringAlgorithm, ValueKind, }; @@ -157,6 +157,12 @@ impl Store { self.states.account_policy() } + #[must_use] + #[allow(clippy::missing_panics_doc)] + pub fn password_policy_map(&self) -> Table { + self.states.password_policy() + } + #[must_use] #[allow(clippy::missing_panics_doc)] pub fn agents_map(&self) -> Table { diff --git a/src/tables.rs b/src/tables.rs index de08d9d..8930f84 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -36,7 +36,7 @@ use anyhow::{anyhow, Result}; use serde::{Deserialize, Serialize}; pub use self::access_token::AccessToken; -pub use self::account_policy::AccountPolicy; +pub use self::account_policy::{AccountPolicy, PasswordPolicy}; pub use self::agent::{Agent, Config as AgentConfig, Kind as AgentKind, Status as AgentStatus}; pub use self::allow_network::{AllowNetwork, Update as AllowNetworkUpdate}; pub use self::block_network::{BlockNetwork, Update as BlockNetworkUpdate}; @@ -180,6 +180,12 @@ impl StateDb { Table::::open(inner).expect("{ACCOUNT_POLICY} table must be present") } + #[must_use] + pub(crate) fn password_policy(&self) -> Table { + let inner = self.inner.as_ref().expect("database must be open"); + Table::::open(inner).expect("{PASSWORD_POLICY} table must be present") + } + #[must_use] pub(crate) fn agents(&self) -> Table { let inner = self.inner.as_ref().expect("database must be open"); diff --git a/src/tables/account_policy.rs b/src/tables/account_policy.rs index 536f2f8..68f610f 100644 --- a/src/tables/account_policy.rs +++ b/src/tables/account_policy.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::{Map, Table}; pub(crate) const ACCOUNT_POLICY_KEY: &[u8] = b"account policy key"; - +pub(crate) const PASSWORD_EXPIRATION_PERIOD_KEY: &[u8] = b"password expiration period"; #[derive(Serialize, Deserialize)] pub struct AccountPolicy { pub(crate) expiry_period_in_secs: u32, @@ -73,6 +73,69 @@ impl<'d> Table<'d, AccountPolicy> { } } +#[derive(Serialize, Deserialize)] +pub struct PasswordPolicy { + pub(crate) expiry_password_period_in_days: u32, +} + +impl<'d> Table<'d, PasswordPolicy> { + /// Opens the `account_policy` map in the database. + /// + /// Returns `None` if the map does not exist. + pub(super) fn open(db: &'d OptimisticTransactionDB) -> Option { + Map::open(db, super::ACCOUNT_POLICY).map(Table::new) + } + + /// Initializes the expiry period. + /// + /// # Errors + /// + /// Returns an error if it has already been initialized or + /// if database operation fails. + pub fn init_password_expiry_period(&self, days: u32) -> Result<()> { + let init = PasswordPolicy { + expiry_password_period_in_days: days, + }; + self.map + .insert(PASSWORD_EXPIRATION_PERIOD_KEY, &super::serialize(&init)?) + } + + /// Updates or initializes the password expiry period. + /// + /// # Errors + /// + /// Returns an error if database operation fails. + pub fn update_password_expiry_period(&self, days: u32) -> Result<()> { + if let Some(old) = self.map.get(PASSWORD_EXPIRATION_PERIOD_KEY)? { + let update = super::serialize(&PasswordPolicy { + expiry_password_period_in_days: days, + })?; + self.map.update( + (PASSWORD_EXPIRATION_PERIOD_KEY, old.as_ref()), + (PASSWORD_EXPIRATION_PERIOD_KEY, &update), + ) + } else { + self.init_password_expiry_period(days) + } + } + + /// Returns the current password expiry period, + /// or `None` if it hasn't been initialized. + /// + /// # Errors + /// + /// Returns an error if database operation fails. + pub fn current_password_expiry_period(&self) -> Result> { + self.map + .get(PASSWORD_EXPIRATION_PERIOD_KEY)? + .map(|p| { + super::deserialize(p.as_ref()) + .map(|p: PasswordPolicy| p.expiry_password_period_in_days) + }) + .transpose() + } +} + #[cfg(test)] mod tests { use std::sync::Arc; @@ -91,4 +154,17 @@ mod tests { assert!(table.update_expiry_period(20).is_ok()); assert_eq!(table.current_expiry_period().unwrap(), Some(20)); } + + #[test] + fn password_policy_operations() { + let db_dir = tempfile::tempdir().unwrap(); + let backup_dir = tempfile::tempdir().unwrap(); + let store = Arc::new(Store::new(db_dir.path(), backup_dir.path()).unwrap()); + let table = store.password_policy_map(); + + assert!(table.update_password_expiry_period(90).is_ok()); + assert_eq!(table.current_password_expiry_period().unwrap(), Some(90)); + assert!(table.update_password_expiry_period(180).is_ok()); + assert_eq!(table.current_password_expiry_period().unwrap(), Some(180)); + } }