mirror of
https://git.proxmox.com/git/proxmox-backup
synced 2025-05-29 19:33:34 +00:00
api: access: add routes for managing AD realms
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com> Reviewed-by: Lukas Wagner <l.wagner@proxmox.com> Tested-by: Lukas Wagner <l.wagner@proxmox.com>
This commit is contained in:
parent
b18a8170cb
commit
c7051f3342
98
pbs-api-types/src/ad.rs
Normal file
98
pbs-api-types/src/ad.rs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use proxmox_schema::{api, Updater};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
LdapMode, LDAP_DOMAIN_SCHEMA, REALM_ID_SCHEMA, SINGLE_LINE_COMMENT_SCHEMA,
|
||||||
|
SYNC_ATTRIBUTES_SCHEMA, SYNC_DEFAULTS_STRING_SCHEMA, USER_CLASSES_SCHEMA,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
"realm": {
|
||||||
|
schema: REALM_ID_SCHEMA,
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
optional: true,
|
||||||
|
schema: SINGLE_LINE_COMMENT_SCHEMA,
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
optional: true,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
"sync-defaults-options": {
|
||||||
|
schema: SYNC_DEFAULTS_STRING_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
"sync-attributes": {
|
||||||
|
schema: SYNC_ATTRIBUTES_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
"user-classes" : {
|
||||||
|
optional: true,
|
||||||
|
schema: USER_CLASSES_SCHEMA,
|
||||||
|
},
|
||||||
|
"base-dn" : {
|
||||||
|
schema: LDAP_DOMAIN_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
"bind-dn" : {
|
||||||
|
schema: LDAP_DOMAIN_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize, Deserialize, Updater, Clone)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// AD realm configuration properties.
|
||||||
|
pub struct AdRealmConfig {
|
||||||
|
#[updater(skip)]
|
||||||
|
pub realm: String,
|
||||||
|
/// AD server address
|
||||||
|
pub server1: String,
|
||||||
|
/// Fallback AD server address
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub server2: Option<String>,
|
||||||
|
/// AD server Port
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub port: Option<u16>,
|
||||||
|
/// Base domain name. Users are searched under this domain using a `subtree search`.
|
||||||
|
/// Expected to be set only internally to `defaultNamingContext` of the AD server, but can be
|
||||||
|
/// overridden if the need arises.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub base_dn: Option<String>,
|
||||||
|
/// Comment
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub comment: Option<String>,
|
||||||
|
/// Connection security
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub mode: Option<LdapMode>,
|
||||||
|
/// Verify server certificate
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub verify: Option<bool>,
|
||||||
|
/// CA certificate to use for the server. The path can point to
|
||||||
|
/// either a file, or a directory. If it points to a file,
|
||||||
|
/// the PEM-formatted X.509 certificate stored at the path
|
||||||
|
/// will be added as a trusted certificate.
|
||||||
|
/// If the path points to a directory,
|
||||||
|
/// the directory replaces the system's default certificate
|
||||||
|
/// store at `/etc/ssl/certs` - Every file in the directory
|
||||||
|
/// will be loaded as a trusted certificate.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub capath: Option<String>,
|
||||||
|
/// Bind domain to use for looking up users
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub bind_dn: Option<String>,
|
||||||
|
/// Custom LDAP search filter for user sync
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub filter: Option<String>,
|
||||||
|
/// Default options for AD sync
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub sync_defaults_options: Option<String>,
|
||||||
|
/// List of LDAP attributes to sync from AD to user config
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub sync_attributes: Option<String>,
|
||||||
|
/// User ``objectClass`` classes to sync
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub user_classes: Option<String>,
|
||||||
|
}
|
@ -130,6 +130,9 @@ pub use openid::*;
|
|||||||
mod ldap;
|
mod ldap;
|
||||||
pub use ldap::*;
|
pub use ldap::*;
|
||||||
|
|
||||||
|
mod ad;
|
||||||
|
pub use ad::*;
|
||||||
|
|
||||||
mod remote;
|
mod remote;
|
||||||
pub use remote::*;
|
pub use remote::*;
|
||||||
|
|
||||||
|
348
src/api2/config/access/ad.rs
Normal file
348
src/api2/config/access/ad.rs
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
use anyhow::{bail, format_err, Error};
|
||||||
|
use hex::FromHex;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use proxmox_ldap::{Config as LdapConfig, Connection};
|
||||||
|
use proxmox_router::{Permission, Router, RpcEnvironment};
|
||||||
|
use proxmox_schema::{api, param_bail};
|
||||||
|
|
||||||
|
use pbs_api_types::{
|
||||||
|
AdRealmConfig, AdRealmConfigUpdater, PRIV_REALM_ALLOCATE, PRIV_SYS_AUDIT,
|
||||||
|
PROXMOX_CONFIG_DIGEST_SCHEMA, REALM_ID_SCHEMA,
|
||||||
|
};
|
||||||
|
|
||||||
|
use pbs_config::domains;
|
||||||
|
|
||||||
|
use crate::{auth::AdAuthenticator, auth_helpers};
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
returns: {
|
||||||
|
description: "List of configured AD realms.",
|
||||||
|
type: Array,
|
||||||
|
items: { type: AdRealmConfig },
|
||||||
|
},
|
||||||
|
access: {
|
||||||
|
permission: &Permission::Privilege(&["access", "domains"], PRIV_REALM_ALLOCATE, false),
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
/// List configured AD realms
|
||||||
|
pub fn list_ad_realms(
|
||||||
|
_param: Value,
|
||||||
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
|
) -> Result<Vec<AdRealmConfig>, Error> {
|
||||||
|
let (config, digest) = domains::config()?;
|
||||||
|
|
||||||
|
let list = config.convert_to_typed_array("ad")?;
|
||||||
|
|
||||||
|
rpcenv["digest"] = hex::encode(digest).into();
|
||||||
|
|
||||||
|
Ok(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
protected: true,
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
config: {
|
||||||
|
type: AdRealmConfig,
|
||||||
|
flatten: true,
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
description: "AD bind password",
|
||||||
|
optional: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
access: {
|
||||||
|
permission: &Permission::Privilege(&["access", "domains"], PRIV_REALM_ALLOCATE, false),
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
/// Create a new AD realm
|
||||||
|
pub async fn create_ad_realm(
|
||||||
|
mut config: AdRealmConfig,
|
||||||
|
password: Option<String>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let domain_config_lock = domains::lock_config()?;
|
||||||
|
|
||||||
|
let (mut domains, _digest) = domains::config()?;
|
||||||
|
|
||||||
|
if domains::exists(&domains, &config.realm) {
|
||||||
|
param_bail!("realm", "realm '{}' already exists.", config.realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ldap_config =
|
||||||
|
AdAuthenticator::api_type_to_config_with_password(&config, password.clone())?;
|
||||||
|
|
||||||
|
if config.base_dn.is_none() {
|
||||||
|
ldap_config.base_dn = retrieve_default_naming_context(&ldap_config).await?;
|
||||||
|
config.base_dn = Some(ldap_config.base_dn.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let conn = Connection::new(ldap_config);
|
||||||
|
conn.check_connection()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format_err!("{e:#}"))?;
|
||||||
|
|
||||||
|
if let Some(password) = password {
|
||||||
|
auth_helpers::store_ldap_bind_password(&config.realm, &password, &domain_config_lock)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
domains.set_data(&config.realm, "ad", &config)?;
|
||||||
|
|
||||||
|
domains::save_config(&domains)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
realm: {
|
||||||
|
schema: REALM_ID_SCHEMA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
returns: { type: AdRealmConfig },
|
||||||
|
access: {
|
||||||
|
permission: &Permission::Privilege(&["access", "domains"], PRIV_SYS_AUDIT, false),
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
/// Read the AD realm configuration
|
||||||
|
pub fn read_ad_realm(
|
||||||
|
realm: String,
|
||||||
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
|
) -> Result<AdRealmConfig, Error> {
|
||||||
|
let (domains, digest) = domains::config()?;
|
||||||
|
|
||||||
|
let config = domains.lookup("ad", &realm)?;
|
||||||
|
|
||||||
|
rpcenv["digest"] = hex::encode(digest).into();
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api()]
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Deletable property name
|
||||||
|
pub enum DeletableProperty {
|
||||||
|
/// Fallback AD server address
|
||||||
|
Server2,
|
||||||
|
/// Port
|
||||||
|
Port,
|
||||||
|
/// Comment
|
||||||
|
Comment,
|
||||||
|
/// Verify server certificate
|
||||||
|
Verify,
|
||||||
|
/// Mode (ldap, ldap+starttls or ldaps),
|
||||||
|
Mode,
|
||||||
|
/// Bind Domain
|
||||||
|
BindDn,
|
||||||
|
/// LDAP bind passwort
|
||||||
|
Password,
|
||||||
|
/// User filter
|
||||||
|
Filter,
|
||||||
|
/// Default options for user sync
|
||||||
|
SyncDefaultsOptions,
|
||||||
|
/// user attributes to sync with AD attributes
|
||||||
|
SyncAttributes,
|
||||||
|
/// User classes
|
||||||
|
UserClasses,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
protected: true,
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
realm: {
|
||||||
|
schema: REALM_ID_SCHEMA,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
type: AdRealmConfigUpdater,
|
||||||
|
flatten: true,
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
description: "AD bind password",
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
description: "List of properties to delete.",
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
items: {
|
||||||
|
type: DeletableProperty,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
digest: {
|
||||||
|
optional: true,
|
||||||
|
schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
returns: { type: AdRealmConfig },
|
||||||
|
access: {
|
||||||
|
permission: &Permission::Privilege(&["access", "domains"], PRIV_REALM_ALLOCATE, false),
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
/// Update an AD realm configuration
|
||||||
|
pub async fn update_ad_realm(
|
||||||
|
realm: String,
|
||||||
|
update: AdRealmConfigUpdater,
|
||||||
|
password: Option<String>,
|
||||||
|
delete: Option<Vec<DeletableProperty>>,
|
||||||
|
digest: Option<String>,
|
||||||
|
_rpcenv: &mut dyn RpcEnvironment,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let domain_config_lock = domains::lock_config()?;
|
||||||
|
|
||||||
|
let (mut domains, expected_digest) = domains::config()?;
|
||||||
|
|
||||||
|
if let Some(ref digest) = digest {
|
||||||
|
let digest = <[u8; 32]>::from_hex(digest)?;
|
||||||
|
crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut config: AdRealmConfig = domains.lookup("ad", &realm)?;
|
||||||
|
|
||||||
|
if let Some(delete) = delete {
|
||||||
|
for delete_prop in delete {
|
||||||
|
match delete_prop {
|
||||||
|
DeletableProperty::Server2 => {
|
||||||
|
config.server2 = None;
|
||||||
|
}
|
||||||
|
DeletableProperty::Comment => {
|
||||||
|
config.comment = None;
|
||||||
|
}
|
||||||
|
DeletableProperty::Port => {
|
||||||
|
config.port = None;
|
||||||
|
}
|
||||||
|
DeletableProperty::Verify => {
|
||||||
|
config.verify = None;
|
||||||
|
}
|
||||||
|
DeletableProperty::Mode => {
|
||||||
|
config.mode = None;
|
||||||
|
}
|
||||||
|
DeletableProperty::BindDn => {
|
||||||
|
config.bind_dn = None;
|
||||||
|
}
|
||||||
|
DeletableProperty::Password => {
|
||||||
|
auth_helpers::remove_ldap_bind_password(&realm, &domain_config_lock)?;
|
||||||
|
}
|
||||||
|
DeletableProperty::Filter => {
|
||||||
|
config.filter = None;
|
||||||
|
}
|
||||||
|
DeletableProperty::SyncDefaultsOptions => {
|
||||||
|
config.sync_defaults_options = None;
|
||||||
|
}
|
||||||
|
DeletableProperty::SyncAttributes => {
|
||||||
|
config.sync_attributes = None;
|
||||||
|
}
|
||||||
|
DeletableProperty::UserClasses => {
|
||||||
|
config.user_classes = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(server1) = update.server1 {
|
||||||
|
config.server1 = server1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(server2) = update.server2 {
|
||||||
|
config.server2 = Some(server2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(port) = update.port {
|
||||||
|
config.port = Some(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(base_dn) = update.base_dn {
|
||||||
|
config.base_dn = Some(base_dn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(comment) = update.comment {
|
||||||
|
let comment = comment.trim().to_string();
|
||||||
|
if comment.is_empty() {
|
||||||
|
config.comment = None;
|
||||||
|
} else {
|
||||||
|
config.comment = Some(comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(mode) = update.mode {
|
||||||
|
config.mode = Some(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(verify) = update.verify {
|
||||||
|
config.verify = Some(verify);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(bind_dn) = update.bind_dn {
|
||||||
|
config.bind_dn = Some(bind_dn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(filter) = update.filter {
|
||||||
|
config.filter = Some(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(sync_defaults_options) = update.sync_defaults_options {
|
||||||
|
config.sync_defaults_options = Some(sync_defaults_options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(sync_attributes) = update.sync_attributes {
|
||||||
|
config.sync_attributes = Some(sync_attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(user_classes) = update.user_classes {
|
||||||
|
config.user_classes = Some(user_classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ldap_config = if password.is_some() {
|
||||||
|
AdAuthenticator::api_type_to_config_with_password(&config, password.clone())?
|
||||||
|
} else {
|
||||||
|
AdAuthenticator::api_type_to_config(&config)?
|
||||||
|
};
|
||||||
|
|
||||||
|
if config.base_dn.is_none() {
|
||||||
|
ldap_config.base_dn = retrieve_default_naming_context(&ldap_config).await?;
|
||||||
|
config.base_dn = Some(ldap_config.base_dn.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let conn = Connection::new(ldap_config);
|
||||||
|
conn.check_connection()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format_err!("{e:#}"))?;
|
||||||
|
|
||||||
|
if let Some(password) = password {
|
||||||
|
auth_helpers::store_ldap_bind_password(&realm, &password, &domain_config_lock)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
domains.set_data(&realm, "ad", &config)?;
|
||||||
|
|
||||||
|
domains::save_config(&domains)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn retrieve_default_naming_context(ldap_config: &LdapConfig) -> Result<String, Error> {
|
||||||
|
let conn = Connection::new(ldap_config.clone());
|
||||||
|
match conn.retrieve_root_dse_attr("defaultNamingContext").await {
|
||||||
|
Ok(base_dn) if !base_dn.is_empty() => Ok(base_dn[0].clone()),
|
||||||
|
Ok(_) => bail!("server did not provide `defaultNamingContext`"),
|
||||||
|
Err(err) => bail!("failed to determine base_dn: {err}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ITEM_ROUTER: Router = Router::new()
|
||||||
|
.get(&API_METHOD_READ_AD_REALM)
|
||||||
|
.put(&API_METHOD_UPDATE_AD_REALM)
|
||||||
|
.delete(&super::ldap::API_METHOD_DELETE_LDAP_REALM);
|
||||||
|
|
||||||
|
pub const ROUTER: Router = Router::new()
|
||||||
|
.get(&API_METHOD_LIST_AD_REALMS)
|
||||||
|
.post(&API_METHOD_CREATE_AD_REALM)
|
||||||
|
.match_all("realm", &ITEM_ROUTER);
|
@ -2,12 +2,14 @@ use proxmox_router::list_subdirs_api_method;
|
|||||||
use proxmox_router::{Router, SubdirMap};
|
use proxmox_router::{Router, SubdirMap};
|
||||||
use proxmox_sortable_macro::sortable;
|
use proxmox_sortable_macro::sortable;
|
||||||
|
|
||||||
|
pub mod ad;
|
||||||
pub mod ldap;
|
pub mod ldap;
|
||||||
pub mod openid;
|
pub mod openid;
|
||||||
pub mod tfa;
|
pub mod tfa;
|
||||||
|
|
||||||
#[sortable]
|
#[sortable]
|
||||||
const SUBDIRS: SubdirMap = &sorted!([
|
const SUBDIRS: SubdirMap = &sorted!([
|
||||||
|
("ad", &ad::ROUTER),
|
||||||
("ldap", &ldap::ROUTER),
|
("ldap", &ldap::ROUTER),
|
||||||
("openid", &openid::ROUTER),
|
("openid", &openid::ROUTER),
|
||||||
("tfa", &tfa::ROUTER),
|
("tfa", &tfa::ROUTER),
|
||||||
|
78
src/auth.rs
78
src/auth.rs
@ -19,7 +19,9 @@ use proxmox_auth_api::Keyring;
|
|||||||
use proxmox_ldap::{Config, Connection, ConnectionMode};
|
use proxmox_ldap::{Config, Connection, ConnectionMode};
|
||||||
use proxmox_tfa::api::{OpenUserChallengeData, TfaConfig};
|
use proxmox_tfa::api::{OpenUserChallengeData, TfaConfig};
|
||||||
|
|
||||||
use pbs_api_types::{LdapMode, LdapRealmConfig, OpenIdRealmConfig, RealmRef, Userid, UsernameRef};
|
use pbs_api_types::{
|
||||||
|
AdRealmConfig, LdapMode, LdapRealmConfig, OpenIdRealmConfig, RealmRef, Userid, UsernameRef,
|
||||||
|
};
|
||||||
use pbs_buildcfg::configdir;
|
use pbs_buildcfg::configdir;
|
||||||
|
|
||||||
use crate::auth_helpers;
|
use crate::auth_helpers;
|
||||||
@ -202,6 +204,80 @@ impl LdapAuthenticator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct AdAuthenticator {
|
||||||
|
config: AdRealmConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AdAuthenticator {
|
||||||
|
pub fn api_type_to_config(config: &AdRealmConfig) -> Result<Config, Error> {
|
||||||
|
Self::api_type_to_config_with_password(
|
||||||
|
config,
|
||||||
|
auth_helpers::get_ldap_bind_password(&config.realm)?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn api_type_to_config_with_password(
|
||||||
|
config: &AdRealmConfig,
|
||||||
|
password: Option<String>,
|
||||||
|
) -> Result<Config, Error> {
|
||||||
|
let mut servers = vec![config.server1.clone()];
|
||||||
|
if let Some(server) = &config.server2 {
|
||||||
|
servers.push(server.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (ca_store, trusted_cert) = lookup_ca_store_or_cert_path(config.capath.as_deref());
|
||||||
|
|
||||||
|
Ok(Config {
|
||||||
|
servers,
|
||||||
|
port: config.port,
|
||||||
|
user_attr: "sAMAccountName".to_owned(),
|
||||||
|
base_dn: config.base_dn.clone().unwrap_or_default(),
|
||||||
|
bind_dn: config.bind_dn.clone(),
|
||||||
|
bind_password: password,
|
||||||
|
tls_mode: ldap_to_conn_mode(config.mode.unwrap_or_default()),
|
||||||
|
verify_certificate: config.verify.unwrap_or_default(),
|
||||||
|
additional_trusted_certificates: trusted_cert,
|
||||||
|
certificate_store_path: ca_store,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Authenticator for AdAuthenticator {
|
||||||
|
/// Authenticate user in AD realm
|
||||||
|
fn authenticate_user<'a>(
|
||||||
|
&'a self,
|
||||||
|
username: &'a UsernameRef,
|
||||||
|
password: &'a str,
|
||||||
|
_client_ip: Option<&'a IpAddr>,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'a>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let ldap_config = Self::api_type_to_config(&self.config)?;
|
||||||
|
let ldap = Connection::new(ldap_config);
|
||||||
|
ldap.authenticate_user(username.as_str(), password).await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_password(
|
||||||
|
&self,
|
||||||
|
_username: &UsernameRef,
|
||||||
|
_password: &str,
|
||||||
|
_client_ip: Option<&IpAddr>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
http_bail!(
|
||||||
|
NOT_IMPLEMENTED,
|
||||||
|
"storing passwords is not implemented for Active Directory realms"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_password(&self, _username: &UsernameRef) -> Result<(), Error> {
|
||||||
|
http_bail!(
|
||||||
|
NOT_IMPLEMENTED,
|
||||||
|
"removing passwords is not implemented for Active Directory realms"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn ldap_to_conn_mode(mode: LdapMode) -> ConnectionMode {
|
fn ldap_to_conn_mode(mode: LdapMode) -> ConnectionMode {
|
||||||
match mode {
|
match mode {
|
||||||
LdapMode::Ldap => ConnectionMode::Ldap,
|
LdapMode::Ldap => ConnectionMode::Ldap,
|
||||||
|
Loading…
Reference in New Issue
Block a user