mirror of
				https://github.com/dani-garcia/vaultwarden.git
				synced 2025-10-26 07:50:02 +02:00 
			
		
		
		
	Merge branch 'domdomegg-domdomegg/single-organization-policy' into main
This commit is contained in:
		| @@ -105,7 +105,7 @@ fn sync(data: Form<SyncData>, headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let collections_json: Vec<Value> = | ||||
|         collections.iter().map(|c| c.to_json_details(&headers.user.uuid, &conn)).collect(); | ||||
|  | ||||
|     let policies = OrgPolicy::find_by_user(&headers.user.uuid, &conn); | ||||
|     let policies = OrgPolicy::find_confirmed_by_user(&headers.user.uuid, &conn); | ||||
|     let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect(); | ||||
|  | ||||
|     let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn); | ||||
|   | ||||
| @@ -683,7 +683,7 @@ fn policies_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
|  | ||||
|     let policies = OrgPolicy::find_by_user(&grantor_user.uuid, &conn); | ||||
|     let policies = OrgPolicy::find_confirmed_by_user(&grantor_user.uuid, &conn); | ||||
|     let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect(); | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|   | ||||
| @@ -102,6 +102,11 @@ fn create_organization(headers: Headers, data: JsonUpcase<OrgData>, conn: DbConn | ||||
|     if !CONFIG.is_org_creation_allowed(&headers.user.email) { | ||||
|         err!("User not allowed to create organizations") | ||||
|     } | ||||
|     if OrgPolicy::is_applicable_to_user(&headers.user.uuid, OrgPolicyType::SingleOrg, &conn) { | ||||
|         err!( | ||||
|             "You may not create an organization. You belong to an organization which has a policy that prohibits you from being a member of any other organization." | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     let data: OrgData = data.into_inner().data; | ||||
|     let (private_key, public_key) = if data.Keys.is_some() { | ||||
| @@ -747,6 +752,30 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase<AcceptD | ||||
|                     err!("You cannot join this organization until you enable two-step login on your user account.") | ||||
|                 } | ||||
|  | ||||
|                 // Enforce Single Organization Policy of organization user is trying to join | ||||
|                 let single_org_policy_enabled = | ||||
|                     match OrgPolicy::find_by_org_and_type(&user_org.org_uuid, OrgPolicyType::SingleOrg as i32, &conn) { | ||||
|                         Some(p) => p.enabled, | ||||
|                         None => false, | ||||
|                     }; | ||||
|                 if single_org_policy_enabled && user_org.atype < UserOrgType::Admin { | ||||
|                     let is_member_of_another_org = UserOrganization::find_any_state_by_user(&user_org.user_uuid, &conn) | ||||
|                         .into_iter() | ||||
|                         .filter(|uo| uo.org_uuid != user_org.org_uuid) | ||||
|                         .count() | ||||
|                         > 1; | ||||
|                     if is_member_of_another_org { | ||||
|                         err!("You may not join this organization until you leave or remove all other organizations.") | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // Enforce Single Organization Policy of other organizations user is a member of | ||||
|                 if OrgPolicy::is_applicable_to_user(&user_org.user_uuid, OrgPolicyType::SingleOrg, &conn) { | ||||
|                     err!( | ||||
|                         "You cannot join this organization because you are a member of an organization which forbids it" | ||||
|                     ) | ||||
|                 } | ||||
|  | ||||
|                 user_org.status = UserOrgStatus::Accepted as i32; | ||||
|                 user_org.save(&conn)?; | ||||
|             } | ||||
| @@ -1219,6 +1248,33 @@ fn put_policy( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // If enabling the SingleOrg policy, remove this org's members that are members of other orgs | ||||
|     if pol_type_enum == OrgPolicyType::SingleOrg && data.enabled { | ||||
|         let org_members = UserOrganization::find_by_org(&org_id, &conn); | ||||
|  | ||||
|         for member in org_members.into_iter() { | ||||
|             // Policy only applies to non-Owner/non-Admin members who have accepted joining the org | ||||
|             if member.atype < UserOrgType::Admin && member.status != UserOrgStatus::Invited as i32 { | ||||
|                 let is_member_of_another_org = UserOrganization::find_any_state_by_user(&member.user_uuid, &conn) | ||||
|                     .into_iter() | ||||
|                     // Other UserOrganization's where they have accepted being a member of | ||||
|                     .filter(|uo| uo.uuid != member.uuid && uo.status != UserOrgStatus::Invited as i32) | ||||
|                     .count() | ||||
|                     > 1; | ||||
|  | ||||
|                 if is_member_of_another_org { | ||||
|                     if CONFIG.mail_enabled() { | ||||
|                         let org = Organization::find_by_uuid(&member.org_uuid, &conn).unwrap(); | ||||
|                         let user = User::find_by_uuid(&member.user_uuid, &conn).unwrap(); | ||||
|  | ||||
|                         mail::send_single_org_removed_from_org(&user.email, &org.name)?; | ||||
|                     } | ||||
|                     member.delete(&conn)?; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn) { | ||||
|         Some(p) => p, | ||||
|         None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()), | ||||
|   | ||||
| @@ -56,7 +56,7 @@ fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult { | ||||
|  | ||||
|     // COMMON | ||||
|     let user = User::find_by_uuid(&device.user_uuid, &conn).unwrap(); | ||||
|     let orgs = UserOrganization::find_by_user(&user.uuid, &conn); | ||||
|     let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, &conn); | ||||
|  | ||||
|     let (access_token, expires_in) = device.refresh_tokens(&user, orgs); | ||||
|  | ||||
| @@ -147,7 +147,7 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult | ||||
|     } | ||||
|  | ||||
|     // Common | ||||
|     let orgs = UserOrganization::find_by_user(&user.uuid, &conn); | ||||
|     let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, &conn); | ||||
|  | ||||
|     let (access_token, expires_in) = device.refresh_tokens(&user, orgs); | ||||
|     device.save(&conn)?; | ||||
|   | ||||
| @@ -874,6 +874,7 @@ where | ||||
|     reg!("email/pw_hint_none", ".html"); | ||||
|     reg!("email/pw_hint_some", ".html"); | ||||
|     reg!("email/send_2fa_removed_from_org", ".html"); | ||||
|     reg!("email/send_single_org_removed_from_org", ".html"); | ||||
|     reg!("email/send_org_invite", ".html"); | ||||
|     reg!("email/send_emergency_access_invite", ".html"); | ||||
|     reg!("email/twofactor_email", ".html"); | ||||
|   | ||||
| @@ -27,7 +27,7 @@ pub enum OrgPolicyType { | ||||
|     TwoFactorAuthentication = 0, | ||||
|     MasterPassword = 1, | ||||
|     PasswordGenerator = 2, | ||||
|     // SingleOrg = 3, // Not currently supported. | ||||
|     SingleOrg = 3, | ||||
|     // RequireSso = 4, // Not currently supported. | ||||
|     PersonalOwnership = 5, | ||||
|     DisableSend = 6, | ||||
| @@ -143,7 +143,7 @@ impl OrgPolicy { | ||||
|         }} | ||||
|     } | ||||
|  | ||||
|     pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub fn find_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             org_policies::table | ||||
|                 .inner_join( | ||||
| @@ -184,8 +184,8 @@ impl OrgPolicy { | ||||
|     /// and the user is not an owner or admin of that org. This is only useful for checking | ||||
|     /// applicability of policy types that have these particular semantics. | ||||
|     pub fn is_applicable_to_user(user_uuid: &str, policy_type: OrgPolicyType, conn: &DbConn) -> bool { | ||||
|         // Returns confirmed users only. | ||||
|         for policy in OrgPolicy::find_by_user(user_uuid, conn) { | ||||
|         // TODO: Should check confirmed and accepted users | ||||
|         for policy in OrgPolicy::find_confirmed_by_user(user_uuid, conn) { | ||||
|             if policy.enabled && policy.has_type(policy_type) { | ||||
|                 let org_uuid = &policy.org_uuid; | ||||
|                 if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn) { | ||||
| @@ -201,8 +201,7 @@ impl OrgPolicy { | ||||
|     /// Returns true if the user belongs to an org that has enabled the `DisableHideEmail` | ||||
|     /// option of the `Send Options` policy, and the user is not an owner or admin of that org. | ||||
|     pub fn is_hide_email_disabled(user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         // Returns confirmed users only. | ||||
|         for policy in OrgPolicy::find_by_user(user_uuid, conn) { | ||||
|         for policy in OrgPolicy::find_confirmed_by_user(user_uuid, conn) { | ||||
|             if policy.enabled && policy.has_type(OrgPolicyType::SendOptions) { | ||||
|                 let org_uuid = &policy.org_uuid; | ||||
|                 if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn) { | ||||
|   | ||||
| @@ -477,7 +477,7 @@ impl UserOrganization { | ||||
|         }} | ||||
|     } | ||||
|  | ||||
|     pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub fn find_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             users_organizations::table | ||||
|                 .filter(users_organizations::user_uuid.eq(user_uuid)) | ||||
|   | ||||
| @@ -185,7 +185,7 @@ use crate::error::MapResult; | ||||
| /// Database methods | ||||
| impl User { | ||||
|     pub fn to_json(&self, conn: &DbConn) -> Value { | ||||
|         let orgs = UserOrganization::find_by_user(&self.uuid, conn); | ||||
|         let orgs = UserOrganization::find_confirmed_by_user(&self.uuid, conn); | ||||
|         let orgs_json: Vec<Value> = orgs.iter().map(|c| c.to_json(conn)).collect(); | ||||
|         let twofactor_enabled = !TwoFactor::find_by_user(&self.uuid, conn).is_empty(); | ||||
|  | ||||
| @@ -256,7 +256,7 @@ impl User { | ||||
|     } | ||||
|  | ||||
|     pub fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         for user_org in UserOrganization::find_by_user(&self.uuid, conn) { | ||||
|         for user_org in UserOrganization::find_confirmed_by_user(&self.uuid, conn) { | ||||
|             if user_org.atype == UserOrgType::Owner { | ||||
|                 let owner_type = UserOrgType::Owner as i32; | ||||
|                 if UserOrganization::find_by_org_and_type(&user_org.org_uuid, owner_type, conn).len() <= 1 { | ||||
|   | ||||
							
								
								
									
										12
									
								
								src/mail.rs
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								src/mail.rs
									
									
									
									
									
								
							| @@ -195,6 +195,18 @@ pub fn send_2fa_removed_from_org(address: &str, org_name: &str) -> EmptyResult { | ||||
|     send_email(address, &subject, body_html, body_text) | ||||
| } | ||||
|  | ||||
| pub fn send_single_org_removed_from_org(address: &str, org_name: &str) -> EmptyResult { | ||||
|     let (subject, body_html, body_text) = get_text( | ||||
|         "email/send_single_org_removed_from_org", | ||||
|         json!({ | ||||
|             "url": CONFIG.domain(), | ||||
|             "org_name": org_name, | ||||
|         }), | ||||
|     )?; | ||||
|  | ||||
|     send_email(address, &subject, body_html, body_text) | ||||
| } | ||||
|  | ||||
| pub fn send_invite( | ||||
|     address: &str, | ||||
|     uuid: &str, | ||||
|   | ||||
| @@ -0,0 +1,5 @@ | ||||
| You have been removed from {{{org_name}}} | ||||
| <!----------------> | ||||
| Your user account has been removed from the *{{org_name}}* organization because you are a part of another organization. The {{org_name}} organization has enabled a policy that prevents users from being a part of multiple organizations. Before you can re-join this organization you need to leave all other organizations or join with a different account. | ||||
| === | ||||
| Github: https://github.com/dani-garcia/vaultwarden | ||||
| @@ -0,0 +1,11 @@ | ||||
| You have been removed from {{{org_name}}} | ||||
| <!----------------> | ||||
| {{> email/email_header }} | ||||
| <table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> | ||||
|    <tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"> | ||||
|       <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center"> | ||||
|          Your user account has been removed from the <b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">{{org_name}}</b> organization because you are a part of another organization. The {{org_name}} organization has enabled a policy that prevents users from being a part of multiple organizations. Before you can re-join this organization you need to leave all other organizations or join with a different account. | ||||
|       </td> | ||||
|    </tr> | ||||
| </table> | ||||
| {{> email/email_footer }} | ||||
		Reference in New Issue
	
	Block a user