mirror of
https://github.com/nitnelave/lldap.git
synced 2023-04-12 14:25:13 +00:00
server, ldap: Use group membership for admin status
This commit is contained in:
parent
5c1db3cf4a
commit
ebffc1c086
@ -286,20 +286,23 @@ fn root_dse_response(base_dn: &str) -> LdapOp {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
|
enum Permission {
|
||||||
|
Admin,
|
||||||
|
Regular,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct LdapHandler<Backend: BackendHandler + LoginHandler + OpaqueHandler> {
|
pub struct LdapHandler<Backend: BackendHandler + LoginHandler + OpaqueHandler> {
|
||||||
dn: LdapDn,
|
user_info: Option<(UserId, Permission)>,
|
||||||
user_id: UserId,
|
|
||||||
backend_handler: Backend,
|
backend_handler: Backend,
|
||||||
pub base_dn: Vec<(String, String)>,
|
pub base_dn: Vec<(String, String)>,
|
||||||
base_dn_str: String,
|
base_dn_str: String,
|
||||||
ldap_user_dn: LdapDn,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend> {
|
impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend> {
|
||||||
pub fn new(backend_handler: Backend, ldap_base_dn: String, ldap_user_dn: UserId) -> Self {
|
pub fn new(backend_handler: Backend, ldap_base_dn: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
dn: LdapDn("unauthenticated".to_string()),
|
user_info: None,
|
||||||
user_id: UserId::new("unauthenticated"),
|
|
||||||
backend_handler,
|
backend_handler,
|
||||||
base_dn: parse_distinguished_name(&ldap_base_dn).unwrap_or_else(|_| {
|
base_dn: parse_distinguished_name(&ldap_base_dn).unwrap_or_else(|_| {
|
||||||
panic!(
|
panic!(
|
||||||
@ -307,7 +310,6 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
ldap_base_dn
|
ldap_base_dn
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
ldap_user_dn: LdapDn(format!("uid={},ou=people,{}", ldap_user_dn, &ldap_base_dn)),
|
|
||||||
base_dn_str: ldap_base_dn,
|
base_dn_str: ldap_base_dn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -332,8 +334,20 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
self.dn = LdapDn(request.dn.clone());
|
let is_admin = self
|
||||||
self.user_id = user_id;
|
.backend_handler
|
||||||
|
.get_user_groups(&user_id)
|
||||||
|
.await
|
||||||
|
.map(|groups| groups.iter().any(|g| g.1 == "lldap_admin"))
|
||||||
|
.unwrap_or(false);
|
||||||
|
self.user_info = Some((
|
||||||
|
user_id,
|
||||||
|
if is_admin {
|
||||||
|
Permission::Admin
|
||||||
|
} else {
|
||||||
|
Permission::Regular
|
||||||
|
},
|
||||||
|
));
|
||||||
(LdapResultCode::Success, "".to_string())
|
(LdapResultCode::Success, "".to_string())
|
||||||
}
|
}
|
||||||
Err(_) => (LdapResultCode::InvalidCredentials, "".to_string()),
|
Err(_) => (LdapResultCode::InvalidCredentials, "".to_string()),
|
||||||
@ -367,10 +381,28 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
&mut self,
|
&mut self,
|
||||||
request: &LdapPasswordModifyRequest,
|
request: &LdapPasswordModifyRequest,
|
||||||
) -> Vec<LdapOp> {
|
) -> Vec<LdapOp> {
|
||||||
|
let (user_id, permission) = match &self.user_info {
|
||||||
|
Some(info) => info,
|
||||||
|
_ => {
|
||||||
|
return vec![make_search_error(
|
||||||
|
LdapResultCode::InsufficentAccessRights,
|
||||||
|
"No user currently bound".to_string(),
|
||||||
|
)];
|
||||||
|
}
|
||||||
|
};
|
||||||
match (&request.user_identity, &request.new_password) {
|
match (&request.user_identity, &request.new_password) {
|
||||||
(Some(user), Some(password)) => {
|
(Some(user), Some(password)) => {
|
||||||
match get_user_id_from_distinguished_name(user, &self.base_dn, &self.base_dn_str) {
|
match get_user_id_from_distinguished_name(user, &self.base_dn, &self.base_dn_str) {
|
||||||
Ok(uid) => {
|
Ok(uid) => {
|
||||||
|
if *permission != Permission::Admin && user_id != &uid {
|
||||||
|
return vec![make_search_error(
|
||||||
|
LdapResultCode::InsufficentAccessRights,
|
||||||
|
format!(
|
||||||
|
r#"User {} cannot modify the password of user {}"#,
|
||||||
|
&user_id, &uid
|
||||||
|
),
|
||||||
|
)];
|
||||||
|
}
|
||||||
if let Err(e) = self.change_password(&uid, password).await {
|
if let Err(e) = self.change_password(&uid, password).await {
|
||||||
vec![make_extended_response(
|
vec![make_extended_response(
|
||||||
LdapResultCode::Other,
|
LdapResultCode::Other,
|
||||||
@ -407,7 +439,16 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn do_search(&mut self, request: &LdapSearchRequest) -> Vec<LdapOp> {
|
pub async fn do_search(&mut self, request: &LdapSearchRequest) -> Vec<LdapOp> {
|
||||||
let admin = self.dn == self.ldap_user_dn;
|
let user_filter = match &self.user_info {
|
||||||
|
Some((_, Permission::Admin)) => None,
|
||||||
|
Some((user_id, Permission::Regular)) => Some(user_id),
|
||||||
|
None => {
|
||||||
|
return vec![make_search_error(
|
||||||
|
LdapResultCode::InsufficentAccessRights,
|
||||||
|
"No user currently bound".to_string(),
|
||||||
|
)];
|
||||||
|
}
|
||||||
|
};
|
||||||
if request.base.is_empty()
|
if request.base.is_empty()
|
||||||
&& request.scope == LdapSearchScope::Base
|
&& request.scope == LdapSearchScope::Base
|
||||||
&& request.filter == LdapFilter::Present("objectClass".to_string())
|
&& request.filter == LdapFilter::Present("objectClass".to_string())
|
||||||
@ -435,7 +476,6 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
}
|
}
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
let mut got_match = false;
|
let mut got_match = false;
|
||||||
let user_filter = if admin { None } else { Some(&self.user_id) };
|
|
||||||
if dn_parts.len() == self.base_dn.len()
|
if dn_parts.len() == self.base_dn.len()
|
||||||
|| (dn_parts.len() == self.base_dn.len() + 1
|
|| (dn_parts.len() == self.base_dn.len() + 1
|
||||||
&& dn_parts[0] == ("ou".to_string(), "people".to_string()))
|
&& dn_parts[0] == ("ou".to_string(), "people".to_string()))
|
||||||
@ -573,8 +613,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
}
|
}
|
||||||
LdapOp::SearchRequest(request) => self.do_search(&request).await,
|
LdapOp::SearchRequest(request) => self.do_search(&request).await,
|
||||||
LdapOp::UnbindRequest => {
|
LdapOp::UnbindRequest => {
|
||||||
self.dn = LdapDn("unauthenticated".to_string());
|
self.user_info = None;
|
||||||
self.user_id = UserId::new("unauthenticated");
|
|
||||||
// No need to notify on unbind (per rfc4511)
|
// No need to notify on unbind (per rfc4511)
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -779,8 +818,14 @@ mod tests {
|
|||||||
password: "pass".to_string(),
|
password: "pass".to_string(),
|
||||||
}))
|
}))
|
||||||
.return_once(|_| Ok(()));
|
.return_once(|_| Ok(()));
|
||||||
let mut ldap_handler =
|
mock.expect_get_user_groups()
|
||||||
LdapHandler::new(mock, "dc=example,dc=com".to_string(), UserId::new("test"));
|
.with(eq(UserId::new("test")))
|
||||||
|
.return_once(|_| {
|
||||||
|
let mut set = HashSet::new();
|
||||||
|
set.insert(GroupIdAndName(GroupId(42), "lldap_admin".to_string()));
|
||||||
|
Ok(set)
|
||||||
|
});
|
||||||
|
let mut ldap_handler = LdapHandler::new(mock, "dc=example,dc=com".to_string());
|
||||||
let request = LdapBindRequest {
|
let request = LdapBindRequest {
|
||||||
dn: "uid=test,ou=people,dc=example,dc=com".to_string(),
|
dn: "uid=test,ou=people,dc=example,dc=com".to_string(),
|
||||||
cred: LdapBindCred::Simple("pass".to_string()),
|
cred: LdapBindCred::Simple("pass".to_string()),
|
||||||
@ -802,8 +847,10 @@ mod tests {
|
|||||||
}))
|
}))
|
||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_| Ok(()));
|
.return_once(|_| Ok(()));
|
||||||
let mut ldap_handler =
|
mock.expect_get_user_groups()
|
||||||
LdapHandler::new(mock, "dc=example,dc=com".to_string(), UserId::new("test"));
|
.with(eq(UserId::new("bob")))
|
||||||
|
.return_once(|_| Ok(HashSet::new()));
|
||||||
|
let mut ldap_handler = LdapHandler::new(mock, "dc=example,dc=com".to_string());
|
||||||
|
|
||||||
let request = LdapOp::BindRequest(LdapBindRequest {
|
let request = LdapOp::BindRequest(LdapBindRequest {
|
||||||
dn: "uid=bob,ou=people,dc=example,dc=com".to_string(),
|
dn: "uid=bob,ou=people,dc=example,dc=com".to_string(),
|
||||||
@ -833,8 +880,14 @@ mod tests {
|
|||||||
}))
|
}))
|
||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_| Ok(()));
|
.return_once(|_| Ok(()));
|
||||||
let mut ldap_handler =
|
mock.expect_get_user_groups()
|
||||||
LdapHandler::new(mock, "dc=example,dc=com".to_string(), UserId::new("test"));
|
.with(eq(UserId::new("test")))
|
||||||
|
.return_once(|_| {
|
||||||
|
let mut set = HashSet::new();
|
||||||
|
set.insert(GroupIdAndName(GroupId(42), "lldap_admin".to_string()));
|
||||||
|
Ok(set)
|
||||||
|
});
|
||||||
|
let mut ldap_handler = LdapHandler::new(mock, "dc=example,dc=com".to_string());
|
||||||
|
|
||||||
let request = LdapBindRequest {
|
let request = LdapBindRequest {
|
||||||
dn: "uid=test,ou=people,dc=example,dc=com".to_string(),
|
dn: "uid=test,ou=people,dc=example,dc=com".to_string(),
|
||||||
@ -868,8 +921,10 @@ mod tests {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}])
|
}])
|
||||||
});
|
});
|
||||||
let mut ldap_handler =
|
mock.expect_get_user_groups()
|
||||||
LdapHandler::new(mock, "dc=example,dc=com".to_string(), UserId::new("admin"));
|
.with(eq(UserId::new("test")))
|
||||||
|
.return_once(|_| Ok(HashSet::new()));
|
||||||
|
let mut ldap_handler = LdapHandler::new(mock, "dc=example,dc=com".to_string());
|
||||||
|
|
||||||
let request = LdapBindRequest {
|
let request = LdapBindRequest {
|
||||||
dn: "uid=test,ou=people,dc=example,dc=com".to_string(),
|
dn: "uid=test,ou=people,dc=example,dc=com".to_string(),
|
||||||
@ -897,8 +952,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_bind_invalid_dn() {
|
async fn test_bind_invalid_dn() {
|
||||||
let mock = MockTestBackendHandler::new();
|
let mock = MockTestBackendHandler::new();
|
||||||
let mut ldap_handler =
|
let mut ldap_handler = LdapHandler::new(mock, "dc=example,dc=com".to_string());
|
||||||
LdapHandler::new(mock, "dc=example,dc=com".to_string(), UserId::new("admin"));
|
|
||||||
|
|
||||||
let request = LdapBindRequest {
|
let request = LdapBindRequest {
|
||||||
dn: "cn=bob,dc=example,dc=com".to_string(),
|
dn: "cn=bob,dc=example,dc=com".to_string(),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
domain::{
|
domain::{
|
||||||
handler::{BackendHandler, LoginHandler, UserId},
|
handler::{BackendHandler, LoginHandler},
|
||||||
opaque_handler::OpaqueHandler,
|
opaque_handler::OpaqueHandler,
|
||||||
},
|
},
|
||||||
infra::{configuration::Configuration, ldap_handler::LdapHandler},
|
infra::{configuration::Configuration, ldap_handler::LdapHandler},
|
||||||
@ -70,7 +70,6 @@ async fn handle_ldap_stream<Stream, Backend>(
|
|||||||
stream: Stream,
|
stream: Stream,
|
||||||
backend_handler: Backend,
|
backend_handler: Backend,
|
||||||
ldap_base_dn: String,
|
ldap_base_dn: String,
|
||||||
ldap_user_dn: UserId,
|
|
||||||
) -> Result<Stream>
|
) -> Result<Stream>
|
||||||
where
|
where
|
||||||
Backend: BackendHandler + LoginHandler + OpaqueHandler + 'static,
|
Backend: BackendHandler + LoginHandler + OpaqueHandler + 'static,
|
||||||
@ -82,7 +81,7 @@ where
|
|||||||
let mut requests = FramedRead::new(r, LdapCodec);
|
let mut requests = FramedRead::new(r, LdapCodec);
|
||||||
let mut resp = FramedWrite::new(w, LdapCodec);
|
let mut resp = FramedWrite::new(w, LdapCodec);
|
||||||
|
|
||||||
let mut session = LdapHandler::new(backend_handler, ldap_base_dn, ldap_user_dn);
|
let mut session = LdapHandler::new(backend_handler, ldap_base_dn);
|
||||||
|
|
||||||
while let Some(msg) = requests.next().await {
|
while let Some(msg) = requests.next().await {
|
||||||
if !handle_incoming_message(msg, &mut resp, &mut session)
|
if !handle_incoming_message(msg, &mut resp, &mut session)
|
||||||
@ -111,11 +110,7 @@ pub fn build_ldap_server<Backend>(
|
|||||||
where
|
where
|
||||||
Backend: BackendHandler + LoginHandler + OpaqueHandler + 'static,
|
Backend: BackendHandler + LoginHandler + OpaqueHandler + 'static,
|
||||||
{
|
{
|
||||||
let context = (
|
let context = (backend_handler, config.ldap_base_dn.clone());
|
||||||
backend_handler,
|
|
||||||
config.ldap_base_dn.clone(),
|
|
||||||
config.ldap_user_dn.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let context_for_tls = context.clone();
|
let context_for_tls = context.clone();
|
||||||
|
|
||||||
@ -124,8 +119,8 @@ where
|
|||||||
fn_service(move |stream: TcpStream| {
|
fn_service(move |stream: TcpStream| {
|
||||||
let context = context.clone();
|
let context = context.clone();
|
||||||
async move {
|
async move {
|
||||||
let (handler, base_dn, user_dn) = context;
|
let (handler, base_dn) = context;
|
||||||
handle_ldap_stream(stream, handler, base_dn, user_dn).await
|
handle_ldap_stream(stream, handler, base_dn).await
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map_err(|err: anyhow::Error| error!("[LDAP] Service Error: {:#}", err))
|
.map_err(|err: anyhow::Error| error!("[LDAP] Service Error: {:#}", err))
|
||||||
@ -144,9 +139,9 @@ where
|
|||||||
fn_service(move |stream: TcpStream| {
|
fn_service(move |stream: TcpStream| {
|
||||||
let tls_context = tls_context.clone();
|
let tls_context = tls_context.clone();
|
||||||
async move {
|
async move {
|
||||||
let ((handler, base_dn, user_dn), tls_acceptor) = tls_context;
|
let ((handler, base_dn), tls_acceptor) = tls_context;
|
||||||
let tls_stream = tls_acceptor.accept(stream).await?;
|
let tls_stream = tls_acceptor.accept(stream).await?;
|
||||||
handle_ldap_stream(tls_stream, handler, base_dn, user_dn).await
|
handle_ldap_stream(tls_stream, handler, base_dn).await
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map_err(|err: anyhow::Error| error!("[LDAPS] Service Error: {:#}", err))
|
.map_err(|err: anyhow::Error| error!("[LDAPS] Service Error: {:#}", err))
|
||||||
|
Loading…
Reference in New Issue
Block a user