From 9f86196a9d537ce8295add4c4fe682d5565e63fe Mon Sep 17 00:00:00 2001 From: Jeremy Lin Date: Sat, 23 Jan 2021 20:50:06 -0800 Subject: [PATCH] Add support for the Personal Ownership policy Upstream refs: * https://github.com/bitwarden/server/pull/1013 * https://bitwarden.com/help/article/policies/#personal-ownership --- src/api/core/ciphers.rs | 39 +++++++++++++++++++++++++++++++++++ src/api/core/organizations.rs | 4 ++-- src/db/models/org_policy.rs | 7 +++++++ src/db/models/organization.rs | 8 +++++-- 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 0136ca79..1587bf79 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -225,6 +225,11 @@ fn post_ciphers_admin(data: JsonUpcase, headers: Headers, conn: fn post_ciphers_create(data: JsonUpcase, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { 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()); cipher.user_uuid = Some(headers.user.uuid.clone()); cipher.save(&conn)?; @@ -251,6 +256,38 @@ fn post_ciphers(data: JsonUpcase, headers: Headers, conn: DbConn, nt 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( cipher: &mut Cipher, data: CipherData, @@ -260,6 +297,8 @@ pub fn update_cipher_from_data( nt: &Notify, ut: UpdateType, ) -> EmptyResult { + enforce_personal_ownership_policy(&data, headers, conn)?; + // Check that the client isn't updating an existing cipher with stale data. if let Some(dt) = data.LastKnownRevisionDate { match NaiveDateTime::parse_from_str(&dt, "%+") { // ISO 8601 format diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 9d3fa7c6..c6840eda 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -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 { let pol_type_enum = match OrgPolicyType::from_i32(pol_type) { 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) { @@ -1056,4 +1056,4 @@ fn get_plans(_headers: Headers, _conn: DbConn) -> JsonResult { ], "ContinuationToken": null }))) -} \ No newline at end of file +} diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs index 98b01a37..679b7e57 100644 --- a/src/db/models/org_policy.rs +++ b/src/db/models/org_policy.rs @@ -26,6 +26,9 @@ pub enum OrgPolicyType { TwoFactorAuthentication = 0, MasterPassword = 1, PasswordGenerator = 2, + // SingleOrg = 3, // Not currently supported. + // RequireSso = 4, // Not currently supported. + PersonalOwnership = 5, } /// 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 { let data_json: Value = serde_json::from_str(&self.data).unwrap_or(Value::Null); json!({ diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index 5fb4f36e..457330cf 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -412,11 +412,15 @@ impl UserOrganization { Ok(()) } - pub fn has_status(self, status: UserOrgStatus) -> bool { + pub fn has_status(&self, status: UserOrgStatus) -> bool { 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.has_status(UserOrgStatus::Confirmed) }