mirror of
https://github.com/dani-garcia/vaultwarden
synced 2024-12-14 01:23:55 +01:00
Basic experimental ldap import support with the official directory connector
This commit is contained in:
parent
a74bc2e58f
commit
85e3c73525
2 changed files with 109 additions and 0 deletions
|
@ -50,6 +50,7 @@ pub fn routes() -> Vec<Route> {
|
||||||
get_organization_tax,
|
get_organization_tax,
|
||||||
get_plans,
|
get_plans,
|
||||||
get_plans_tax_rates,
|
get_plans_tax_rates,
|
||||||
|
import,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1076,3 +1077,101 @@ fn get_plans_tax_rates(_headers: Headers, _conn: DbConn) -> JsonResult {
|
||||||
"ContinuationToken": null
|
"ContinuationToken": null
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
struct OrgImportGroupData {
|
||||||
|
Name: String, // "GroupName"
|
||||||
|
ExternalId: String, // "cn=GroupName,ou=Groups,dc=example,dc=com"
|
||||||
|
Users: Vec<String>, // ["uid=user,ou=People,dc=example,dc=com"]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
struct OrgImportUserData {
|
||||||
|
Email: String, // "user@maildomain.net"
|
||||||
|
ExternalId: String, // "uid=user,ou=People,dc=example,dc=com"
|
||||||
|
Deleted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
struct OrgImportData {
|
||||||
|
Groups: Vec<OrgImportGroupData>,
|
||||||
|
OverwriteExisting: bool,
|
||||||
|
Users: Vec<OrgImportUserData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/organizations/<org_id>/import", data = "<data>")]
|
||||||
|
fn import(org_id: String, data: JsonUpcase<OrgImportData>, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||||
|
let data = data.into_inner().data;
|
||||||
|
println!("{:#?}", data);
|
||||||
|
|
||||||
|
// TODO: Currently we aren't storing the externalId's anywhere, so we also don't have a way
|
||||||
|
// to differentiate between auto-imported users and manually added ones.
|
||||||
|
// This means that this endpoint can end up removing users that were added manually by an admin,
|
||||||
|
// as opposed to upstream which only removes auto-imported users.
|
||||||
|
|
||||||
|
// User needs to be admin or owner to use the Directry Connector
|
||||||
|
match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) {
|
||||||
|
Some(user_org) if user_org.atype >= UserOrgType::Admin => { /* Okay, nothing to do */ }
|
||||||
|
Some(_) => err!("User has insufficient permissions to use Directory Connector"),
|
||||||
|
None => err!("User not part of organization"),
|
||||||
|
};
|
||||||
|
|
||||||
|
for user_data in &data.Users {
|
||||||
|
if user_data.Deleted {
|
||||||
|
// If user is marked for deletion and it exists, delete it
|
||||||
|
if let Some(user_org) = UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &conn) {
|
||||||
|
user_org.delete(&conn)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user is not part of the organization, but it exists
|
||||||
|
} else if UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &conn).is_none() {
|
||||||
|
if let Some (user) = User::find_by_mail(&user_data.Email, &conn) {
|
||||||
|
|
||||||
|
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(&conn)?;
|
||||||
|
|
||||||
|
if CONFIG.mail_enabled() {
|
||||||
|
let org_name = match Organization::find_by_uuid(&org_id, &conn) {
|
||||||
|
Some(org) => org.name,
|
||||||
|
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(headers.user.email.clone()),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_and_type(&org_id, UserOrgType::User as i32, &conn) {
|
||||||
|
if let Some (user_email) = User::find_by_uuid(&user_org.user_uuid, &conn).map(|u| u.email) {
|
||||||
|
if !data.Users.iter().any(|u| u.Email == user_email) {
|
||||||
|
user_org.delete(&conn)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -439,6 +439,16 @@ impl UserOrganization {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_by_email_and_org(email: &str, org_id: &str, conn: &DbConn) -> Option<UserOrganization> {
|
||||||
|
if let Some(user) = super::User::find_by_mail(email, conn) {
|
||||||
|
if let Some(user_org) = UserOrganization::find_by_user_and_org(&user.uuid, org_id, &conn) {
|
||||||
|
return Some(user_org);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn has_status(&self, status: UserOrgStatus) -> bool {
|
pub fn has_status(&self, status: UserOrgStatus) -> bool {
|
||||||
self.status == status as i32
|
self.status == status as i32
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue