From c006cbb2a04bce4afe90c12719986c68386aa4cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Tue, 5 Dec 2023 14:53:03 +0100 Subject: [PATCH 01/10] [PM-3434] Password generator (#261) ## Type of change ``` - [ ] Bug fix - [x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Implement password generation The `min_` fields were set to bool incorrectly I assume, so I changed them to u8. At the moment if all the minimums turn out larger than the length, I just expand the length, but that seems wrong. I feel like it would be best to return an error in that case instead of changing any values behind the user, thoughts? --- .../src/tool/generators/client_generator.rs | 21 + .../bitwarden/src/tool/generators/password.rs | 396 +++++++++++++++++- crates/bw/src/main.rs | 2 +- 3 files changed, 401 insertions(+), 18 deletions(-) diff --git a/crates/bitwarden/src/tool/generators/client_generator.rs b/crates/bitwarden/src/tool/generators/client_generator.rs index 8af8ecd19..4675ad99d 100644 --- a/crates/bitwarden/src/tool/generators/client_generator.rs +++ b/crates/bitwarden/src/tool/generators/client_generator.rs @@ -10,6 +10,27 @@ pub struct ClientGenerator<'a> { } impl<'a> ClientGenerator<'a> { + /// Generates a random password. + /// + /// The character sets and password length can be customized using the `input` parameter. + /// + /// # Examples + /// + /// ``` + /// use bitwarden::{Client, tool::PasswordGeneratorRequest, error::Result}; + /// async fn test() -> Result<()> { + /// let input = PasswordGeneratorRequest { + /// lowercase: true, + /// uppercase: true, + /// numbers: true, + /// length: 20, + /// ..Default::default() + /// }; + /// let password = Client::new(None).generator().password(input).await.unwrap(); + /// println!("{}", password); + /// Ok(()) + /// } + /// ``` pub async fn password(&self, input: PasswordGeneratorRequest) -> Result { password(input) } diff --git a/crates/bitwarden/src/tool/generators/password.rs b/crates/bitwarden/src/tool/generators/password.rs index 237394f56..cf986495c 100644 --- a/crates/bitwarden/src/tool/generators/password.rs +++ b/crates/bitwarden/src/tool/generators/password.rs @@ -1,32 +1,394 @@ -use crate::error::Result; +use std::collections::BTreeSet; + +use rand::{distributions::Distribution, seq::SliceRandom, RngCore}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -/// Password generator request. If all options are false, the default is to -/// generate a password with: -/// - lowercase -/// - uppercase -/// - numbers -/// -/// The default length is 16. -#[derive(Serialize, Deserialize, Debug, JsonSchema, Default)] +use crate::error::{Error, Result}; + +/// Password generator request options. +#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct PasswordGeneratorRequest { + /// Include lowercase characters (a-z). pub lowercase: bool, + /// Include uppercase characters (A-Z). pub uppercase: bool, + /// Include numbers (0-9). pub numbers: bool, + /// Include special characters: ! @ # $ % ^ & * pub special: bool, - pub length: Option, + /// The length of the generated password. + /// Note that the password length must be greater than the sum of all the minimums. + pub length: u8, + + /// When set to true, the generated password will not contain ambiguous characters. + /// The ambiguous characters are: I, O, l, 0, 1 + pub avoid_ambiguous: bool, // TODO: Should we rename this to include_all_characters? + + /// The minimum number of lowercase characters in the generated password. + /// When set, the value must be between 1 and 9. This value is ignored is lowercase is false + pub min_lowercase: Option, + /// The minimum number of uppercase characters in the generated password. + /// When set, the value must be between 1 and 9. This value is ignored is uppercase is false + pub min_uppercase: Option, + /// The minimum number of numbers in the generated password. + /// When set, the value must be between 1 and 9. This value is ignored is numbers is false + pub min_number: Option, + /// The minimum number of special characters in the generated password. + /// When set, the value must be between 1 and 9. This value is ignored is special is false + pub min_special: Option, +} + +const DEFAULT_PASSWORD_LENGTH: u8 = 16; + +impl Default for PasswordGeneratorRequest { + fn default() -> Self { + Self { + lowercase: true, + uppercase: true, + numbers: true, + special: false, + length: DEFAULT_PASSWORD_LENGTH, + avoid_ambiguous: false, + min_lowercase: None, + min_uppercase: None, + min_number: None, + min_special: None, + } + } +} + +const UPPER_CHARS_AMBIGUOUS: &[char] = &['I', 'O']; +const LOWER_CHARS_AMBIGUOUS: &[char] = &['l']; +const NUMBER_CHARS_AMBIGUOUS: &[char] = &['0', '1']; +const SPECIAL_CHARS: &[char] = &['!', '@', '#', '$', '%', '^', '&', '*']; + +/// A set of characters used to generate a password. This set is backed by a BTreeSet +/// to have consistent ordering between runs. This is not important during normal execution, +/// but it's necessary for the tests to be repeatable. +/// To create an instance, use [`CharSet::default()`](CharSet::default) +#[derive(Clone, Default)] +struct CharSet(BTreeSet); +impl CharSet { + /// Includes the given characters in the set. Any duplicate items will be ignored + pub fn include(self, other: impl IntoIterator) -> Self { + self.include_if(true, other) + } + + /// Includes the given characters in the set if the predicate is true. Any duplicate items will be ignored + pub fn include_if(mut self, predicate: bool, other: impl IntoIterator) -> Self { + if predicate { + self.0.extend(other); + } + self + } + + /// Excludes the given characters from the set. Any missing items will be ignored + pub fn exclude_if<'a>( + self, + predicate: bool, + other: impl IntoIterator, + ) -> Self { + if predicate { + let other: BTreeSet<_> = other.into_iter().copied().collect(); + Self(self.0.difference(&other).copied().collect()) + } else { + self + } + } +} +impl<'a> IntoIterator for &'a CharSet { + type Item = char; + type IntoIter = std::iter::Copied>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter().copied() + } +} +impl Distribution for CharSet { + fn sample(&self, rng: &mut R) -> char { + let idx = rng.gen_range(0..self.0.len()); + *self.0.iter().nth(idx).expect("Valid index") + } +} + +/// Represents a set of valid options to generate a password with. +/// To get an instance of it, use [`PasswordGeneratorRequest::validate_options`](PasswordGeneratorRequest::validate_options) +struct PasswordGeneratorOptions { + pub(super) lower: (CharSet, usize), + pub(super) upper: (CharSet, usize), + pub(super) number: (CharSet, usize), + pub(super) special: (CharSet, usize), + pub(super) all: (CharSet, usize), + + pub(super) length: usize, +} + +impl PasswordGeneratorRequest { + /// Validates the request and returns an immutable struct with valid options to use with the password generator. + fn validate_options(self) -> Result { + // TODO: Add password generator policy checks + + // We always have to have at least one character set enabled + if !self.lowercase && !self.uppercase && !self.numbers && !self.special { + return Err(Error::Internal( + "At least one character set must be enabled", + )); + } + + if self.length < 4 { + return Err(Error::Internal( + "A password must be at least 4 characters long", + )); + } + + // Make sure the minimum values are zero when the character + // set is disabled, and at least one when it's enabled + fn get_minimum(min: Option, enabled: bool) -> usize { + if enabled { + usize::max(min.unwrap_or(1) as usize, 1) + } else { + 0 + } + } + + let length = self.length as usize; + let min_lowercase = get_minimum(self.min_lowercase, self.lowercase); + let min_uppercase = get_minimum(self.min_uppercase, self.uppercase); + let min_number = get_minimum(self.min_number, self.numbers); + let min_special = get_minimum(self.min_special, self.special); + + // Check that the minimum lengths aren't larger than the password length + let minimum_length = min_lowercase + min_uppercase + min_number + min_special; + if minimum_length > length { + return Err(Error::Internal( + "Password length can't be less than the sum of the minimums", + )); + } + + let lower = ( + CharSet::default() + .include_if(self.lowercase, 'a'..='z') + .exclude_if(self.avoid_ambiguous, LOWER_CHARS_AMBIGUOUS), + min_lowercase, + ); + + let upper = ( + CharSet::default() + .include_if(self.uppercase, 'A'..='Z') + .exclude_if(self.avoid_ambiguous, UPPER_CHARS_AMBIGUOUS), + min_uppercase, + ); + + let number = ( + CharSet::default() + .include_if(self.numbers, '0'..='9') + .exclude_if(self.avoid_ambiguous, NUMBER_CHARS_AMBIGUOUS), + min_number, + ); + + let special = ( + CharSet::default().include_if(self.special, SPECIAL_CHARS.iter().copied()), + min_special, + ); + + let all = ( + CharSet::default() + .include(&lower.0) + .include(&upper.0) + .include(&number.0) + .include(&special.0), + length - minimum_length, + ); + + Ok(PasswordGeneratorOptions { + lower, + upper, + number, + special, + all, + length, + }) + } +} + +/// Implementation of the random password generator. This is not accessible to the public API. +/// See [`ClientGenerator::password`](crate::ClientGenerator::password) for the API function. +pub(super) fn password(input: PasswordGeneratorRequest) -> Result { + let options = input.validate_options()?; + Ok(password_with_rng(rand::thread_rng(), options)) +} + +fn password_with_rng(mut rng: impl RngCore, options: PasswordGeneratorOptions) -> String { + let mut buf: Vec = Vec::with_capacity(options.length); + + let opts = [ + &options.all, + &options.upper, + &options.lower, + &options.number, + &options.special, + ]; + for (set, qty) in opts { + buf.extend(set.sample_iter(&mut rng).take(*qty)); + } + + buf.shuffle(&mut rng); - pub avoid_ambiguous: Option, // TODO: Should we rename this to include_all_characters? - pub min_lowercase: Option, - pub min_uppercase: Option, - pub min_number: Option, - pub min_special: Option, + buf.iter().collect() } -pub(super) fn password(_input: PasswordGeneratorRequest) -> Result { - Ok("pa11w0rd".to_string()) +#[cfg(test)] +mod test { + use std::collections::BTreeSet; + + use rand::SeedableRng; + + use super::*; + + // We convert the slices to BTreeSets to be able to use `is_subset` + fn ref_to_set<'a>(chars: impl IntoIterator) -> BTreeSet { + chars.into_iter().copied().collect() + } + fn to_set(chars: impl IntoIterator) -> BTreeSet { + chars.into_iter().collect() + } + + #[test] + fn test_password_gen_all_charsets_enabled() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let options = PasswordGeneratorRequest { + lowercase: true, + uppercase: true, + numbers: true, + special: true, + avoid_ambiguous: false, + ..Default::default() + } + .validate_options() + .unwrap(); + + assert_eq!(to_set(&options.lower.0), to_set('a'..='z')); + assert_eq!(to_set(&options.upper.0), to_set('A'..='Z')); + assert_eq!(to_set(&options.number.0), to_set('0'..='9')); + assert_eq!(to_set(&options.special.0), ref_to_set(SPECIAL_CHARS)); + + let pass = password_with_rng(&mut rng, options); + assert_eq!(pass, "Z!^B5r%hUa23dFM@"); + } + + #[test] + fn test_password_gen_only_letters_enabled() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let options = PasswordGeneratorRequest { + lowercase: true, + uppercase: true, + numbers: false, + special: false, + avoid_ambiguous: false, + ..Default::default() + } + .validate_options() + .unwrap(); + + assert_eq!(to_set(&options.lower.0), to_set('a'..='z')); + assert_eq!(to_set(&options.upper.0), to_set('A'..='Z')); + assert_eq!(to_set(&options.number.0), to_set([])); + assert_eq!(to_set(&options.special.0), to_set([])); + + let pass = password_with_rng(&mut rng, options); + assert_eq!(pass, "NQiFrGufQMiNUAmj"); + } + + #[test] + fn test_password_gen_only_numbers_and_lower_enabled_no_ambiguous() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let options = PasswordGeneratorRequest { + lowercase: true, + uppercase: false, + numbers: true, + special: false, + avoid_ambiguous: true, + ..Default::default() + } + .validate_options() + .unwrap(); + + assert!(to_set(&options.lower.0).is_subset(&to_set('a'..='z'))); + assert!(to_set(&options.lower.0).is_disjoint(&ref_to_set(LOWER_CHARS_AMBIGUOUS))); + + assert!(to_set(&options.number.0).is_subset(&to_set('0'..='9'))); + assert!(to_set(&options.number.0).is_disjoint(&ref_to_set(NUMBER_CHARS_AMBIGUOUS))); + + assert_eq!(to_set(&options.upper.0), to_set([])); + assert_eq!(to_set(&options.special.0), to_set([])); + + let pass = password_with_rng(&mut rng, options); + assert_eq!(pass, "mnjabfz5ct272prf"); + } + + #[test] + fn test_password_gen_only_upper_and_special_enabled_no_ambiguous() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let options = PasswordGeneratorRequest { + lowercase: false, + uppercase: true, + numbers: false, + special: true, + avoid_ambiguous: true, + ..Default::default() + } + .validate_options() + .unwrap(); + + assert!(to_set(&options.upper.0).is_subset(&to_set('A'..='Z'))); + assert!(to_set(&options.upper.0).is_disjoint(&ref_to_set(UPPER_CHARS_AMBIGUOUS))); + + assert_eq!(to_set(&options.special.0), ref_to_set(SPECIAL_CHARS)); + + assert_eq!(to_set(&options.lower.0), to_set([])); + assert_eq!(to_set(&options.number.0), to_set([])); + + let pass = password_with_rng(&mut rng, options); + assert_eq!(pass, "B*GBQANS%UZPQD!K"); + } + + #[test] + fn test_password_gen_minimum_limits() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let options = PasswordGeneratorRequest { + lowercase: true, + uppercase: true, + numbers: true, + special: true, + avoid_ambiguous: false, + length: 24, + min_lowercase: Some(5), + min_uppercase: Some(5), + min_number: Some(5), + min_special: Some(5), + } + .validate_options() + .unwrap(); + + assert_eq!(to_set(&options.lower.0), to_set('a'..='z')); + assert_eq!(to_set(&options.upper.0), to_set('A'..='Z')); + assert_eq!(to_set(&options.number.0), to_set('0'..='9')); + assert_eq!(to_set(&options.special.0), ref_to_set(SPECIAL_CHARS)); + + assert_eq!(options.lower.1, 5); + assert_eq!(options.upper.1, 5); + assert_eq!(options.number.1, 5); + assert_eq!(options.special.1, 5); + + let pass = password_with_rng(&mut rng, options); + assert_eq!(pass, "236q5!a#R%PG5rI%k1!*@uRt"); + } } diff --git a/crates/bw/src/main.rs b/crates/bw/src/main.rs index c4664c7f4..236aef22d 100644 --- a/crates/bw/src/main.rs +++ b/crates/bw/src/main.rs @@ -213,7 +213,7 @@ async fn process_commands() -> Result<()> { uppercase: args.uppercase, numbers: args.numbers, special: args.special, - length: Some(args.length), + length: args.length, ..Default::default() }) .await?; From f8ba13a9a6a9aedfe6da3dedf40d38bd44b64789 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:44:14 -0500 Subject: [PATCH 02/10] [deps]: Update actions/setup-dotnet action to v4 (#395) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-dotnet.yml | 2 +- .github/workflows/publish-dotnet.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-dotnet.yml b/.github/workflows/build-dotnet.yml index 1889d5656..7ebb03526 100644 --- a/.github/workflows/build-dotnet.yml +++ b/.github/workflows/build-dotnet.yml @@ -30,7 +30,7 @@ jobs: path: languages/csharp/Bitwarden.Sdk - name: Set up .NET Core - uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 + uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 with: global-json-file: languages/csharp/global.json diff --git a/.github/workflows/publish-dotnet.yml b/.github/workflows/publish-dotnet.yml index c86b90dd1..b52656de2 100644 --- a/.github/workflows/publish-dotnet.yml +++ b/.github/workflows/publish-dotnet.yml @@ -32,7 +32,7 @@ jobs: path: languages/csharp/Bitwarden.Sdk - name: Set up .NET Core - uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 + uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 with: global-json-file: languages/csharp/global.json From e5a35bc5127d250aa0a7ecdd8e2a99dc2ed4db8a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:47:27 -0500 Subject: [PATCH 03/10] [deps]: Update actions/setup-java action to v4 (#390) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-android.yml | 2 +- .github/workflows/build-java.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index d04d15a45..e3abd0430 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -80,7 +80,7 @@ jobs: key: cargo-combine-cache - name: Setup Java - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0 with: distribution: temurin java-version: 17 diff --git a/.github/workflows/build-java.yml b/.github/workflows/build-java.yml index aa99523a7..653b5337b 100644 --- a/.github/workflows/build-java.yml +++ b/.github/workflows/build-java.yml @@ -30,7 +30,7 @@ jobs: path: languages/java/src/main/java/bit/sdk/schema/ - name: Setup Java - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0 with: distribution: temurin java-version: 17 From 2e09ae0f9cd8f1621be6b06b1aba68c93d8e73ca Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:48:54 -0500 Subject: [PATCH 04/10] [deps]: Update actions/checkout action to v4 (#387) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/publish-php.yml | 2 +- .github/workflows/publish-ruby.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-php.yml b/.github/workflows/publish-php.yml index bc1478bc5..e425e26eb 100644 --- a/.github/workflows/publish-php.yml +++ b/.github/workflows/publish-php.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup PHP with PECL extension uses: shivammathur/setup-php@7fdd3ece872ec7ec4c098ae5ab7637d5e0a96067 # 2.26.0 diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index b4e022243..abd472c64 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -20,7 +20,7 @@ jobs: - build_rust steps: - name: Checkout Repository - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Ruby uses: ruby/setup-ruby@54a18e26dbbb1eabc604f317ade9a5788dddef81 # v1.159.0 From f43b6970561b76c8c179cb4f8cf753da10c10f40 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:50:10 -0500 Subject: [PATCH 05/10] [deps]: Update gh minor (#381) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-java.yml | 4 ++-- .github/workflows/generate_schemas.yml | 2 +- .github/workflows/publish-php.yml | 2 +- .github/workflows/publish-ruby.yml | 2 +- .github/workflows/publish-rust-crates.yml | 2 +- .github/workflows/release-cli.yml | 2 +- .github/workflows/release-napi.yml | 2 +- .github/workflows/version-bump.yml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-java.yml b/.github/workflows/build-java.yml index 653b5337b..002164647 100644 --- a/.github/workflows/build-java.yml +++ b/.github/workflows/build-java.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download Java schemas artifact uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 @@ -60,7 +60,7 @@ jobs: path: languages/java/src/main/resources/windows-x64 - name: Publish Maven - uses: gradle/gradle-build-action@b5126f31dbc19dd434c3269bf8c28c315e121da2 # v2.8.1 + uses: gradle/gradle-build-action@87a9a15658c426a54dd469d4fc7dc1a73ca9d4a6 # v2.10.0 with: arguments: publish build-root-directory: languages/java diff --git a/.github/workflows/generate_schemas.yml b/.github/workflows/generate_schemas.yml index b0517ea36..1d69b1190 100644 --- a/.github/workflows/generate_schemas.yml +++ b/.github/workflows/generate_schemas.yml @@ -72,7 +72,7 @@ jobs: if-no-files-found: error - name: Upload Go schemas artifact - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: name: schemas.go path: ${{ github.workspace }}/languages/go/schema.go diff --git a/.github/workflows/publish-php.yml b/.github/workflows/publish-php.yml index e425e26eb..b033319ec 100644 --- a/.github/workflows/publish-php.yml +++ b/.github/workflows/publish-php.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup PHP with PECL extension - uses: shivammathur/setup-php@7fdd3ece872ec7ec4c098ae5ab7637d5e0a96067 # 2.26.0 + uses: shivammathur/setup-php@e6f75134d35752277f093989e72e140eaa222f35 # 2.28.0 with: php-version: "8.0" tools: composer diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index abd472c64..fa46af292 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Ruby - uses: ruby/setup-ruby@54a18e26dbbb1eabc604f317ade9a5788dddef81 # v1.159.0 + uses: ruby/setup-ruby@8575951200e472d5f2d95c625da0c7bec8217c42 # v1.161.0 with: ruby-version: 3.2 diff --git a/.github/workflows/publish-rust-crates.yml b/.github/workflows/publish-rust-crates.yml index b5a03ad47..010238f40 100644 --- a/.github/workflows/publish-rust-crates.yml +++ b/.github/workflows/publish-rust-crates.yml @@ -103,7 +103,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Login to Azure - uses: Azure/login@4c88f01b0e3a5600e08a37889921afd060f75cf0 # v1.5.0 + uses: Azure/login@de95379fe4dadc2defb305917eaa7e5dde727294 # v1.5.1 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 9ef782b7f..aad7a0a46 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -128,7 +128,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Login to Azure - uses: Azure/login@4c88f01b0e3a5600e08a37889921afd060f75cf0 # v1.5.0 + uses: Azure/login@de95379fe4dadc2defb305917eaa7e5dde727294 # v1.5.1 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} diff --git a/.github/workflows/release-napi.yml b/.github/workflows/release-napi.yml index 1e7b2e022..3171cfb33 100644 --- a/.github/workflows/release-napi.yml +++ b/.github/workflows/release-napi.yml @@ -126,7 +126,7 @@ jobs: run: npm run tsc - name: Login to Azure - uses: Azure/login@4c88f01b0e3a5600e08a37889921afd060f75cf0 # v1.5.0 + uses: Azure/login@de95379fe4dadc2defb305917eaa7e5dde727294 # v1.5.1 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 9fb441fa9..5e403ac5b 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -43,7 +43,7 @@ jobs: run: cargo install cargo-edit - name: Login to Azure - Prod Subscription - uses: Azure/login@4c88f01b0e3a5600e08a37889921afd060f75cf0 # v1.5.0 + uses: Azure/login@de95379fe4dadc2defb305917eaa7e5dde727294 # v1.5.1 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} From 6ffef566754a3c75e4384fc4e7efe989abbd76eb Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Tue, 5 Dec 2023 10:22:35 -0500 Subject: [PATCH 06/10] SM-1023: Remove Instant from Client in SDK (#397) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Refactor out the `Instant` type in Client and use unix timestamps. This is to support this PR: https://github.com/bitwarden/sdk/pull/388 where we need to serialize token expiry times. It also brings benefits to other areas like WASM, where `Instant` doesn't exist. ## Code changes - **crates/bitwarden/src/auth/renew.rs:** Update the `renew_token` function to use timestamps instead of the `Instant` type - **crates/bitwarden/src/client/client.rs:** Update `Client` to store the unix timestamp for token expiration ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) --- crates/bitwarden/src/auth/renew.rs | 6 +++--- crates/bitwarden/src/client/client.rs | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/bitwarden/src/auth/renew.rs b/crates/bitwarden/src/auth/renew.rs index 3b2dbef7d..a8bfac51f 100644 --- a/crates/bitwarden/src/auth/renew.rs +++ b/crates/bitwarden/src/auth/renew.rs @@ -1,4 +1,4 @@ -use std::time::{Duration, Instant}; +use chrono::Utc; #[cfg(feature = "internal")] use crate::{auth::api::request::ApiTokenRequest, client::UserLoginMethod}; @@ -9,10 +9,10 @@ use crate::{ }; pub(crate) async fn renew_token(client: &mut Client) -> Result<()> { - const TOKEN_RENEW_MARGIN: Duration = Duration::from_secs(5 * 60); + const TOKEN_RENEW_MARGIN_SECONDS: i64 = 5 * 60; if let (Some(expires), Some(login_method)) = (&client.token_expires_in, &client.login_method) { - if expires > &(Instant::now() + TOKEN_RENEW_MARGIN) { + if Utc::now().timestamp() < expires - TOKEN_RENEW_MARGIN_SECONDS { return Ok(()); } diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 5133a5c01..6d1503312 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -1,5 +1,4 @@ -use std::time::{Duration, Instant}; - +use chrono::Utc; use reqwest::header::{self}; use uuid::Uuid; @@ -69,7 +68,7 @@ pub(crate) enum ServiceAccountLoginMethod { pub struct Client { token: Option, pub(crate) refresh_token: Option, - pub(crate) token_expires_in: Option, + pub(crate) token_expires_in: Option, pub(crate) login_method: Option, /// Use Client::get_api_configurations() to access this. @@ -190,7 +189,7 @@ impl Client { ) { self.token = Some(token.clone()); self.refresh_token = refresh_token; - self.token_expires_in = Some(Instant::now() + Duration::from_secs(expires_in)); + self.token_expires_in = Some(Utc::now().timestamp() + expires_in as i64); self.login_method = Some(login_method); self.__api_configurations.identity.oauth_access_token = Some(token.clone()); self.__api_configurations.api.oauth_access_token = Some(token); From 327cbb8f85b0b4f406bdf2ba680f1aa8fda8b6a2 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 5 Dec 2023 16:46:58 +0100 Subject: [PATCH 07/10] Fix regex (#396) --- .github/workflows/delete-old-packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/delete-old-packages.yml b/.github/workflows/delete-old-packages.yml index 323a9a0a2..50c3a8fef 100644 --- a/.github/workflows/delete-old-packages.yml +++ b/.github/workflows/delete-old-packages.yml @@ -22,4 +22,4 @@ jobs: min-versions-to-keep: 25 # Ignore versions only containing version numbers - ignore-versions: '^\\d*\\.\\d*\\.\\d*(-SNAPSHOT)?$' + ignore-versions: '^\d*\.\d*\.\d*(-SNAPSHOT)?$' From 7ececc54b7888e0a12f26de93772b460aa97cfd2 Mon Sep 17 00:00:00 2001 From: tangowithfoxtrot <5676771+tangowithfoxtrot@users.noreply.github.com> Date: Wed, 6 Dec 2023 10:03:48 -0800 Subject: [PATCH 08/10] rename Python package to `bitwarden_sdk` (#399) ## Type of change - [ ] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [x] Build/deploy pipeline (DevOps) - [x] Other ## Objective Rename the Python package. This should be merged before #369. ## Code changes - **`languages/python/setup.py`:** Rename package - **`languages/python/bitwarden_sdk`:** Rename directory to match package name - **`.gitignore`:** Changed path to `schemas.py` - **`.github/workflows/generate_schemas.yml`:** Changed path to `schemas.py` ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) --- .github/workflows/generate_schemas.yml | 2 +- .gitignore | 2 +- .../python/{BitwardenClient => bitwarden_sdk}/__init__.py | 0 .../{BitwardenClient => bitwarden_sdk}/bitwarden_client.py | 0 languages/python/example.py | 4 ++-- languages/python/setup.py | 4 ++-- 6 files changed, 6 insertions(+), 6 deletions(-) rename languages/python/{BitwardenClient => bitwarden_sdk}/__init__.py (100%) rename languages/python/{BitwardenClient => bitwarden_sdk}/bitwarden_client.py (100%) diff --git a/.github/workflows/generate_schemas.yml b/.github/workflows/generate_schemas.yml index 1d69b1190..29798032e 100644 --- a/.github/workflows/generate_schemas.yml +++ b/.github/workflows/generate_schemas.yml @@ -54,7 +54,7 @@ jobs: uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: name: schemas.py - path: ${{ github.workspace }}/languages/python/BitwardenClient/schemas.py + path: ${{ github.workspace }}/languages/python/bitwarden_sdk/schemas.py if-no-files-found: error - name: Upload ruby schemas artifact diff --git a/.gitignore b/.gitignore index 18d2d6639..6ddcd3205 100644 --- a/.gitignore +++ b/.gitignore @@ -56,7 +56,7 @@ support/schemas crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts languages/csharp/Bitwarden.Sdk/schemas.cs languages/js_webassembly/bitwarden_client/schemas.ts -languages/python/BitwardenClient/schemas.py +languages/python/bitwarden_sdk/schemas.py languages/cpp/include/schemas.hpp languages/go/schema.go languages/java/src/main/java/com/bitwarden/sdk/schema diff --git a/languages/python/BitwardenClient/__init__.py b/languages/python/bitwarden_sdk/__init__.py similarity index 100% rename from languages/python/BitwardenClient/__init__.py rename to languages/python/bitwarden_sdk/__init__.py diff --git a/languages/python/BitwardenClient/bitwarden_client.py b/languages/python/bitwarden_sdk/bitwarden_client.py similarity index 100% rename from languages/python/BitwardenClient/bitwarden_client.py rename to languages/python/bitwarden_sdk/bitwarden_client.py diff --git a/languages/python/example.py b/languages/python/example.py index 2ee1cb280..b3f2ab006 100644 --- a/languages/python/example.py +++ b/languages/python/example.py @@ -1,8 +1,8 @@ import json import logging import sys -from BitwardenClient.bitwarden_client import BitwardenClient -from BitwardenClient.schemas import client_settings_from_dict, DeviceType +from bitwarden_sdk.bitwarden_client import BitwardenClient +from bitwarden_sdk.schemas import client_settings_from_dict, DeviceType # Create the BitwardenClient, which is used to interact with the SDK client = BitwardenClient(client_settings_from_dict({ diff --git a/languages/python/setup.py b/languages/python/setup.py index 7976ebcba..b243a4fe8 100644 --- a/languages/python/setup.py +++ b/languages/python/setup.py @@ -2,11 +2,11 @@ from setuptools_rust import Binding, RustExtension setup( - name="BitwardenClient", + name="bitwarden_sdk", description="A Bitwarden Client for python", version="0.1", rust_extensions=[RustExtension( "bitwarden_py", path="../../crates/bitwarden-py/Cargo.toml", binding=Binding.PyO3)], - packages=['bitwardenclient'], + packages=['bitwarden_sdk'], zip_safe=False, ) From 030e8424ce75ae0969851b244cde5ea599e932bc Mon Sep 17 00:00:00 2001 From: tangowithfoxtrot <5676771+tangowithfoxtrot@users.noreply.github.com> Date: Wed, 6 Dec 2023 13:20:37 -0800 Subject: [PATCH 09/10] Fix broken schema generation (#403) ## Type of change - [x] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ## Objective Fix broken schema generation workflow from #399. ## Code changes - **./support/scripts/schemas.ts:** Update the Python schemas dir ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) --- support/scripts/schemas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/scripts/schemas.ts b/support/scripts/schemas.ts index 602a68bbb..0ca6f023b 100644 --- a/support/scripts/schemas.ts +++ b/support/scripts/schemas.ts @@ -50,7 +50,7 @@ async function main() { }, }); - writeToFile("./languages/python/BitwardenClient/schemas.py", python.lines); + writeToFile("./languages/python/bitwarden_sdk/schemas.py", python.lines); const ruby = await quicktype({ inputData, From f9f1e5a5681a5bbf9b15584ec3632c258555cf6d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 11:07:23 +0100 Subject: [PATCH 10/10] [deps]: Update typescript to v5.3.3 (#384) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-napi/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-napi/package-lock.json b/crates/bitwarden-napi/package-lock.json index a53303523..df8824a29 100644 --- a/crates/bitwarden-napi/package-lock.json +++ b/crates/bitwarden-napi/package-lock.json @@ -196,9 +196,9 @@ } }, "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc",