Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
15 changes: 15 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ keywords = ["bitwarden"]
bitwarden = { path = "crates/bitwarden", version = "=1.0.0" }
bitwarden-api-api = { path = "crates/bitwarden-api-api", version = "=2.0.0" }
bitwarden-api-identity = { path = "crates/bitwarden-api-identity", version = "=2.0.0" }
bitwarden-api-key-connector = { path = "crates/bitwarden-api-key-connector", version = "=2.0.0" }
bitwarden-auth = { path = "crates/bitwarden-auth", version = "=2.0.0" }
bitwarden-cli = { path = "crates/bitwarden-cli", version = "=2.0.0" }
bitwarden-collections = { path = "crates/bitwarden-collections", version = "=2.0.0" }
Expand Down
24 changes: 24 additions & 0 deletions crates/bitwarden-api-key-connector/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "bitwarden-api-key-connector"
description = "Api bindings for the Bitwarden Key Connector API."
categories = ["api-bindings"]

version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
homepage.workspace = true
repository.workspace = true
license-file.workspace = true
keywords.workspace = true

[dependencies]
async-trait = { workspace = true }
mockall = { version = ">=0.13.1, <0.15" }
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true }

[dev-dependencies]
wiremock = { workspace = true }
24 changes: 24 additions & 0 deletions crates/bitwarden-api-key-connector/src/apis/configuration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#[derive(Debug, Clone)]
pub struct Configuration {
pub base_path: String,
pub user_agent: Option<String>,
pub client: reqwest::Client,
pub oauth_access_token: Option<String>,
}

impl Configuration {
pub fn new() -> Configuration {
Configuration::default()
}
}

impl Default for Configuration {
fn default() -> Self {
Configuration {
base_path: "https://key-connector.bitwarden.com".to_owned(),
user_agent: Some("api/key-connector/rust".to_owned()),
client: reqwest::Client::new(),
oauth_access_token: None,
}
}
}
75 changes: 75 additions & 0 deletions crates/bitwarden-api-key-connector/src/apis/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use std::sync::Arc;

use crate::apis::configuration::Configuration;

pub mod configuration;
pub mod user_keys_api;

#[derive(Debug, Clone)]
pub struct ResponseContent {
pub status: reqwest::StatusCode,
pub content: String,
}

#[allow(missing_docs)]
#[derive(Debug)]
pub enum Error {
Reqwest(reqwest::Error),
Serde(serde_json::Error),
Io(std::io::Error),
ResponseError(ResponseContent),
}

impl From<reqwest::Error> for Error {
fn from(e: reqwest::Error) -> Self {
Error::Reqwest(e)
}
}

impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Error::Serde(e)
}
}

impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::Io(e)
}
}

pub enum ApiClient {
Real(ApiClientReal),
Mock(ApiClientMock),
}

pub struct ApiClientReal {
user_keys_api: user_keys_api::UserKeysApiClient,
}

pub struct ApiClientMock {
pub user_keys_api: user_keys_api::MockUserKeysApi,
}

impl ApiClient {
pub fn new(configuration: &Arc<Configuration>) -> Self {
Self::Real(ApiClientReal {
user_keys_api: user_keys_api::UserKeysApiClient::new(configuration.clone()),
})
}

pub fn new_mocked(func: impl FnOnce(&mut ApiClientMock)) -> Self {
let mut mock = ApiClientMock {
user_keys_api: user_keys_api::MockUserKeysApi::new(),
};
func(&mut mock);
Self::Mock(mock)
}

pub fn user_keys_api(&self) -> &dyn user_keys_api::UserKeysApi {
match self {
ApiClient::Real(real) => &real.user_keys_api,
ApiClient::Mock(mock) => &mock.user_keys_api,
}
}
}
196 changes: 196 additions & 0 deletions crates/bitwarden-api-key-connector/src/apis/user_keys_api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use std::sync::Arc;

use async_trait::async_trait;
use configuration::Configuration;
use mockall::automock;
use reqwest::Method;
use serde::Serialize;

use crate::{
apis::{Error, configuration},
models::{
user_key_request_model::UserKeyKeyRequestModel,
user_key_response_model::UserKeyResponseModel,
},
};

#[automock]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
pub trait UserKeysApi: Send + Sync {
/// GET /user-keys
async fn get_user_key(&self) -> Result<UserKeyResponseModel, Error>;

/// POST /user-keys
async fn post_user_key(&self, request_model: UserKeyKeyRequestModel) -> Result<(), Error>;

/// PUT /user-keys
async fn put_user_key(&self, request_model: UserKeyKeyRequestModel) -> Result<(), Error>;
}

pub struct UserKeysApiClient {
configuration: Arc<Configuration>,
}

impl UserKeysApiClient {
pub fn new(configuration: Arc<Configuration>) -> Self {
Self { configuration }
}
}

#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl UserKeysApi for UserKeysApiClient {
async fn get_user_key(&self) -> Result<UserKeyResponseModel, Error> {
let response = request(&self.configuration, Method::GET, None::<()>).await?;

let body = response.text().await?;
let response_model = serde_json::from_str::<UserKeyResponseModel>(&body)?;
Ok(response_model)
}

async fn post_user_key(&self, request_model: UserKeyKeyRequestModel) -> Result<(), Error> {
request(&self.configuration, Method::POST, Some(request_model)).await?;

Ok(())
}

async fn put_user_key(&self, request_model: UserKeyKeyRequestModel) -> Result<(), Error> {
request(&self.configuration, Method::PUT, Some(request_model)).await?;

Ok(())
}
}

async fn request(
configuration: &Arc<Configuration>,
method: Method,
body: Option<impl Serialize>,
) -> Result<reqwest::Response, Error> {
let url = format!("{}/user-keys", configuration.base_path);

let mut request = configuration
.client
.request(method, url)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.header(reqwest::header::ACCEPT, "application/json");

if let Some(ref user_agent) = configuration.user_agent {
request = request.header(reqwest::header::USER_AGENT, user_agent.clone());
}
if let Some(ref access_token) = configuration.oauth_access_token {
request = request.bearer_auth(access_token.clone());
}
if let Some(ref body) = body {
request =
request.body(serde_json::to_string(&body).expect("Serialize should be infallible"))
}

let response = request.send().await?;

Ok(response.error_for_status()?)
}

#[cfg(test)]
mod tests {
use std::sync::Arc;

use wiremock::{
Mock, MockServer, ResponseTemplate,
matchers::{header, method, path},
};

use crate::{
apis::{
configuration::Configuration,
user_keys_api::{UserKeysApi, UserKeysApiClient},
},
models::user_key_request_model::UserKeyKeyRequestModel,
};

const ACCESS_TOKEN: &str = "test_access_token";
const KEY_CONNECTOR_KEY: &str = "test_key_connector_key";

async fn setup_mock_server_with_auth() -> (MockServer, Configuration) {
let server = MockServer::start().await;

let configuration = Configuration {
base_path: format!("http://{}", server.address()),
user_agent: Some("Bitwarden Rust-SDK [TEST]".to_string()),
client: reqwest::Client::new(),
oauth_access_token: Some(ACCESS_TOKEN.to_string()),
};

(server, configuration)
}

#[tokio::test]
async fn test_get() {
let (server, configuration) = setup_mock_server_with_auth().await;

Mock::given(method("GET"))
.and(path("/user-keys"))
.and(header("authorization", format!("Bearer {ACCESS_TOKEN}")))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"key": KEY_CONNECTOR_KEY.to_string()
})))
.expect(1)
.mount(&server)
.await;

let api_client = UserKeysApiClient::new(Arc::new(configuration));

let result = api_client.get_user_key().await;

assert!(result.is_ok());
assert_eq!(KEY_CONNECTOR_KEY, result.unwrap().key);
}

#[tokio::test]
async fn test_post() {
let (server, configuration) = setup_mock_server_with_auth().await;

Mock::given(method("POST"))
.and(path("/user-keys"))
.and(header("authorization", format!("Bearer {ACCESS_TOKEN}")))
.and(header("content-type", "application/json"))
.respond_with(ResponseTemplate::new(200))
.expect(1)
.mount(&server)
.await;

let request_model = UserKeyKeyRequestModel {
key: KEY_CONNECTOR_KEY.to_string(),
};

let api_client = UserKeysApiClient::new(Arc::new(configuration));

let result = api_client.post_user_key(request_model).await;

assert!(result.is_ok());
}

#[tokio::test]
async fn test_put() {
let (server, configuration) = setup_mock_server_with_auth().await;

Mock::given(method("PUT"))
.and(path("/user-keys"))
.and(header("authorization", format!("Bearer {ACCESS_TOKEN}")))
.and(header("content-type", "application/json"))
.respond_with(ResponseTemplate::new(200))
.expect(1)
.mount(&server)
.await;

let request_model = UserKeyKeyRequestModel {
key: KEY_CONNECTOR_KEY.to_string(),
};

let api_client = UserKeysApiClient::new(Arc::new(configuration));

let result = api_client.put_user_key(request_model).await;

assert!(result.is_ok());
}
}
4 changes: 4 additions & 0 deletions crates/bitwarden-api-key-connector/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//! Client for interacting with the Key Connector API.
pub mod apis;
pub mod models;
2 changes: 2 additions & 0 deletions crates/bitwarden-api-key-connector/src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod user_key_request_model;
pub mod user_key_response_model;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use serde::{Deserialize, Serialize};

#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct UserKeyKeyRequestModel {
#[serde(rename = "key", alias = "Key")]
pub key: String,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use serde::{Deserialize, Serialize};

#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct UserKeyResponseModel {
#[serde(rename = "key", alias = "Key")]
pub key: String,
}
1 change: 1 addition & 0 deletions crates/bitwarden-auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ uniffi = ["bitwarden-core/uniffi", "dep:uniffi"] # Uniffi bindings
# Note: dependencies must be alphabetized to pass the cargo sort check in the CI pipeline.
[dependencies]
bitwarden-api-api = { workspace = true }
bitwarden-api-key-connector = { workspace = true }
bitwarden-core = { workspace = true, features = ["internal"] }
bitwarden-crypto = { workspace = true }
bitwarden-encoding = { workspace = true }
Expand Down
Loading
Loading