Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use bitwarden_api_api::models::{
GroupAccessPolicyResponseModel, ServiceAccountAccessPolicyResponseModel,
UserAccessPolicyResponseModel,
};
use bitwarden_core::key_management::{KeyIds, SymmetricKeyId};
use bitwarden_crypto::{Decryptable, EncString, KeyStoreContext};

use super::types::{
AccessPolicyResponse, GroupAccessPolicyResponse, ServiceAccountAccessPolicyResponse,
UserAccessPolicyResponse,
};

pub(super) fn user_from_api(p: UserAccessPolicyResponseModel) -> Option<UserAccessPolicyResponse> {
Some(UserAccessPolicyResponse {
organization_user_id: p.organization_user_id?,
organization_user_name: p.organization_user_name,
current_user: p.current_user.unwrap_or(false),
policy: api_permissions(p.read, p.write, p.manage)?,
})
}

pub(super) fn group_from_api(
p: GroupAccessPolicyResponseModel,
) -> Option<GroupAccessPolicyResponse> {
Some(GroupAccessPolicyResponse {
group_id: p.group_id?,
group_name: p.group_name,
current_user_in_group: p.current_user_in_group.unwrap_or(false),
policy: api_permissions(p.read, p.write, p.manage)?,
})
}

pub(super) fn service_account_from_api(
p: ServiceAccountAccessPolicyResponseModel,
ctx: &mut KeyStoreContext<KeyIds>,
org_key: SymmetricKeyId,
) -> Option<ServiceAccountAccessPolicyResponse> {
let decrypted_name = p
.service_account_name
.and_then(|n| n.parse::<EncString>().ok()?.decrypt(ctx, org_key).ok());
Some(ServiceAccountAccessPolicyResponse {
service_account_id: p.service_account_id?,
service_account_name: decrypted_name,
policy: api_permissions(p.read, p.write, p.manage)?,
})
}

/// Returns `None` if `manage` is absent from the API response.
///
/// `manage` must not default to `false` — an absent field would silently downgrade a policy
/// that has `manage: true` in the database. Instead we drop the policy from the list so the
/// caller can detect the gap (e.g. a missing field from an older server version).
fn api_permissions(
read: Option<bool>,
write: Option<bool>,
manage: Option<bool>,
) -> Option<AccessPolicyResponse> {
Some(AccessPolicyResponse {
read: read.unwrap_or(false),
write: write.unwrap_or(false),
manage: manage?,
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use bitwarden_core::{client::Client, key_management::SymmetricKeyId};
use bitwarden_crypto::{Decryptable, EncString};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use uuid::Uuid;

use crate::access_policies::types::{
AccessPolicyResponse, GrantedPoliciesResponse, GrantedProjectPolicyResponse,
};

#[derive(Error, Debug)]
pub enum GetGrantedPoliciesError {
#[error("Internal error: {0}")]
InternalError(String),
#[error("Crypto error: {0}")]
CryptoError(String),
}

#[allow(missing_docs)]
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct GetGrantedPoliciesRequest {
pub service_account_id: Uuid,
}

pub async fn get_granted_policies(
client: &Client,
request: &GetGrantedPoliciesRequest,
) -> Result<GrantedPoliciesResponse, GetGrantedPoliciesError> {
let config = client.internal.get_api_configurations().await;

let response = config
.api_client
.access_policies_api()
.get_service_account_granted_policies(request.service_account_id)
.await
.map_err(|e| GetGrantedPoliciesError::InternalError(format!("{e:?}")))?;

let org_id = client
.internal
.get_access_token_organization()
.ok_or_else(|| {
GetGrantedPoliciesError::CryptoError("Not authenticated as a service account".into())
})?;
let key_store = client.internal.get_key_store();
let mut ctx = key_store.context();
let org_key = SymmetricKeyId::Organization(org_id);

let granted_project_policies = response
.granted_project_policies
.unwrap_or_default()
.into_iter()
.filter_map(|details| {
let policy_model = details.access_policy?;
let project_id = policy_model.granted_project_id?;
let decrypted_name = policy_model
.granted_project_name
.and_then(|n| n.parse::<EncString>().ok()?.decrypt(&mut ctx, org_key).ok());
// manage must not default to false — an absent field would silently downgrade a
// policy that has manage:true in the database. Drop the policy instead so the
// caller can detect the gap.
Some(GrantedProjectPolicyResponse {
project_id,
project_name: decrypted_name,
has_permission: details.has_permission.unwrap_or(false),
policy: AccessPolicyResponse {
read: policy_model.read.unwrap_or(false),
write: policy_model.write.unwrap_or(false),
manage: policy_model.manage?,
},
})
})
.collect();

Ok(GrantedPoliciesResponse {
granted_project_policies,
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use bitwarden_core::{client::Client, key_management::SymmetricKeyId};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use uuid::Uuid;

use crate::access_policies::{conversions, types::AccessPoliciesResponse};

#[derive(Error, Debug)]
pub enum GetProjectAccessPoliciesError {
#[error("Internal error: {0}")]
InternalError(String),
#[error("Crypto error: {0}")]
CryptoError(String),
}

#[allow(missing_docs)]
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct GetProjectAccessPoliciesRequest {
pub project_id: Uuid,
}

pub async fn get_project_access_policies(
client: &Client,
request: &GetProjectAccessPoliciesRequest,
) -> Result<AccessPoliciesResponse, GetProjectAccessPoliciesError> {
let config = client.internal.get_api_configurations().await;

let people = config
.api_client
.access_policies_api()
.get_project_people_access_policies(request.project_id)
.await
.map_err(|e| GetProjectAccessPoliciesError::InternalError(format!("{e:?}")))?;

let sa = config
.api_client
.access_policies_api()
.get_project_service_accounts_access_policies(request.project_id)
.await
.map_err(|e| GetProjectAccessPoliciesError::InternalError(format!("{e:?}")))?;

let org_id = client
.internal
.get_access_token_organization()
.ok_or_else(|| {
GetProjectAccessPoliciesError::CryptoError(
"Not authenticated as a service account".into(),
)
})?;
let key_store = client.internal.get_key_store();
let mut ctx = key_store.context();
let org_key = SymmetricKeyId::Organization(org_id);

let user_access_policies = people
.user_access_policies
.unwrap_or_default()
.into_iter()
.filter_map(conversions::user_from_api)
.collect();

let group_access_policies = people
.group_access_policies
.unwrap_or_default()
.into_iter()
.filter_map(conversions::group_from_api)
.collect();

let service_account_access_policies = sa
.service_account_access_policies
.unwrap_or_default()
.into_iter()
.filter_map(|p| conversions::service_account_from_api(p, &mut ctx, org_key))
.collect();

Ok(AccessPoliciesResponse {
user_access_policies,
group_access_policies,
service_account_access_policies,
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use bitwarden_core::{client::Client, key_management::SymmetricKeyId};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use uuid::Uuid;

use crate::access_policies::{conversions, types::AccessPoliciesResponse};

#[derive(Error, Debug)]
pub enum GetSecretAccessPoliciesError {
#[error("Internal error: {0}")]
InternalError(String),
#[error("Crypto error: {0}")]
CryptoError(String),
}

#[allow(missing_docs)]
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct GetSecretAccessPoliciesRequest {
pub secret_id: Uuid,
}

pub async fn get_secret_access_policies(
client: &Client,
request: &GetSecretAccessPoliciesRequest,
) -> Result<AccessPoliciesResponse, GetSecretAccessPoliciesError> {
let config = client.internal.get_api_configurations().await;

let response = config
.api_client
.access_policies_api()
.get_secret_access_policies(request.secret_id)
.await
.map_err(|e| GetSecretAccessPoliciesError::InternalError(format!("{e:?}")))?;

let org_id = client
.internal
.get_access_token_organization()
.ok_or_else(|| {
GetSecretAccessPoliciesError::CryptoError(
"Not authenticated as a service account".into(),
)
})?;
let key_store = client.internal.get_key_store();
let mut ctx = key_store.context();
let org_key = SymmetricKeyId::Organization(org_id);

let user_access_policies = response
.user_access_policies
.unwrap_or_default()
.into_iter()
.filter_map(conversions::user_from_api)
.collect();

let group_access_policies = response
.group_access_policies
.unwrap_or_default()
.into_iter()
.filter_map(conversions::group_from_api)
.collect();

let service_account_access_policies = response
.service_account_access_policies
.unwrap_or_default()
.into_iter()
.filter_map(|p| conversions::service_account_from_api(p, &mut ctx, org_key))
.collect();

Ok(AccessPoliciesResponse {
user_access_policies,
group_access_policies,
service_account_access_policies,
})
}
31 changes: 31 additions & 0 deletions bitwarden_license/bitwarden-sm/src/access_policies/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
mod conversions;
mod get_granted;
mod get_project;
mod get_secret;
mod potential_grantees;
mod put_granted;
mod put_project;
mod put_secret;
pub mod types;

pub use get_granted::{GetGrantedPoliciesError, GetGrantedPoliciesRequest, get_granted_policies};
pub use get_project::{
GetProjectAccessPoliciesError, GetProjectAccessPoliciesRequest, get_project_access_policies,
};
pub use get_secret::{
GetSecretAccessPoliciesError, GetSecretAccessPoliciesRequest, get_secret_access_policies,
};
pub use potential_grantees::{
GetPotentialGranteesError, GetPotentialGranteesRequest, GranteeType, get_potential_grantees,
};
pub use put_granted::{
GrantedProjectEntry, PutGrantedPoliciesError, PutGrantedPoliciesRequest, put_granted_policies,
};
pub use put_project::{
PutProjectAccessPoliciesError, PutProjectAccessPoliciesRequest, put_project_access_policies,
};
pub use types::{
AccessPoliciesResponse, AccessPolicyEntry, AccessPolicyResponse, GrantedPoliciesResponse,
GrantedProjectPolicyResponse, GroupAccessPolicyResponse, PotentialGrantee,
PotentialGranteesResponse, ServiceAccountAccessPolicyResponse, UserAccessPolicyResponse,
};
Loading
Loading