commit 9f86196a9d537ce8295add4c4fe682d5565e63fe
parent 85adcf1ae54b1f48b6f68162494c9d3c332c2163
Author: Jeremy Lin <jeremy.lin@gmail.com>
Date: Sat, 23 Jan 2021 20:50:06 -0800
Add support for the Personal Ownership policy
Upstream refs:
* https://github.com/bitwarden/server/pull/1013
* https://bitwarden.com/help/article/policies/#personal-ownership
Diffstat:
4 files changed, 54 insertions(+), 5 deletions(-)
diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs
@@ -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 {
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<CipherData>, 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
@@ -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
@@ -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
@@ -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)
}