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

[PM-5692] Extract generators to separate crate #511

Merged
merged 6 commits into from
Jan 19, 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
3 changes: 2 additions & 1 deletion .github/workflows/build-rust-crates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ jobs:

package:
- bitwarden
- bitwarden-crypto
- bitwarden-api-api
- bitwarden-api-identity
- bitwarden-crypto
- bitwarden-generators

steps:
- name: Checkout
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/publish-rust-crates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ on:
required: true
default: true
type: boolean
publish_bitwarden-generators:
description: "Publish bitwarden-generators crate"
required: true
default: true
type: boolean

defaults:
run:
Expand Down Expand Up @@ -67,6 +72,7 @@ jobs:
PUBLISH_BITWARDEN_API_API: ${{ github.event.inputs.publish_bitwarden-api-api }}
PUBLISH_BITWARDEN_API_IDENTITY: ${{ github.event.inputs.publish_bitwarden-api-identity }}
PUBLISH_BITWARDEN_CRYPTO: ${{ github.event.inputs.publish_bitwarden-crypto }}
PUBLISH_BITWARDEN_GENERATORS: ${{ github.event.inputs.publish_bitwarden-generators }}
run: |
if [[ "$PUBLISH_BITWARDEN" == "false" ]] && [[ "$PUBLISH_BITWARDEN_API_API" == "false" ]] && [[ "$PUBLISH_BITWARDEN_API_IDENTITY" == "false" ]]; then
echo "==================================="
Expand Down Expand Up @@ -98,6 +104,11 @@ jobs:
PACKAGES_LIST="$PACKAGES_LIST bitwarden-crypto"
fi

if [[ "$PUBLISH_BITWARDEN_GENERATORS" == "true" ]]; then
PACKAGES_COMMAND="$PACKAGES_COMMAND -p bitwarden-generators"
PACKAGES_LIST="$PACKAGES_LIST bitwarden-generators"
fi

echo "Packages command: " $PACKAGES_COMMAND
echo "Packages list: " $PACKAGES_LIST

Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/version-bump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ on:
- bitwarden-api-api
- bitwarden-api-identity
- bitwarden-crypto
- bitwarden-generators
- bitwarden-json
- cli
- napi
Expand Down Expand Up @@ -123,6 +124,12 @@ jobs:
if: ${{ inputs.project == 'bitwarden-crypto' }}
run: cargo-set-version set-version -p bitwarden-crypto ${{ inputs.version_number }}

### bitwarden-generators

- name: Bump bitwarden-generators crate Version
if: ${{ inputs.project == 'bitwarden-generators' }}
run: cargo-set-version set-version -p bitwarden-generators ${{ inputs.version_number }}

### cli

- name: Bump cli Version
Expand Down
18 changes: 18 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions crates/bitwarden-generators/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "bitwarden-generators"
version = "0.1.0"
edition = "2021"


[features]
mobile = ["uniffi"] # Mobile-specific features

[dependencies]
bitwarden-crypto = { path = "../bitwarden-crypto", version = "=0.1.0" }
rand = ">=0.8.5, <0.9"
reqwest = { version = ">=0.11, <0.12", features = [
"json",
], default-features = false }
schemars = { version = ">=0.8.9, <0.9", features = ["uuid1", "chrono"] }
serde = { version = ">=1.0, <2.0", features = ["derive"] }
serde_json = ">=1.0.96, <2.0"
thiserror = ">=1.0.40, <2.0"
uniffi = { version = "=0.25.2", optional = true }

[dev-dependencies]
rand_chacha = "0.3.1"
tokio = { version = "1.35.1", features = ["rt", "macros"] }
wiremock = "0.5.22"
13 changes: 13 additions & 0 deletions crates/bitwarden-generators/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use thiserror::Error;

use crate::{passphrase::PassphraseError, password::PasswordError, username::UsernameError};

#[derive(Debug, Error)]

Check warning on line 5 in crates/bitwarden-generators/src/error.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/error.rs#L5

Added line #L5 was not covered by tests
pub enum GeneratorError {
#[error(transparent)]
PassphraseErrors(#[from] PassphraseError),
#[error(transparent)]
PasswordError(#[from] PasswordError),
#[error(transparent)]
UsernameError(#[from] UsernameError),
}
13 changes: 13 additions & 0 deletions crates/bitwarden-generators/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
mod passphrase;
pub use passphrase::{passphrase, PassphraseGeneratorRequest};
mod error;
mod util;
pub use error::GeneratorError;
mod password;
pub use password::{password, PasswordGeneratorRequest};
mod username;
pub use username::{username, ForwarderServiceType, UsernameGeneratorRequest};
mod username_forwarders;

#[cfg(feature = "mobile")]
uniffi::setup_scaffolding!();

Check warning on line 13 in crates/bitwarden-generators/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/lib.rs#L13

Added line #L13 was not covered by tests
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@
use rand::{seq::SliceRandom, Rng, RngCore};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::{error::Result, util::capitalize_first_letter};
use crate::{error::GeneratorError, util::capitalize_first_letter};

#[derive(Debug, Error)]

Check warning on line 9 in crates/bitwarden-generators/src/passphrase.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/passphrase.rs#L9

Added line #L9 was not covered by tests
pub enum PassphraseError {
#[error("'num_words' must be between {} and {}", minimum, maximum)]
InvalidNumWords { minimum: u8, maximum: u8 },
#[error("'word_separator' cannot be empty")]
EmptyWordSeparator,
}

/// Passphrase generator request options.
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
Expand Down Expand Up @@ -46,16 +55,19 @@

impl PassphraseGeneratorRequest {
/// Validates the request and returns an immutable struct with valid options to use with the passphrase generator.
fn validate_options(self) -> Result<ValidPassphraseGeneratorOptions> {
fn validate_options(self) -> Result<ValidPassphraseGeneratorOptions, PassphraseError> {
// TODO: Add password generator policy checks

if !(MINIMUM_PASSPHRASE_NUM_WORDS..=MAXIMUM_PASSPHRASE_NUM_WORDS).contains(&self.num_words)
{
return Err(format!("'num_words' must be between {MINIMUM_PASSPHRASE_NUM_WORDS} and {MAXIMUM_PASSPHRASE_NUM_WORDS}").into());
return Err(PassphraseError::InvalidNumWords {
minimum: MINIMUM_PASSPHRASE_NUM_WORDS,
maximum: MAXIMUM_PASSPHRASE_NUM_WORDS,
});

Check warning on line 66 in crates/bitwarden-generators/src/passphrase.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/passphrase.rs#L63-L66

Added lines #L63 - L66 were not covered by tests
}

if self.word_separator.chars().next().is_none() {
return Err("'word_separator' cannot be empty".into());
return Err(PassphraseError::EmptyWordSeparator);

Check warning on line 70 in crates/bitwarden-generators/src/passphrase.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/passphrase.rs#L70

Added line #L70 was not covered by tests
};

Ok(ValidPassphraseGeneratorOptions {
Expand All @@ -67,13 +79,8 @@
}
}

/// Implementation of the random passphrase generator. This is not accessible to the public API.
/// See [`ClientGenerator::passphrase`](crate::ClientGenerator::passphrase) for the API function.
///
/// # Arguments:
/// * `options`: Valid parameters used to generate the passphrase. To create it, use
/// [`PassphraseGeneratorRequest::validate_options`](PassphraseGeneratorRequest::validate_options).
pub(super) fn passphrase(request: PassphraseGeneratorRequest) -> Result<String> {
/// Implementation of the random passphrase generator.
pub fn passphrase(request: PassphraseGeneratorRequest) -> Result<String, GeneratorError> {

Check warning on line 83 in crates/bitwarden-generators/src/passphrase.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/passphrase.rs#L83

Added line #L83 was not covered by tests
let options = request.validate_options()?;
Ok(passphrase_with_rng(rand::thread_rng(), options))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@
use rand::{distributions::Distribution, seq::SliceRandom, RngCore};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::error::Result;
use crate::GeneratorError;

#[derive(Debug, Error)]

Check warning on line 10 in crates/bitwarden-generators/src/password.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/password.rs#L10

Added line #L10 was not covered by tests
pub enum PasswordError {
#[error("No character set enabled")]
NoCharacterSetEnabled,
#[error("Invalid password length")]
InvalidLength,
}

/// Password generator request options.
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
Expand Down Expand Up @@ -32,7 +41,7 @@
/// When set, the value must be between 1 and 9. This value is ignored is lowercase is false
pub min_lowercase: Option<u8>,
/// 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
/// When set, the value must be between 1 and 9. This value is ignored is uppercase is false
pub min_uppercase: Option<u8>,
/// 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
Expand Down Expand Up @@ -128,16 +137,16 @@

impl PasswordGeneratorRequest {
/// Validates the request and returns an immutable struct with valid options to use with the password generator.
fn validate_options(self) -> Result<PasswordGeneratorOptions> {
fn validate_options(self) -> Result<PasswordGeneratorOptions, PasswordError> {
// 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("At least one character set must be enabled".into());
return Err(PasswordError::NoCharacterSetEnabled);

Check warning on line 145 in crates/bitwarden-generators/src/password.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/password.rs#L145

Added line #L145 was not covered by tests
}

if self.length < 4 {
return Err("A password must be at least 4 characters long".into());
return Err(PasswordError::InvalidLength);

Check warning on line 149 in crates/bitwarden-generators/src/password.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/password.rs#L149

Added line #L149 was not covered by tests
}

// Make sure the minimum values are zero when the character
Expand All @@ -159,7 +168,7 @@
// 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("Password length can't be less than the sum of the minimums".into());
return Err(PasswordError::InvalidLength);

Check warning on line 171 in crates/bitwarden-generators/src/password.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/password.rs#L171

Added line #L171 was not covered by tests
}

let lower = (
Expand Down Expand Up @@ -208,9 +217,8 @@
}
}

/// 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<String> {
/// Implementation of the random password generator.
pub fn password(input: PasswordGeneratorRequest) -> Result<String, GeneratorError> {

Check warning on line 221 in crates/bitwarden-generators/src/password.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/password.rs#L221

Added line #L221 was not covered by tests
let options = input.validate_options()?;
Ok(password_with_rng(rand::thread_rng(), options))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
use bitwarden_crypto::EFF_LONG_WORD_LIST;
use rand::{distributions::Distribution, seq::SliceRandom, Rng, RngCore};
use reqwest::StatusCode;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::{error::Result, util::capitalize_first_letter};
use crate::{util::capitalize_first_letter, GeneratorError};

#[derive(Debug, Error)]
pub enum UsernameError {
#[error("Invalid API Key")]
InvalidApiKey,
#[error("Unknown error")]
Unknown,

#[error("Received error message from server: [{}] {}", .status, .message)]
ResponseContent { status: StatusCode, message: String },

#[error(transparent)]
Reqwest(#[from] reqwest::Error),
}

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
Expand Down Expand Up @@ -85,10 +101,14 @@
impl ForwarderServiceType {
// Generate a username using the specified email forwarding service
// This requires an HTTP client to be passed in, as the service will need to make API calls
pub async fn generate(self, http: &reqwest::Client, website: Option<String>) -> Result<String> {
pub async fn generate(
self,
http: &reqwest::Client,
website: Option<String>,
) -> Result<String, UsernameError> {

Check warning on line 108 in crates/bitwarden-generators/src/username.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/username.rs#L104-L108

Added lines #L104 - L108 were not covered by tests
use ForwarderServiceType::*;

use crate::tool::generators::username_forwarders::*;
use crate::username_forwarders::*;

Check warning on line 111 in crates/bitwarden-generators/src/username.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/username.rs#L111

Added line #L111 was not covered by tests

match self {
AddyIo {
Expand All @@ -107,14 +127,14 @@
}
}

/// Implementation of the username generator. This is not accessible to the public API.
/// See [`ClientGenerator::username`](crate::ClientGenerator::username) for the API function.
/// Implementation of the username generator.
///
/// Note: The HTTP client is passed in as a required parameter for convenience,
/// as some username generators require making API calls.
pub(super) async fn username(
pub async fn username(

Check warning on line 134 in crates/bitwarden-generators/src/username.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/username.rs#L134

Added line #L134 was not covered by tests
input: UsernameGeneratorRequest,
http: &reqwest::Client,
) -> Result<String> {
) -> Result<String, GeneratorError> {

Check warning on line 137 in crates/bitwarden-generators/src/username.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/username.rs#L137

Added line #L137 was not covered by tests
use rand::thread_rng;
use UsernameGeneratorRequest::*;
match input {
Expand All @@ -124,7 +144,7 @@
} => Ok(username_word(&mut thread_rng(), capitalize, include_number)),
Subaddress { r#type, email } => Ok(username_subaddress(&mut thread_rng(), r#type, email)),
Catchall { r#type, domain } => Ok(username_catchall(&mut thread_rng(), r#type, domain)),
Forwarded { service, website } => service.generate(http, website).await,
Forwarded { service, website } => Ok(service.generate(http, website).await?),

Check warning on line 147 in crates/bitwarden-generators/src/username.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-generators/src/username.rs#L147

Added line #L147 was not covered by tests
}
}

Expand Down
Loading
Loading