Merge pull request #1326 from jjlin/personal-ownership

Add support for the Personal Ownership policy
This commit is contained in:
Daniel García 2021-01-24 14:09:12 +01:00 committed by GitHub
commit 06888251e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 54 additions and 4 deletions

View file

@ -225,6 +225,11 @@ fn post_ciphers_admin(data: JsonUpcase<ShareCipherData>, headers: Headers, conn:
fn post_ciphers_create(data: JsonUpcase<ShareCipherData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { fn post_ciphers_create(data: JsonUpcase<ShareCipherData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult {
let mut data: ShareCipherData = data.into_inner().data; let mut data: ShareCipherData = data.into_inner().data;
// This check is usually only needed in update_cipher_from_data(), but we
// need it here as well to avoid creating an empty cipher in the call to
// cipher.save() below.
enforce_personal_ownership_policy(&data.Cipher, &headers, &conn)?;
let mut cipher = Cipher::new(data.Cipher.Type, data.Cipher.Name.clone()); let mut cipher = Cipher::new(data.Cipher.Type, data.Cipher.Name.clone());
cipher.user_uuid = Some(headers.user.uuid.clone()); cipher.user_uuid = Some(headers.user.uuid.clone());
cipher.save(&conn)?; cipher.save(&conn)?;
@ -251,6 +256,38 @@ fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn, nt
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn)))
} }
/// Enforces the personal ownership policy on user-owned ciphers, if applicable.
/// A non-owner/admin user belonging to an org with the personal ownership policy
/// enabled isn't allowed to create new user-owned ciphers or modify existing ones
/// (that were created before the policy was applicable to the user). The user is
/// allowed to delete or share such ciphers to an org, however.
///
/// Ref: https://bitwarden.com/help/article/policies/#personal-ownership
fn enforce_personal_ownership_policy(
data: &CipherData,
headers: &Headers,
conn: &DbConn
) -> EmptyResult {
if data.OrganizationId.is_none() {
let user_uuid = &headers.user.uuid;
for policy in OrgPolicy::find_by_user(user_uuid, conn) {
if policy.enabled && policy.has_type(OrgPolicyType::PersonalOwnership) {
let org_uuid = &policy.org_uuid;
match UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn) {
Some(user) =>
if user.atype < UserOrgType::Admin &&
user.has_status(UserOrgStatus::Confirmed) {
err!("Due to an Enterprise Policy, you are restricted \
from saving items to your personal vault.")
},
None => err!("Error looking up user type"),
}
}
}
}
Ok(())
}
pub fn update_cipher_from_data( pub fn update_cipher_from_data(
cipher: &mut Cipher, cipher: &mut Cipher,
data: CipherData, data: CipherData,
@ -260,6 +297,8 @@ pub fn update_cipher_from_data(
nt: &Notify, nt: &Notify,
ut: UpdateType, ut: UpdateType,
) -> EmptyResult { ) -> EmptyResult {
enforce_personal_ownership_policy(&data, headers, conn)?;
// Check that the client isn't updating an existing cipher with stale data. // Check that the client isn't updating an existing cipher with stale data.
if let Some(dt) = data.LastKnownRevisionDate { if let Some(dt) = data.LastKnownRevisionDate {
match NaiveDateTime::parse_from_str(&dt, "%+") { // ISO 8601 format match NaiveDateTime::parse_from_str(&dt, "%+") { // ISO 8601 format

View file

@ -966,7 +966,7 @@ fn list_policies_token(org_id: String, token: String, conn: DbConn) -> JsonResul
fn get_policy(org_id: String, pol_type: i32, _headers: AdminHeaders, conn: DbConn) -> JsonResult { fn get_policy(org_id: String, pol_type: i32, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
let pol_type_enum = match OrgPolicyType::from_i32(pol_type) { let pol_type_enum = match OrgPolicyType::from_i32(pol_type) {
Some(pt) => pt, Some(pt) => pt,
None => err!("Invalid policy type"), None => err!("Invalid or unsupported policy type"),
}; };
let policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn) { let policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn) {
@ -1056,4 +1056,4 @@ fn get_plans(_headers: Headers, _conn: DbConn) -> JsonResult {
], ],
"ContinuationToken": null "ContinuationToken": null
}))) })))
} }

View file

@ -26,6 +26,9 @@ pub enum OrgPolicyType {
TwoFactorAuthentication = 0, TwoFactorAuthentication = 0,
MasterPassword = 1, MasterPassword = 1,
PasswordGenerator = 2, PasswordGenerator = 2,
// SingleOrg = 3, // Not currently supported.
// RequireSso = 4, // Not currently supported.
PersonalOwnership = 5,
} }
/// Local methods /// Local methods
@ -40,6 +43,10 @@ impl OrgPolicy {
} }
} }
pub fn has_type(&self, policy_type: OrgPolicyType) -> bool {
self.atype == policy_type as i32
}
pub fn to_json(&self) -> Value { pub fn to_json(&self) -> Value {
let data_json: Value = serde_json::from_str(&self.data).unwrap_or(Value::Null); let data_json: Value = serde_json::from_str(&self.data).unwrap_or(Value::Null);
json!({ json!({

View file

@ -412,11 +412,15 @@ impl UserOrganization {
Ok(()) Ok(())
} }
pub fn has_status(self, status: UserOrgStatus) -> bool { pub fn has_status(&self, status: UserOrgStatus) -> bool {
self.status == status as i32 self.status == status as i32
} }
pub fn has_full_access(self) -> bool { pub fn has_type(&self, user_type: UserOrgType) -> bool {
self.atype == user_type as i32
}
pub fn has_full_access(&self) -> bool {
(self.access_all || self.atype >= UserOrgType::Admin) && (self.access_all || self.atype >= UserOrgType::Admin) &&
self.has_status(UserOrgStatus::Confirmed) self.has_status(UserOrgStatus::Confirmed)
} }