feat: Implement LDAP bind/whoami
This commit is contained in:
parent
bdb5008914
commit
b4330b37fa
19 changed files with 664 additions and 37 deletions
129
src/ldap/op/bind.rs
Normal file
129
src/ldap/op/bind.rs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
use ldap3_proto::proto::{LdapBindCred, LdapBindRequest, LdapBindResponse, LdapOp, LdapResult};
|
||||
use ldap3_proto::{LdapMsg, LdapResultCode};
|
||||
|
||||
use crate::db::{Database, DatabaseInterface};
|
||||
use crate::ldap::{
|
||||
Dn, InvalidDnError, LdapReturnError, LdapStream, LdapStreamError, NotUserDnError,
|
||||
};
|
||||
|
||||
pub enum BindError {
|
||||
// TODO: DB Error
|
||||
InvalidCredentials,
|
||||
InvalidDn(InvalidDnError),
|
||||
NotUserDn(NotUserDnError),
|
||||
UnsupportedSASL,
|
||||
}
|
||||
|
||||
impl BindError {
|
||||
pub async fn error_message(
|
||||
&self,
|
||||
stream: &mut LdapStream,
|
||||
msgid: i32,
|
||||
) -> Result<(), LdapStreamError> {
|
||||
let resp_msg = LdapMsg {
|
||||
msgid,
|
||||
op: LdapOp::BindResponse(LdapBindResponse {
|
||||
res: LdapResult {
|
||||
code: self.code(),
|
||||
matcheddn: String::new(),
|
||||
message: self.message(),
|
||||
referral: vec![],
|
||||
},
|
||||
saslcreds: None,
|
||||
}),
|
||||
ctrl: vec![],
|
||||
};
|
||||
|
||||
stream.send(resp_msg).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InvalidDnError> for BindError {
|
||||
fn from(e: InvalidDnError) -> Self {
|
||||
Self::InvalidDn(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl LdapReturnError for BindError {
|
||||
fn code(&self) -> LdapResultCode {
|
||||
match self {
|
||||
Self::InvalidCredentials => LdapResultCode::InvalidCredentials,
|
||||
Self::InvalidDn(e) => e.code(),
|
||||
Self::NotUserDn(e) => e.code(),
|
||||
Self::UnsupportedSASL => LdapResultCode::OperationsError,
|
||||
}
|
||||
}
|
||||
|
||||
fn message(&self) -> String {
|
||||
match self {
|
||||
Self::InvalidCredentials => "Wrong username or password".to_string(),
|
||||
Self::InvalidDn(e) => e.message(),
|
||||
Self::NotUserDn(e) => e.message(),
|
||||
Self::UnsupportedSASL => "SASL login is not supported".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn bind_success(stream: &mut LdapStream, msgid: i32) -> Result<(), LdapStreamError> {
|
||||
let resp_msg = LdapMsg {
|
||||
msgid,
|
||||
op: LdapOp::BindResponse(LdapBindResponse {
|
||||
res: LdapResult {
|
||||
code: LdapResultCode::Success,
|
||||
matcheddn: String::new(),
|
||||
message: String::new(),
|
||||
referral: vec![],
|
||||
},
|
||||
saslcreds: None,
|
||||
}),
|
||||
ctrl: vec![],
|
||||
};
|
||||
|
||||
stream.send(resp_msg).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tries to bind the user.
|
||||
///
|
||||
/// On success, returns `Ok(Some(bound_dn))`. `Ok(None)` means credentials failed,
|
||||
/// either because the account does not exist, or the password is wrong.
|
||||
pub async fn op_bind<D: DatabaseInterface>(
|
||||
stream: &mut LdapStream,
|
||||
db: &Database<D>,
|
||||
req: LdapBindRequest,
|
||||
msgid: i32,
|
||||
) -> Result<Option<Dn>, LdapStreamError> {
|
||||
let dn = match Dn::from_dn_str(&req.dn) {
|
||||
Ok(dn) => dn,
|
||||
Err(e) => {
|
||||
BindError::InvalidDn(e).error_message(stream, msgid).await?;
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
let LdapBindCred::Simple(password) = req.cred else {
|
||||
BindError::UnsupportedSASL
|
||||
.error_message(stream, msgid)
|
||||
.await?;
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let user_ref = match dn.to_user_ref() {
|
||||
Ok(user_ref) => user_ref,
|
||||
Err(e) => {
|
||||
BindError::NotUserDn(e).error_message(stream, msgid).await?;
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
if db.check_password(&user_ref, &password).await {
|
||||
bind_success(stream, msgid).await?;
|
||||
Ok(Some(dn))
|
||||
} else {
|
||||
BindError::InvalidCredentials
|
||||
.error_message(stream, msgid)
|
||||
.await?;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue