#![allow(clippy::uninlined_format_args)] use std::collections::HashSet; use anyhow::{anyhow, Result}; use requestty::{prompt_one, Question}; mod ldap; mod lldap; use ldap::LdapGroup; use lldap::{LldapGroup, User}; fn ask_generic_confirmation(name: &str, message: &str) -> Result { let confirm = Question::confirm(name) .message(message) .default(true) .build(); let answer = prompt_one(confirm)?; Ok(answer.as_bool().unwrap()) } fn get_users_to_add(users: &[User], existing_users: &[String]) -> Result>> { let existing_users = HashSet::<&String>::from_iter(existing_users); let num_found_users = users.len(); let input_users: Vec<_> = users .iter() .filter(|u| !existing_users.contains(&u.user_input.id)) .map(User::clone) .collect(); println!( "Found {} users, of which {} new users: [\n {}\n]", num_found_users, input_users.len(), input_users .iter() .map(|u| format!( "\"{}\" ({})", &u.user_input.id, if u.password.is_some() { "with password" } else { "no password" } )) .collect::>() .join(",\n ") ); if !input_users.is_empty() && ask_generic_confirmation( "proceed_users", "Do you want to proceed to add those users to LLDAP?", )? { Ok(Some(input_users)) } else { Ok(None) } } fn should_insert_groups( input_groups: &[LdapGroup], existing_groups: &[LldapGroup], ) -> Result { let existing_group_names = HashSet::<&str>::from_iter(existing_groups.iter().map(|g| g.display_name.as_str())); let new_groups = input_groups .iter() .filter(|g| !existing_group_names.contains(g.name.as_str())); let num_new_groups = new_groups.clone().count(); println!( "Found {} groups, of which {} new groups: [\n {}\n]", input_groups.len(), num_new_groups, new_groups .map(|g| g.name.as_str()) .collect::>() .join(",\n ") ); Ok(num_new_groups != 0 && ask_generic_confirmation( "proceed_groups", "Do you want to proceed to add those groups to LLDAP?", )?) } struct GroupList { ldap_groups: Vec, lldap_groups: Vec, } fn migrate_groups( graphql_client: &lldap::GraphQLClient, ldap_connection: &mut ldap::LdapClient, ) -> Result> { Ok( if ask_generic_confirmation("should_import_groups", "Do you want to import groups?")? { let mut existing_groups = lldap::get_lldap_groups(graphql_client)?; let ldap_groups = ldap::get_groups(ldap_connection)?; if should_insert_groups(&ldap_groups, &existing_groups)? { lldap::insert_groups_into_lldap( &ldap_groups, &mut existing_groups, graphql_client, )?; } Some(GroupList { ldap_groups, lldap_groups: existing_groups, }) } else { None }, ) } struct UserList { lldap_users: Vec, ldap_users: Vec, } fn migrate_users( graphql_client: &lldap::GraphQLClient, ldap_connection: &mut ldap::LdapClient, ) -> Result> { Ok( if ask_generic_confirmation("should_import_users", "Do you want to import users?")? { let mut existing_users = lldap::get_lldap_users(graphql_client)?; let users = ldap::get_users(ldap_connection)?; if let Some(users_to_add) = get_users_to_add(&users, &existing_users)? { lldap::insert_users_into_lldap(users_to_add, &mut existing_users, graphql_client)?; } Some(UserList { lldap_users: existing_users, ldap_users: users, }) } else { None }, ) } fn migrate_memberships( user_list: Option, group_list: Option, graphql_client: lldap::GraphQLClient, ldap_connection: &mut ldap::LdapClient, ) -> Result<()> { let (ldap_users, existing_users) = user_list .map( |UserList { ldap_users, lldap_users, }| (Some(ldap_users), Some(lldap_users)), ) .unwrap_or_default(); let (ldap_groups, existing_groups) = group_list .map( |GroupList { ldap_groups, lldap_groups, }| (Some(ldap_groups), Some(lldap_groups)), ) .unwrap_or_default(); let ldap_users = ldap_users .ok_or_else(|| anyhow!("Missing LDAP users")) .or_else(|_| ldap::get_users(ldap_connection))?; let ldap_groups = ldap_groups .ok_or_else(|| anyhow!("Missing LDAP groups")) .or_else(|_| ldap::get_groups(ldap_connection))?; let existing_groups = existing_groups .ok_or_else(|| anyhow!("Missing LLDAP groups")) .or_else(|_| lldap::get_lldap_groups(&graphql_client))?; let existing_users = existing_users .ok_or_else(|| anyhow!("Missing LLDAP users")) .or_else(|_| lldap::get_lldap_users(&graphql_client))?; lldap::insert_group_memberships_into_lldap( &ldap_users, &ldap_groups, &existing_users, &existing_groups, &graphql_client, )?; Ok(()) } fn main() -> Result<()> { println!( "The migration tool requires access to both the original LDAP \ server and the HTTP API of the target LLDAP server." ); if !ask_generic_confirmation("setup_ready", "Are you ready to start?")? { return Ok(()); } let mut ldap_connection = ldap::get_ldap_connection()?; let graphql_client = lldap::get_lldap_client()?; let user_list = migrate_users(&graphql_client, &mut ldap_connection)?; let group_list = migrate_groups(&graphql_client, &mut ldap_connection)?; if ask_generic_confirmation( "should_import_memberships", "Do you want to import group memberships?", )? { migrate_memberships(user_list, group_list, graphql_client, &mut ldap_connection)?; } Ok(()) }