aboutsummaryrefslogtreecommitdiff
path: root/src/login/ldap_provider.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/login/ldap_provider.rs')
-rw-r--r--src/login/ldap_provider.rs265
1 files changed, 0 insertions, 265 deletions
diff --git a/src/login/ldap_provider.rs b/src/login/ldap_provider.rs
deleted file mode 100644
index 0af5676..0000000
--- a/src/login/ldap_provider.rs
+++ /dev/null
@@ -1,265 +0,0 @@
-use anyhow::Result;
-use async_trait::async_trait;
-use ldap3::{LdapConnAsync, Scope, SearchEntry};
-use log::debug;
-
-use crate::config::*;
-use crate::login::*;
-use crate::storage;
-
-pub struct LdapLoginProvider {
- ldap_server: String,
-
- pre_bind_on_login: bool,
- bind_dn_and_pw: Option<(String, String)>,
-
- search_base: String,
- attrs_to_retrieve: Vec<String>,
- username_attr: String,
- mail_attr: String,
- crypto_root_attr: String,
-
- storage_specific: StorageSpecific,
- in_memory_store: storage::in_memory::MemDb,
- garage_store: storage::garage::GarageRoot,
-}
-
-enum BucketSource {
- Constant(String),
- Attr(String),
-}
-
-enum StorageSpecific {
- InMemory,
- Garage {
- from_config: LdapGarageConfig,
- bucket_source: BucketSource,
- },
-}
-
-impl LdapLoginProvider {
- pub fn new(config: LoginLdapConfig) -> Result<Self> {
- let bind_dn_and_pw = match (config.bind_dn, config.bind_password) {
- (Some(dn), Some(pw)) => Some((dn, pw)),
- (None, None) => None,
- _ => bail!(
- "If either of `bind_dn` or `bind_password` is set, the other must be set as well."
- ),
- };
-
- if config.pre_bind_on_login && bind_dn_and_pw.is_none() {
- bail!("Cannot use `pre_bind_on_login` without setting `bind_dn` and `bind_password`");
- }
-
- let mut attrs_to_retrieve = vec![
- config.username_attr.clone(),
- config.mail_attr.clone(),
- config.crypto_root_attr.clone(),
- ];
-
- // storage specific
- let specific = match config.storage {
- LdapStorage::InMemory => StorageSpecific::InMemory,
- LdapStorage::Garage(grgconf) => {
- attrs_to_retrieve.push(grgconf.aws_access_key_id_attr.clone());
- attrs_to_retrieve.push(grgconf.aws_secret_access_key_attr.clone());
-
- let bucket_source =
- match (grgconf.default_bucket.clone(), grgconf.bucket_attr.clone()) {
- (Some(b), None) => BucketSource::Constant(b),
- (None, Some(a)) => BucketSource::Attr(a),
- _ => bail!("Must set `bucket` or `bucket_attr`, but not both"),
- };
-
- if let BucketSource::Attr(a) = &bucket_source {
- attrs_to_retrieve.push(a.clone());
- }
-
- StorageSpecific::Garage {
- from_config: grgconf,
- bucket_source,
- }
- }
- };
-
- Ok(Self {
- ldap_server: config.ldap_server,
- pre_bind_on_login: config.pre_bind_on_login,
- bind_dn_and_pw,
- search_base: config.search_base,
- attrs_to_retrieve,
- username_attr: config.username_attr,
- mail_attr: config.mail_attr,
- crypto_root_attr: config.crypto_root_attr,
- storage_specific: specific,
- //@FIXME should be created outside of the login provider
- //Login provider should return only a cryptoroot + a storage URI
- //storage URI that should be resolved outside...
- in_memory_store: storage::in_memory::MemDb::new(),
- garage_store: storage::garage::GarageRoot::new()?,
- })
- }
-
- async fn storage_creds_from_ldap_user(&self, user: &SearchEntry) -> Result<Builder> {
- let storage: Builder = match &self.storage_specific {
- StorageSpecific::InMemory => {
- self.in_memory_store
- .builder(&get_attr(user, &self.username_attr)?)
- .await
- }
- StorageSpecific::Garage {
- from_config,
- bucket_source,
- } => {
- let aws_access_key_id = get_attr(user, &from_config.aws_access_key_id_attr)?;
- let aws_secret_access_key =
- get_attr(user, &from_config.aws_secret_access_key_attr)?;
- let bucket = match bucket_source {
- BucketSource::Constant(b) => b.clone(),
- BucketSource::Attr(a) => get_attr(user, &a)?,
- };
-
- self.garage_store.user(storage::garage::GarageConf {
- region: from_config.aws_region.clone(),
- s3_endpoint: from_config.s3_endpoint.clone(),
- k2v_endpoint: from_config.k2v_endpoint.clone(),
- aws_access_key_id,
- aws_secret_access_key,
- bucket,
- })?
- }
- };
-
- Ok(storage)
- }
-}
-
-#[async_trait]
-impl LoginProvider for LdapLoginProvider {
- async fn login(&self, username: &str, password: &str) -> Result<Credentials> {
- check_identifier(username)?;
-
- let (conn, mut ldap) = LdapConnAsync::new(&self.ldap_server).await?;
- ldap3::drive!(conn);
-
- if self.pre_bind_on_login {
- let (dn, pw) = self.bind_dn_and_pw.as_ref().unwrap();
- ldap.simple_bind(dn, pw).await?.success()?;
- }
-
- let (matches, _res) = ldap
- .search(
- &self.search_base,
- Scope::Subtree,
- &format!(
- "(&(objectClass=inetOrgPerson)({}={}))",
- self.username_attr, username
- ),
- &self.attrs_to_retrieve,
- )
- .await?
- .success()?;
-
- if matches.is_empty() {
- bail!("Invalid username");
- }
- if matches.len() > 1 {
- bail!("Invalid username (multiple matching accounts)");
- }
- let user = SearchEntry::construct(matches.into_iter().next().unwrap());
- debug!(
- "Found matching LDAP user for username {}: {}",
- username, user.dn
- );
-
- // Try to login against LDAP server with provided password
- // to check user's password
- ldap.simple_bind(&user.dn, password)
- .await?
- .success()
- .context("Invalid password")?;
- debug!("Ldap login with user name {} successfull", username);
-
- // cryptography
- let crstr = get_attr(&user, &self.crypto_root_attr)?;
- let cr = CryptoRoot(crstr);
- let keys = cr.crypto_keys(password)?;
-
- // storage
- let storage = self.storage_creds_from_ldap_user(&user).await?;
-
- drop(ldap);
-
- Ok(Credentials { storage, keys })
- }
-
- async fn public_login(&self, email: &str) -> Result<PublicCredentials> {
- check_identifier(email)?;
-
- let (dn, pw) = match self.bind_dn_and_pw.as_ref() {
- Some(x) => x,
- None => bail!("Missing bind_dn and bind_password in LDAP login provider config"),
- };
-
- let (conn, mut ldap) = LdapConnAsync::new(&self.ldap_server).await?;
- ldap3::drive!(conn);
- ldap.simple_bind(dn, pw).await?.success()?;
-
- let (matches, _res) = ldap
- .search(
- &self.search_base,
- Scope::Subtree,
- &format!(
- "(&(objectClass=inetOrgPerson)({}={}))",
- self.mail_attr, email
- ),
- &self.attrs_to_retrieve,
- )
- .await?
- .success()?;
-
- if matches.is_empty() {
- bail!("No such user account");
- }
- if matches.len() > 1 {
- bail!("Multiple matching user accounts");
- }
- let user = SearchEntry::construct(matches.into_iter().next().unwrap());
- debug!("Found matching LDAP user for email {}: {}", email, user.dn);
-
- // cryptography
- let crstr = get_attr(&user, &self.crypto_root_attr)?;
- let cr = CryptoRoot(crstr);
- let public_key = cr.public_key()?;
-
- // storage
- let storage = self.storage_creds_from_ldap_user(&user).await?;
- drop(ldap);
-
- Ok(PublicCredentials {
- storage,
- public_key,
- })
- }
-}
-
-fn get_attr(user: &SearchEntry, attr: &str) -> Result<String> {
- Ok(user
- .attrs
- .get(attr)
- .ok_or(anyhow!("Missing attr: {}", attr))?
- .iter()
- .next()
- .ok_or(anyhow!("No value for attr: {}", attr))?
- .clone())
-}
-
-fn check_identifier(id: &str) -> Result<()> {
- let is_ok = id
- .chars()
- .all(|c| c.is_alphanumeric() || "-+_.@".contains(c));
- if !is_ok {
- bail!("Invalid username/email address, must contain only a-z A-Z 0-9 - + _ . @");
- }
- Ok(())
-}