-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merging a PR from @Kurnihil into the already rebased branch. Made some small changes to make it work with newer changes. Some finetuning is probably still needed. Co-authored-by: Daniele Andrei <daniele.andrei@geo-satis.com> Co-authored-by: Kurnihil
- Loading branch information
Showing
16 changed files
with
282 additions
and
7 deletions.
There are no files selected for viewing
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
use chrono::Utc; | ||
use rocket::{ | ||
request::{self, FromRequest, Outcome}, | ||
Request, Route, | ||
}; | ||
|
||
use crate::{ | ||
api::{EmptyResult, JsonUpcase}, | ||
auth, | ||
db::{models::*, DbConn}, | ||
mail, CONFIG, | ||
}; | ||
|
||
pub fn routes() -> Vec<Route> { | ||
routes![ldap_import] | ||
} | ||
|
||
#[derive(Deserialize, Debug)] | ||
#[allow(non_snake_case)] | ||
struct OrgImportGroupData { | ||
Name: String, | ||
ExternalId: String, | ||
MemberExternalIds: Vec<String>, | ||
} | ||
|
||
#[derive(Deserialize, Debug)] | ||
#[allow(non_snake_case)] | ||
struct OrgImportUserData { | ||
Email: String, | ||
ExternalId: String, | ||
Deleted: bool, | ||
} | ||
|
||
#[derive(Deserialize, Debug)] | ||
#[allow(non_snake_case)] | ||
struct OrgImportData { | ||
Groups: Vec<OrgImportGroupData>, | ||
Members: Vec<OrgImportUserData>, | ||
OverwriteExisting: bool, | ||
#[allow(dead_code)] | ||
LargeImport: bool, | ||
} | ||
|
||
#[post("/public/organization/import", data = "<data>")] | ||
async fn ldap_import(data: JsonUpcase<OrgImportData>, token: PublicToken, mut conn: DbConn) -> EmptyResult { | ||
let _ = &conn; | ||
let org_id = token.0; | ||
let data = data.into_inner().data; | ||
|
||
for user_data in &data.Members { | ||
if user_data.Deleted { | ||
// If user is marked for deletion and it exists, revoke it | ||
if let Some(mut user_org) = | ||
UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &mut conn).await | ||
{ | ||
user_org.revoke(); | ||
user_org.save(&mut conn).await?; | ||
} | ||
|
||
// If user is part of the organization, restore it | ||
} else if let Some(mut user_org) = | ||
UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &mut conn).await | ||
{ | ||
if user_org.status < UserOrgStatus::Revoked as i32 { | ||
user_org.restore(); | ||
user_org.save(&mut conn).await?; | ||
} | ||
} else { | ||
// If user is not part of the organization | ||
let user = match User::find_by_mail(&user_data.Email, &mut conn).await { | ||
Some(user) => user, // exists in vaultwarden | ||
None => { | ||
// doesn't exist in vaultwarden | ||
let mut new_user = User::new(user_data.Email.clone()); | ||
new_user.set_external_id(Some(user_data.ExternalId.clone())); | ||
new_user.save(&mut conn).await?; | ||
|
||
if !CONFIG.mail_enabled() { | ||
let invitation = Invitation::new(&new_user.email); | ||
invitation.save(&mut conn).await?; | ||
} | ||
new_user | ||
} | ||
}; | ||
let user_org_status = if CONFIG.mail_enabled() { | ||
UserOrgStatus::Invited as i32 | ||
} else { | ||
UserOrgStatus::Accepted as i32 // Automatically mark user as accepted if no email invites | ||
}; | ||
|
||
let mut new_org_user = UserOrganization::new(user.uuid.clone(), org_id.clone()); | ||
new_org_user.access_all = false; | ||
new_org_user.atype = UserOrgType::User as i32; | ||
new_org_user.status = user_org_status; | ||
|
||
new_org_user.save(&mut conn).await?; | ||
|
||
if CONFIG.mail_enabled() { | ||
let (org_name, org_email) = match Organization::find_by_uuid(&org_id, &mut conn).await { | ||
Some(org) => (org.name, org.billing_email), | ||
None => err!("Error looking up organization"), | ||
}; | ||
|
||
mail::send_invite( | ||
&user_data.Email, | ||
&user.uuid, | ||
Some(org_id.clone()), | ||
Some(new_org_user.uuid), | ||
&org_name, | ||
Some(org_email), | ||
) | ||
.await?; | ||
} | ||
} | ||
} | ||
|
||
for group_data in &data.Groups { | ||
let group_uuid = match Group::find_by_external_id(&group_data.ExternalId, &mut conn).await { | ||
Some(group) => group.uuid, | ||
None => { | ||
let mut group = | ||
Group::new(org_id.clone(), group_data.Name.clone(), false, Some(group_data.ExternalId.clone())); | ||
group.save(&mut conn).await?; | ||
group.uuid | ||
} | ||
}; | ||
|
||
GroupUser::delete_all_by_group(&group_uuid, &mut conn).await?; | ||
|
||
for ext_id in &group_data.MemberExternalIds { | ||
if let Some(user) = User::find_by_external_id(ext_id, &mut conn).await { | ||
if let Some(user_org) = UserOrganization::find_by_user_and_org(&user.uuid, &org_id, &mut conn).await { | ||
let mut group_user = GroupUser::new(group_uuid.clone(), user_org.uuid.clone()); | ||
group_user.save(&mut conn).await?; | ||
} | ||
} | ||
} | ||
} | ||
|
||
// If this flag is enabled, any user that isn't provided in the Users list will be removed (by default they will be kept unless they have Deleted == true) | ||
if data.OverwriteExisting { | ||
for user_org in UserOrganization::find_by_org(&org_id, &mut conn).await { | ||
if let Some(user_external_id) = | ||
User::find_by_uuid(&user_org.user_uuid, &mut conn).await.map(|u| u.external_id) | ||
{ | ||
if user_external_id.is_some() | ||
&& !data.Members.iter().any(|u| u.ExternalId == *user_external_id.as_ref().unwrap()) | ||
{ | ||
if user_org.atype == UserOrgType::Owner && user_org.status == UserOrgStatus::Confirmed as i32 { | ||
// Removing owner, check that there is at least one other confirmed owner | ||
if UserOrganization::count_confirmed_by_org_and_type(&org_id, UserOrgType::Owner, &mut conn) | ||
.await | ||
<= 1 | ||
{ | ||
warn!("Can't delete the last owner"); | ||
continue; | ||
} | ||
} | ||
user_org.delete(&mut conn).await?; | ||
} | ||
} | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct PublicToken(String); | ||
|
||
#[rocket::async_trait] | ||
impl<'r> FromRequest<'r> for PublicToken { | ||
type Error = &'static str; | ||
|
||
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> { | ||
let headers = request.headers(); | ||
// Get access_token | ||
let access_token: &str = match headers.get_one("Authorization") { | ||
Some(a) => match a.rsplit("Bearer ").next() { | ||
Some(split) => split, | ||
None => err_handler!("No access token provided"), | ||
}, | ||
None => err_handler!("No access token provided"), | ||
}; | ||
// Check JWT token is valid and get device and user from it | ||
let claims = match auth::decode_api_org(access_token) { | ||
Ok(claims) => claims, | ||
Err(_) => err_handler!("Invalid claim"), | ||
}; | ||
// Check if time is between claims.nbf and claims.exp | ||
let time_now = Utc::now().naive_utc().timestamp(); | ||
if time_now < claims.nbf { | ||
err_handler!("Token issued in the future"); | ||
} | ||
if time_now > claims.exp { | ||
err_handler!("Token expired"); | ||
} | ||
// Check if claims.iss is host|claims.scope[0] | ||
let host = match auth::Host::from_request(request).await { | ||
Outcome::Success(host) => host, | ||
_ => err_handler!("Error getting Host"), | ||
}; | ||
let complete_host = format!("{}|{}", host.host, claims.scope[0]); | ||
if complete_host != claims.iss { | ||
err_handler!("Token not issued by this server"); | ||
} | ||
|
||
// Check if claims.sub is org_api_key.uuid | ||
// Check if claims.client_sub is org_api_key.org_uuid | ||
let conn = match DbConn::from_request(request).await { | ||
Outcome::Success(conn) => conn, | ||
_ => err_handler!("Error getting DB"), | ||
}; | ||
let org_uuid = match claims.client_id.strip_prefix("organization.") { | ||
Some(uuid) => uuid, | ||
None => err_handler!("Malformed client_id"), | ||
}; | ||
let org_api_key = match OrganizationApiKey::find_by_org_uuid(org_uuid, &conn).await { | ||
Some(org_api_key) => org_api_key, | ||
None => err_handler!("Invalid client_id"), | ||
}; | ||
if org_api_key.org_uuid != claims.client_sub { | ||
err_handler!("Token not issued for this org"); | ||
} | ||
if org_api_key.uuid != claims.sub { | ||
err_handler!("Token not issued for this client"); | ||
} | ||
|
||
Outcome::Success(PublicToken(claims.client_sub)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.