organization.rs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. use num_traits::FromPrimitive;
  2. use serde_json::Value;
  3. use std::cmp::Ordering;
  4. use super::{CollectionUser, OrgPolicy, User};
  5. db_object! {
  6. #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
  7. #[table_name = "organizations"]
  8. #[primary_key(uuid)]
  9. pub struct Organization {
  10. pub uuid: String,
  11. pub name: String,
  12. pub billing_email: String,
  13. pub private_key: Option<String>,
  14. pub public_key: Option<String>,
  15. }
  16. #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
  17. #[table_name = "users_organizations"]
  18. #[primary_key(uuid)]
  19. pub struct UserOrganization {
  20. pub uuid: String,
  21. pub user_uuid: String,
  22. pub org_uuid: String,
  23. pub access_all: bool,
  24. pub akey: String,
  25. pub status: i32,
  26. pub atype: i32,
  27. }
  28. }
  29. pub enum UserOrgStatus {
  30. Invited = 0,
  31. Accepted = 1,
  32. Confirmed = 2,
  33. }
  34. #[derive(Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)]
  35. pub enum UserOrgType {
  36. Owner = 0,
  37. Admin = 1,
  38. User = 2,
  39. Manager = 3,
  40. }
  41. impl UserOrgType {
  42. pub fn from_str(s: &str) -> Option<Self> {
  43. match s {
  44. "0" | "Owner" => Some(UserOrgType::Owner),
  45. "1" | "Admin" => Some(UserOrgType::Admin),
  46. "2" | "User" => Some(UserOrgType::User),
  47. "3" | "Manager" => Some(UserOrgType::Manager),
  48. _ => None,
  49. }
  50. }
  51. }
  52. impl Ord for UserOrgType {
  53. fn cmp(&self, other: &UserOrgType) -> Ordering {
  54. // For easy comparison, map each variant to an access level (where 0 is lowest).
  55. static ACCESS_LEVEL: [i32; 4] = [
  56. 3, // Owner
  57. 2, // Admin
  58. 0, // User
  59. 1, // Manager
  60. ];
  61. ACCESS_LEVEL[*self as usize].cmp(&ACCESS_LEVEL[*other as usize])
  62. }
  63. }
  64. impl PartialOrd for UserOrgType {
  65. fn partial_cmp(&self, other: &UserOrgType) -> Option<Ordering> {
  66. Some(self.cmp(other))
  67. }
  68. }
  69. impl PartialEq<i32> for UserOrgType {
  70. fn eq(&self, other: &i32) -> bool {
  71. *other == *self as i32
  72. }
  73. }
  74. impl PartialOrd<i32> for UserOrgType {
  75. fn partial_cmp(&self, other: &i32) -> Option<Ordering> {
  76. if let Some(other) = Self::from_i32(*other) {
  77. return Some(self.cmp(&other));
  78. }
  79. None
  80. }
  81. fn gt(&self, other: &i32) -> bool {
  82. matches!(self.partial_cmp(other), Some(Ordering::Greater))
  83. }
  84. fn ge(&self, other: &i32) -> bool {
  85. matches!(self.partial_cmp(other), Some(Ordering::Greater) | Some(Ordering::Equal))
  86. }
  87. }
  88. impl PartialEq<UserOrgType> for i32 {
  89. fn eq(&self, other: &UserOrgType) -> bool {
  90. *self == *other as i32
  91. }
  92. }
  93. impl PartialOrd<UserOrgType> for i32 {
  94. fn partial_cmp(&self, other: &UserOrgType) -> Option<Ordering> {
  95. if let Some(self_type) = UserOrgType::from_i32(*self) {
  96. return Some(self_type.cmp(other));
  97. }
  98. None
  99. }
  100. fn lt(&self, other: &UserOrgType) -> bool {
  101. matches!(self.partial_cmp(other), Some(Ordering::Less) | None)
  102. }
  103. fn le(&self, other: &UserOrgType) -> bool {
  104. matches!(self.partial_cmp(other), Some(Ordering::Less) | Some(Ordering::Equal) | None)
  105. }
  106. }
  107. /// Local methods
  108. impl Organization {
  109. pub fn new(name: String, billing_email: String, private_key: Option<String>, public_key: Option<String>) -> Self {
  110. Self {
  111. uuid: crate::util::get_uuid(),
  112. name,
  113. billing_email,
  114. private_key,
  115. public_key,
  116. }
  117. }
  118. pub fn to_json(&self) -> Value {
  119. json!({
  120. "Id": self.uuid,
  121. "Identifier": null, // not supported by us
  122. "Name": self.name,
  123. "Seats": 10, // The value doesn't matter, we don't check server-side
  124. "MaxCollections": 10, // The value doesn't matter, we don't check server-side
  125. "MaxStorageGb": 10, // The value doesn't matter, we don't check server-side
  126. "Use2fa": true,
  127. "UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
  128. "UseEvents": false, // not supported by us
  129. "UseGroups": false, // not supported by us
  130. "UseTotp": true,
  131. "UsePolicies": true,
  132. "UseSso": false, // We do not support SSO
  133. "SelfHost": true,
  134. "UseApi": false, // not supported by us
  135. "HasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(),
  136. "ResetPasswordEnrolled": false, // not supported by us
  137. "BusinessName": null,
  138. "BusinessAddress1": null,
  139. "BusinessAddress2": null,
  140. "BusinessAddress3": null,
  141. "BusinessCountry": null,
  142. "BusinessTaxNumber": null,
  143. "BillingEmail": self.billing_email,
  144. "Plan": "TeamsAnnually",
  145. "PlanType": 5, // TeamsAnnually plan
  146. "UsersGetPremium": true,
  147. "Object": "organization",
  148. })
  149. }
  150. }
  151. impl UserOrganization {
  152. pub fn new(user_uuid: String, org_uuid: String) -> Self {
  153. Self {
  154. uuid: crate::util::get_uuid(),
  155. user_uuid,
  156. org_uuid,
  157. access_all: false,
  158. akey: String::new(),
  159. status: UserOrgStatus::Accepted as i32,
  160. atype: UserOrgType::User as i32,
  161. }
  162. }
  163. }
  164. use crate::db::DbConn;
  165. use crate::api::EmptyResult;
  166. use crate::error::MapResult;
  167. /// Database methods
  168. impl Organization {
  169. pub fn save(&self, conn: &DbConn) -> EmptyResult {
  170. UserOrganization::find_by_org(&self.uuid, conn).iter().for_each(|user_org| {
  171. User::update_uuid_revision(&user_org.user_uuid, conn);
  172. });
  173. db_run! { conn:
  174. sqlite, mysql {
  175. match diesel::replace_into(organizations::table)
  176. .values(OrganizationDb::to_db(self))
  177. .execute(conn)
  178. {
  179. Ok(_) => Ok(()),
  180. // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
  181. Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
  182. diesel::update(organizations::table)
  183. .filter(organizations::uuid.eq(&self.uuid))
  184. .set(OrganizationDb::to_db(self))
  185. .execute(conn)
  186. .map_res("Error saving organization")
  187. }
  188. Err(e) => Err(e.into()),
  189. }.map_res("Error saving organization")
  190. }
  191. postgresql {
  192. let value = OrganizationDb::to_db(self);
  193. diesel::insert_into(organizations::table)
  194. .values(&value)
  195. .on_conflict(organizations::uuid)
  196. .do_update()
  197. .set(&value)
  198. .execute(conn)
  199. .map_res("Error saving organization")
  200. }
  201. }
  202. }
  203. pub fn delete(self, conn: &DbConn) -> EmptyResult {
  204. use super::{Cipher, Collection};
  205. Cipher::delete_all_by_organization(&self.uuid, conn)?;
  206. Collection::delete_all_by_organization(&self.uuid, conn)?;
  207. UserOrganization::delete_all_by_organization(&self.uuid, conn)?;
  208. OrgPolicy::delete_all_by_organization(&self.uuid, conn)?;
  209. db_run! { conn: {
  210. diesel::delete(organizations::table.filter(organizations::uuid.eq(self.uuid)))
  211. .execute(conn)
  212. .map_res("Error saving organization")
  213. }}
  214. }
  215. pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
  216. db_run! { conn: {
  217. organizations::table
  218. .filter(organizations::uuid.eq(uuid))
  219. .first::<OrganizationDb>(conn)
  220. .ok().from_db()
  221. }}
  222. }
  223. pub fn get_all(conn: &DbConn) -> Vec<Self> {
  224. db_run! { conn: {
  225. organizations::table.load::<OrganizationDb>(conn).expect("Error loading organizations").from_db()
  226. }}
  227. }
  228. }
  229. impl UserOrganization {
  230. pub fn to_json(&self, conn: &DbConn) -> Value {
  231. let org = Organization::find_by_uuid(&self.org_uuid, conn).unwrap();
  232. json!({
  233. "Id": self.org_uuid,
  234. "Identifier": null, // not supported by us
  235. "Name": org.name,
  236. "Seats": 10, // The value doesn't matter, we don't check server-side
  237. "MaxCollections": 10, // The value doesn't matter, we don't check server-side
  238. "UsersGetPremium": true,
  239. "Use2fa": true,
  240. "UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
  241. "UseEvents": false, // not supported by us
  242. "UseGroups": false, // not supported by us
  243. "UseTotp": true,
  244. "UsePolicies": true,
  245. "UseApi": false, // not supported by us
  246. "SelfHost": true,
  247. "HasPublicAndPrivateKeys": org.private_key.is_some() && org.public_key.is_some(),
  248. "ResetPasswordEnrolled": false, // not supported by us
  249. "SsoBound": false, // We do not support SSO
  250. "UseSso": false, // We do not support SSO
  251. // TODO: Add support for Business Portal
  252. // Upstream is moving Policies and SSO management outside of the web-vault to /portal
  253. // For now they still have that code also in the web-vault, but they will remove it at some point.
  254. // https://github.com/bitwarden/server/tree/master/bitwarden_license/src/
  255. "UseBusinessPortal": false, // Disable BusinessPortal Button
  256. // TODO: Add support for Custom User Roles
  257. // See: https://bitwarden.com/help/article/user-types-access-control/#custom-role
  258. // "Permissions": {
  259. // "AccessBusinessPortal": false,
  260. // "AccessEventLogs": false,
  261. // "AccessImportExport": false,
  262. // "AccessReports": false,
  263. // "ManageAllCollections": false,
  264. // "ManageAssignedCollections": false,
  265. // "ManageCiphers": false,
  266. // "ManageGroups": false,
  267. // "ManagePolicies": false,
  268. // "ManageResetPassword": false,
  269. // "ManageSso": false,
  270. // "ManageUsers": false,
  271. // },
  272. "MaxStorageGb": 10, // The value doesn't matter, we don't check server-side
  273. // These are per user
  274. "Key": self.akey,
  275. "Status": self.status,
  276. "Type": self.atype,
  277. "Enabled": true,
  278. "Object": "profileOrganization",
  279. })
  280. }
  281. pub fn to_json_user_details(&self, conn: &DbConn) -> Value {
  282. let user = User::find_by_uuid(&self.user_uuid, conn).unwrap();
  283. json!({
  284. "Id": self.uuid,
  285. "UserId": self.user_uuid,
  286. "Name": user.name,
  287. "Email": user.email,
  288. "Status": self.status,
  289. "Type": self.atype,
  290. "AccessAll": self.access_all,
  291. "Object": "organizationUserUserDetails",
  292. })
  293. }
  294. pub fn to_json_user_access_restrictions(&self, col_user: &CollectionUser) -> Value {
  295. json!({
  296. "Id": self.uuid,
  297. "ReadOnly": col_user.read_only,
  298. "HidePasswords": col_user.hide_passwords,
  299. })
  300. }
  301. pub fn to_json_details(&self, conn: &DbConn) -> Value {
  302. let coll_uuids = if self.access_all {
  303. vec![] // If we have complete access, no need to fill the array
  304. } else {
  305. let collections = CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn);
  306. collections
  307. .iter()
  308. .map(|c| {
  309. json!({
  310. "Id": c.collection_uuid,
  311. "ReadOnly": c.read_only,
  312. "HidePasswords": c.hide_passwords,
  313. })
  314. })
  315. .collect()
  316. };
  317. json!({
  318. "Id": self.uuid,
  319. "UserId": self.user_uuid,
  320. "Status": self.status,
  321. "Type": self.atype,
  322. "AccessAll": self.access_all,
  323. "Collections": coll_uuids,
  324. "Object": "organizationUserDetails",
  325. })
  326. }
  327. pub fn save(&self, conn: &DbConn) -> EmptyResult {
  328. User::update_uuid_revision(&self.user_uuid, conn);
  329. db_run! { conn:
  330. sqlite, mysql {
  331. match diesel::replace_into(users_organizations::table)
  332. .values(UserOrganizationDb::to_db(self))
  333. .execute(conn)
  334. {
  335. Ok(_) => Ok(()),
  336. // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
  337. Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
  338. diesel::update(users_organizations::table)
  339. .filter(users_organizations::uuid.eq(&self.uuid))
  340. .set(UserOrganizationDb::to_db(self))
  341. .execute(conn)
  342. .map_res("Error adding user to organization")
  343. }
  344. Err(e) => Err(e.into()),
  345. }.map_res("Error adding user to organization")
  346. }
  347. postgresql {
  348. let value = UserOrganizationDb::to_db(self);
  349. diesel::insert_into(users_organizations::table)
  350. .values(&value)
  351. .on_conflict(users_organizations::uuid)
  352. .do_update()
  353. .set(&value)
  354. .execute(conn)
  355. .map_res("Error adding user to organization")
  356. }
  357. }
  358. }
  359. pub fn delete(self, conn: &DbConn) -> EmptyResult {
  360. User::update_uuid_revision(&self.user_uuid, conn);
  361. CollectionUser::delete_all_by_user_and_org(&self.user_uuid, &self.org_uuid, conn)?;
  362. db_run! { conn: {
  363. diesel::delete(users_organizations::table.filter(users_organizations::uuid.eq(self.uuid)))
  364. .execute(conn)
  365. .map_res("Error removing user from organization")
  366. }}
  367. }
  368. pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
  369. for user_org in Self::find_by_org(org_uuid, conn) {
  370. user_org.delete(conn)?;
  371. }
  372. Ok(())
  373. }
  374. pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
  375. for user_org in Self::find_any_state_by_user(user_uuid, conn) {
  376. user_org.delete(conn)?;
  377. }
  378. Ok(())
  379. }
  380. pub fn find_by_email_and_org(email: &str, org_id: &str, conn: &DbConn) -> Option<UserOrganization> {
  381. if let Some(user) = super::User::find_by_mail(email, conn) {
  382. if let Some(user_org) = UserOrganization::find_by_user_and_org(&user.uuid, org_id, conn) {
  383. return Some(user_org);
  384. }
  385. }
  386. None
  387. }
  388. pub fn has_status(&self, status: UserOrgStatus) -> bool {
  389. self.status == status as i32
  390. }
  391. pub fn has_type(&self, user_type: UserOrgType) -> bool {
  392. self.atype == user_type as i32
  393. }
  394. pub fn has_full_access(&self) -> bool {
  395. (self.access_all || self.atype >= UserOrgType::Admin) && self.has_status(UserOrgStatus::Confirmed)
  396. }
  397. pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
  398. db_run! { conn: {
  399. users_organizations::table
  400. .filter(users_organizations::uuid.eq(uuid))
  401. .first::<UserOrganizationDb>(conn)
  402. .ok().from_db()
  403. }}
  404. }
  405. pub fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
  406. db_run! { conn: {
  407. users_organizations::table
  408. .filter(users_organizations::uuid.eq(uuid))
  409. .filter(users_organizations::org_uuid.eq(org_uuid))
  410. .first::<UserOrganizationDb>(conn)
  411. .ok().from_db()
  412. }}
  413. }
  414. pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
  415. db_run! { conn: {
  416. users_organizations::table
  417. .filter(users_organizations::user_uuid.eq(user_uuid))
  418. .filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
  419. .load::<UserOrganizationDb>(conn)
  420. .unwrap_or_default().from_db()
  421. }}
  422. }
  423. pub fn find_invited_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
  424. db_run! { conn: {
  425. users_organizations::table
  426. .filter(users_organizations::user_uuid.eq(user_uuid))
  427. .filter(users_organizations::status.eq(UserOrgStatus::Invited as i32))
  428. .load::<UserOrganizationDb>(conn)
  429. .unwrap_or_default().from_db()
  430. }}
  431. }
  432. pub fn find_any_state_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
  433. db_run! { conn: {
  434. users_organizations::table
  435. .filter(users_organizations::user_uuid.eq(user_uuid))
  436. .load::<UserOrganizationDb>(conn)
  437. .unwrap_or_default().from_db()
  438. }}
  439. }
  440. pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
  441. db_run! { conn: {
  442. users_organizations::table
  443. .filter(users_organizations::org_uuid.eq(org_uuid))
  444. .load::<UserOrganizationDb>(conn)
  445. .expect("Error loading user organizations").from_db()
  446. }}
  447. }
  448. pub fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
  449. db_run! { conn: {
  450. users_organizations::table
  451. .filter(users_organizations::org_uuid.eq(org_uuid))
  452. .count()
  453. .first::<i64>(conn)
  454. .ok()
  455. .unwrap_or(0)
  456. }}
  457. }
  458. pub fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Vec<Self> {
  459. db_run! { conn: {
  460. users_organizations::table
  461. .filter(users_organizations::org_uuid.eq(org_uuid))
  462. .filter(users_organizations::atype.eq(atype))
  463. .load::<UserOrganizationDb>(conn)
  464. .expect("Error loading user organizations").from_db()
  465. }}
  466. }
  467. pub fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
  468. db_run! { conn: {
  469. users_organizations::table
  470. .filter(users_organizations::user_uuid.eq(user_uuid))
  471. .filter(users_organizations::org_uuid.eq(org_uuid))
  472. .first::<UserOrganizationDb>(conn)
  473. .ok().from_db()
  474. }}
  475. }
  476. pub fn find_by_cipher_and_org(cipher_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec<Self> {
  477. db_run! { conn: {
  478. users_organizations::table
  479. .filter(users_organizations::org_uuid.eq(org_uuid))
  480. .left_join(users_collections::table.on(
  481. users_collections::user_uuid.eq(users_organizations::user_uuid)
  482. ))
  483. .left_join(ciphers_collections::table.on(
  484. ciphers_collections::collection_uuid.eq(users_collections::collection_uuid).and(
  485. ciphers_collections::cipher_uuid.eq(&cipher_uuid)
  486. )
  487. ))
  488. .filter(
  489. users_organizations::access_all.eq(true).or( // AccessAll..
  490. ciphers_collections::cipher_uuid.eq(&cipher_uuid) // ..or access to collection with cipher
  491. )
  492. )
  493. .select(users_organizations::all_columns)
  494. .load::<UserOrganizationDb>(conn).expect("Error loading user organizations").from_db()
  495. }}
  496. }
  497. pub fn find_by_collection_and_org(collection_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec<Self> {
  498. db_run! { conn: {
  499. users_organizations::table
  500. .filter(users_organizations::org_uuid.eq(org_uuid))
  501. .left_join(users_collections::table.on(
  502. users_collections::user_uuid.eq(users_organizations::user_uuid)
  503. ))
  504. .filter(
  505. users_organizations::access_all.eq(true).or( // AccessAll..
  506. users_collections::collection_uuid.eq(&collection_uuid) // ..or access to collection with cipher
  507. )
  508. )
  509. .select(users_organizations::all_columns)
  510. .load::<UserOrganizationDb>(conn).expect("Error loading user organizations").from_db()
  511. }}
  512. }
  513. }
  514. #[cfg(test)]
  515. mod tests {
  516. use super::*;
  517. #[test]
  518. #[allow(non_snake_case)]
  519. fn partial_cmp_UserOrgType() {
  520. assert!(UserOrgType::Owner > UserOrgType::Admin);
  521. assert!(UserOrgType::Admin > UserOrgType::Manager);
  522. assert!(UserOrgType::Manager > UserOrgType::User);
  523. }
  524. }