mirror of
https://github.com/lldap/lldap.git
synced 2026-03-31 15:07:48 +01:00
ldap: add support for creating groups
This commit is contained in:
committed by
nitnelave
parent
31364da6d4
commit
b8f114bd43
@@ -31,9 +31,9 @@ where
|
||||
Ok(pair)
|
||||
}
|
||||
})()
|
||||
.map_err(|s| LdapError {
|
||||
.map_err(|e| LdapError {
|
||||
code: LdapResultCode::InvalidDNSyntax,
|
||||
message: s,
|
||||
message: e,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -44,38 +44,53 @@ pub fn parse_distinguished_name(dn: &str) -> LdapResult<Vec<(String, String)>> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_id_from_distinguished_name(
|
||||
dn: &str,
|
||||
base_tree: &[(String, String)],
|
||||
base_dn_str: &str,
|
||||
is_group: bool,
|
||||
) -> LdapResult<String> {
|
||||
let parts = parse_distinguished_name(dn)?;
|
||||
{
|
||||
let ou = if is_group { "groups" } else { "people" };
|
||||
if !is_subtree(&parts, base_tree) {
|
||||
Err("Not a subtree of the base tree".to_string())
|
||||
} else if parts.len() == base_tree.len() + 2 {
|
||||
if parts[1].0 != "ou" || parts[1].1 != ou || (parts[0].0 != "cn" && parts[0].0 != "uid")
|
||||
{
|
||||
Err(format!(
|
||||
r#"Unexpected DN format. Got "{}", expected: "uid=id,ou={},{}""#,
|
||||
dn, ou, base_dn_str
|
||||
))
|
||||
} else {
|
||||
Ok(parts[0].1.to_string())
|
||||
}
|
||||
} else {
|
||||
Err(format!(
|
||||
r#"Unexpected DN format. Got "{}", expected: "uid=id,ou={},{}""#,
|
||||
dn, ou, base_dn_str
|
||||
))
|
||||
pub enum UserOrGroupName {
|
||||
User(UserId),
|
||||
Group(GroupName),
|
||||
BadSubStree,
|
||||
UnexpectedFormat,
|
||||
InvalidSyntax(LdapError),
|
||||
}
|
||||
|
||||
impl UserOrGroupName {
|
||||
pub fn into_ldap_error(self, input: &str, expected_format: String) -> LdapError {
|
||||
LdapError {
|
||||
code: LdapResultCode::InvalidDNSyntax,
|
||||
message: match self {
|
||||
UserOrGroupName::BadSubStree => "Not a subtree of the base tree".to_string(),
|
||||
UserOrGroupName::InvalidSyntax(err) => return err,
|
||||
UserOrGroupName::UnexpectedFormat
|
||||
| UserOrGroupName::User(_)
|
||||
| UserOrGroupName::Group(_) => format!(
|
||||
r#"Unexpected DN format. Got "{}", expected: {}"#,
|
||||
input, expected_format
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
.map_err(|s| LdapError {
|
||||
code: LdapResultCode::InvalidDNSyntax,
|
||||
message: s,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_user_or_group_id_from_distinguished_name(
|
||||
dn: &str,
|
||||
base_tree: &[(String, String)],
|
||||
) -> UserOrGroupName {
|
||||
let parts = match parse_distinguished_name(dn) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return UserOrGroupName::InvalidSyntax(e),
|
||||
};
|
||||
if !is_subtree(&parts, base_tree) {
|
||||
return UserOrGroupName::BadSubStree;
|
||||
} else if parts.len() == base_tree.len() + 2
|
||||
&& parts[1].0 == "ou"
|
||||
&& (parts[0].0 == "cn" || parts[0].0 == "uid")
|
||||
{
|
||||
if parts[1].1 == "groups" {
|
||||
return UserOrGroupName::Group(GroupName::from(parts[0].1.clone()));
|
||||
} else if parts[1].1 == "people" {
|
||||
return UserOrGroupName::User(UserId::from(parts[0].1.clone()));
|
||||
}
|
||||
}
|
||||
UserOrGroupName::UnexpectedFormat
|
||||
}
|
||||
|
||||
pub fn get_user_id_from_distinguished_name(
|
||||
@@ -83,7 +98,10 @@ pub fn get_user_id_from_distinguished_name(
|
||||
base_tree: &[(String, String)],
|
||||
base_dn_str: &str,
|
||||
) -> LdapResult<UserId> {
|
||||
get_id_from_distinguished_name(dn, base_tree, base_dn_str, false).map(UserId::from)
|
||||
match get_user_or_group_id_from_distinguished_name(dn, base_tree) {
|
||||
UserOrGroupName::User(user_id) => Ok(user_id),
|
||||
err => Err(err.into_ldap_error(dn, format!(r#""uid=id,ou=people,{}""#, base_dn_str))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_group_id_from_distinguished_name(
|
||||
@@ -91,7 +109,10 @@ pub fn get_group_id_from_distinguished_name(
|
||||
base_tree: &[(String, String)],
|
||||
base_dn_str: &str,
|
||||
) -> LdapResult<GroupName> {
|
||||
get_id_from_distinguished_name(dn, base_tree, base_dn_str, true).map(GroupName::from)
|
||||
match get_user_or_group_id_from_distinguished_name(dn, base_tree) {
|
||||
UserOrGroupName::Group(group_name) => Ok(group_name),
|
||||
err => Err(err.into_ldap_error(dn, format!(r#""uid=id,ou=groups,{}""#, base_dn_str))),
|
||||
}
|
||||
}
|
||||
|
||||
fn looks_like_distinguished_name(dn: &str) -> bool {
|
||||
|
||||
@@ -6,7 +6,8 @@ use crate::{
|
||||
group::{convert_groups_to_ldap_op, get_groups_list},
|
||||
user::{convert_users_to_ldap_op, get_user_list},
|
||||
utils::{
|
||||
LdapInfo, get_user_id_from_distinguished_name, is_subtree, parse_distinguished_name,
|
||||
LdapInfo, UserOrGroupName, get_user_id_from_distinguished_name,
|
||||
get_user_or_group_id_from_distinguished_name, is_subtree, parse_distinguished_name,
|
||||
},
|
||||
},
|
||||
opaque_handler::OpaqueHandler,
|
||||
@@ -19,16 +20,18 @@ use crate::{
|
||||
};
|
||||
use anyhow::Result;
|
||||
use ldap3_proto::proto::{
|
||||
LdapAddRequest, LdapBindCred, LdapBindRequest, LdapBindResponse, LdapCompareRequest,
|
||||
LdapDerefAliases, LdapExtendedRequest, LdapExtendedResponse, LdapFilter, LdapModify,
|
||||
LdapModifyRequest, LdapModifyType, LdapOp, LdapPartialAttribute, LdapPasswordModifyRequest,
|
||||
LdapResult as LdapResultOp, LdapResultCode, LdapSearchRequest, LdapSearchResultEntry,
|
||||
LdapSearchScope, OID_PASSWORD_MODIFY, OID_WHOAMI,
|
||||
LdapAddRequest, LdapAttribute, LdapBindCred, LdapBindRequest, LdapBindResponse,
|
||||
LdapCompareRequest, LdapDerefAliases, LdapExtendedRequest, LdapExtendedResponse, LdapFilter,
|
||||
LdapModify, LdapModifyRequest, LdapModifyType, LdapOp, LdapPartialAttribute,
|
||||
LdapPasswordModifyRequest, LdapResult as LdapResultOp, LdapResultCode, LdapSearchRequest,
|
||||
LdapSearchResultEntry, LdapSearchScope, OID_PASSWORD_MODIFY, OID_WHOAMI,
|
||||
};
|
||||
use lldap_auth::access_control::ValidationResults;
|
||||
use lldap_domain::{
|
||||
requests::CreateUserRequest,
|
||||
types::{Attribute, AttributeName, AttributeType, Email, Group, UserAndGroups, UserId},
|
||||
requests::{CreateGroupRequest, CreateUserRequest},
|
||||
types::{
|
||||
Attribute, AttributeName, AttributeType, Email, Group, GroupName, UserAndGroups, UserId,
|
||||
},
|
||||
};
|
||||
use lldap_domain_handlers::handler::{
|
||||
BackendHandler, BindRequest, LoginHandler, ReadSchemaBackendHandler,
|
||||
@@ -721,7 +724,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "debug")]
|
||||
async fn do_create_user(&self, request: LdapAddRequest) -> LdapResult<Vec<LdapOp>> {
|
||||
async fn do_create_user_or_group(&self, request: LdapAddRequest) -> LdapResult<Vec<LdapOp>> {
|
||||
let backend_handler = self
|
||||
.user_info
|
||||
.as_ref()
|
||||
@@ -730,11 +733,33 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
||||
code: LdapResultCode::InsufficentAccessRights,
|
||||
message: "Unauthorized write".to_string(),
|
||||
})?;
|
||||
let user_id = get_user_id_from_distinguished_name(
|
||||
&request.dn,
|
||||
&self.ldap_info.base_dn,
|
||||
&self.ldap_info.base_dn_str,
|
||||
)?;
|
||||
let base_dn_str = &self.ldap_info.base_dn_str;
|
||||
match get_user_or_group_id_from_distinguished_name(&request.dn, &self.ldap_info.base_dn) {
|
||||
UserOrGroupName::User(user_id) => {
|
||||
self.do_create_user(backend_handler, user_id, request.attributes)
|
||||
.await
|
||||
}
|
||||
UserOrGroupName::Group(group_name) => {
|
||||
self.do_create_group(backend_handler, group_name, request.attributes)
|
||||
.await
|
||||
}
|
||||
err => Err(err.into_ldap_error(
|
||||
&request.dn,
|
||||
format!(
|
||||
r#""uid=id,ou=people,{}" or "uid=id,ou=groups,{}""#,
|
||||
base_dn_str, base_dn_str
|
||||
),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "debug")]
|
||||
async fn do_create_user(
|
||||
&self,
|
||||
backend_handler: &impl AdminBackendHandler,
|
||||
user_id: UserId,
|
||||
attributes: Vec<LdapAttribute>,
|
||||
) -> LdapResult<Vec<LdapOp>> {
|
||||
fn parse_attribute(mut attr: LdapPartialAttribute) -> LdapResult<(String, Vec<u8>)> {
|
||||
if attr.vals.len() > 1 {
|
||||
Err(LdapError {
|
||||
@@ -752,8 +777,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
||||
}
|
||||
}
|
||||
}
|
||||
let attributes: HashMap<String, Vec<u8>> = request
|
||||
.attributes
|
||||
let attributes: HashMap<String, Vec<u8>> = attributes
|
||||
.into_iter()
|
||||
.filter(|a| !a.atype.eq_ignore_ascii_case("objectclass"))
|
||||
.map(parse_attribute)
|
||||
@@ -828,6 +852,26 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
||||
Ok(vec![make_add_error(LdapResultCode::Success, String::new())])
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "debug")]
|
||||
async fn do_create_group(
|
||||
&self,
|
||||
backend_handler: &impl AdminBackendHandler,
|
||||
group_name: GroupName,
|
||||
_attributes: Vec<LdapAttribute>,
|
||||
) -> LdapResult<Vec<LdapOp>> {
|
||||
backend_handler
|
||||
.create_group(CreateGroupRequest {
|
||||
display_name: group_name,
|
||||
attributes: Vec::new(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| LdapError {
|
||||
code: LdapResultCode::OperationsError,
|
||||
message: format!("Could not create group: {:#?}", e),
|
||||
})?;
|
||||
Ok(vec![make_add_error(LdapResultCode::Success, String::new())])
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "debug")]
|
||||
pub async fn do_compare(&mut self, request: LdapCompareRequest) -> LdapResult<Vec<LdapOp>> {
|
||||
let req = make_search_request::<String>(
|
||||
@@ -911,7 +955,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
||||
LdapOp::ModifyRequest(request) => self.do_modify_request(&request).await,
|
||||
LdapOp::ExtendedRequest(request) => self.do_extended_request(&request).await,
|
||||
LdapOp::AddRequest(request) => self
|
||||
.do_create_user(request)
|
||||
.do_create_user_or_group(request)
|
||||
.await
|
||||
.unwrap_or_else(|e: LdapError| vec![make_add_error(e.code, e.message)]),
|
||||
LdapOp::CompareRequest(request) => self
|
||||
@@ -2797,24 +2841,32 @@ mod tests {
|
||||
}],
|
||||
};
|
||||
assert_eq!(
|
||||
ldap_handler.do_create_user(request).await,
|
||||
ldap_handler.do_create_user_or_group(request).await,
|
||||
Ok(vec![make_add_error(LdapResultCode::Success, String::new())])
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_user_wrong_ou() {
|
||||
let ldap_handler = setup_bound_admin_handler(MockTestBackendHandler::new()).await;
|
||||
async fn test_create_group() {
|
||||
let mut mock = MockTestBackendHandler::new();
|
||||
mock.expect_create_group()
|
||||
.with(eq(CreateGroupRequest {
|
||||
display_name: GroupName::new("bob"),
|
||||
..Default::default()
|
||||
}))
|
||||
.times(1)
|
||||
.return_once(|_| Ok(GroupId(5)));
|
||||
let ldap_handler = setup_bound_admin_handler(mock).await;
|
||||
let request = LdapAddRequest {
|
||||
dn: "uid=bob,ou=groups,dc=example,dc=com".to_owned(),
|
||||
attributes: vec![LdapPartialAttribute {
|
||||
atype: "cn".to_owned(),
|
||||
vals: vec![b"Bob".to_vec()],
|
||||
vals: vec![b"Bobby".to_vec()],
|
||||
}],
|
||||
};
|
||||
assert_eq!(
|
||||
ldap_handler.do_create_user(request).await,
|
||||
Err(LdapError{ code: LdapResultCode::InvalidDNSyntax, message: r#"Unexpected DN format. Got "uid=bob,ou=groups,dc=example,dc=com", expected: "uid=id,ou=people,dc=example,dc=com""#.to_string() })
|
||||
ldap_handler.do_create_user_or_group(request).await,
|
||||
Ok(vec![make_add_error(LdapResultCode::Success, String::new())])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2849,7 +2901,7 @@ mod tests {
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
ldap_handler.do_create_user(request).await,
|
||||
ldap_handler.do_create_user_or_group(request).await,
|
||||
Ok(vec![make_add_error(LdapResultCode::Success, String::new())])
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user