mirror of
https://github.com/nitnelave/lldap.git
synced 2023-04-12 14:25:13 +00:00
server: Add support for creating a user through LDAP
This commit is contained in:
parent
2477439ecc
commit
27144ee37e
@ -98,6 +98,16 @@ impl From<&JpegPhoto> for sea_query::Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[u8]> for JpegPhoto {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
fn try_from(bytes: &[u8]) -> anyhow::Result<Self> {
|
||||||
|
// Confirm that it's a valid Jpeg, then store only the bytes.
|
||||||
|
image::io::Reader::with_format(std::io::Cursor::new(bytes), image::ImageFormat::Jpeg)
|
||||||
|
.decode()?;
|
||||||
|
Ok(JpegPhoto(bytes.to_vec()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<Vec<u8>> for JpegPhoto {
|
impl TryFrom<Vec<u8>> for JpegPhoto {
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
fn try_from(bytes: Vec<u8>) -> anyhow::Result<Self> {
|
fn try_from(bytes: Vec<u8>) -> anyhow::Result<Self> {
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::{
|
domain::{
|
||||||
handler::{BackendHandler, BindRequest, LoginHandler, UserId},
|
handler::{
|
||||||
|
BackendHandler, BindRequest, CreateUserRequest, JpegPhoto, LoginHandler, UserId,
|
||||||
|
},
|
||||||
ldap::{
|
ldap::{
|
||||||
error::{LdapError, LdapResult},
|
error::{LdapError, LdapResult},
|
||||||
group::get_groups_list,
|
group::get_groups_list,
|
||||||
@ -15,8 +19,8 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use ldap3_proto::proto::{
|
use ldap3_proto::proto::{
|
||||||
LdapBindCred, LdapBindRequest, LdapBindResponse, LdapExtendedRequest, LdapExtendedResponse,
|
LdapAddRequest, LdapBindCred, LdapBindRequest, LdapBindResponse, LdapExtendedRequest,
|
||||||
LdapFilter, LdapOp, LdapPartialAttribute, LdapPasswordModifyRequest,
|
LdapExtendedResponse, LdapFilter, LdapOp, LdapPartialAttribute, LdapPasswordModifyRequest,
|
||||||
LdapResult as LdapResultOp, LdapResultCode, LdapSearchRequest, LdapSearchResultEntry,
|
LdapResult as LdapResultOp, LdapResultCode, LdapSearchRequest, LdapSearchResultEntry,
|
||||||
LdapSearchScope,
|
LdapSearchScope,
|
||||||
};
|
};
|
||||||
@ -82,6 +86,15 @@ fn make_search_error(code: LdapResultCode, message: String) -> LdapOp {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_add_error(code: LdapResultCode, message: String) -> LdapOp {
|
||||||
|
LdapOp::AddResponse(LdapResultOp {
|
||||||
|
code,
|
||||||
|
matcheddn: "".to_string(),
|
||||||
|
message,
|
||||||
|
referral: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn make_extended_response(code: LdapResultCode, message: String) -> LdapOp {
|
fn make_extended_response(code: LdapResultCode, message: String) -> LdapOp {
|
||||||
LdapOp::ExtendedResponse(LdapExtendedResponse {
|
LdapOp::ExtendedResponse(LdapExtendedResponse {
|
||||||
res: LdapResultOp {
|
res: LdapResultOp {
|
||||||
@ -432,6 +445,90 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
Ok(results)
|
Ok(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn do_create_user(&self, request: LdapAddRequest) -> LdapResult<Vec<LdapOp>> {
|
||||||
|
if !self
|
||||||
|
.user_info
|
||||||
|
.as_ref()
|
||||||
|
.map(|u| u.is_admin())
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return Err(LdapError {
|
||||||
|
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,
|
||||||
|
)?;
|
||||||
|
fn parse_attribute(mut attr: LdapPartialAttribute) -> LdapResult<(String, Vec<u8>)> {
|
||||||
|
if attr.vals.len() > 1 {
|
||||||
|
Err(LdapError {
|
||||||
|
code: LdapResultCode::ConstraintViolation,
|
||||||
|
message: format!("Expected a single value for attribute {}", attr.atype),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
attr.atype.make_ascii_lowercase();
|
||||||
|
match attr.vals.pop() {
|
||||||
|
Some(val) => Ok((attr.atype, val)),
|
||||||
|
None => Err(LdapError {
|
||||||
|
code: LdapResultCode::ConstraintViolation,
|
||||||
|
message: format!("Missing value for attribute {}", attr.atype),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let attributes: HashMap<String, Vec<u8>> = request
|
||||||
|
.attributes
|
||||||
|
.into_iter()
|
||||||
|
.map(parse_attribute)
|
||||||
|
.collect::<LdapResult<_>>()?;
|
||||||
|
fn decode_attribute_value(val: &[u8]) -> LdapResult<String> {
|
||||||
|
std::str::from_utf8(val)
|
||||||
|
.map_err(|e| LdapError {
|
||||||
|
code: LdapResultCode::ConstraintViolation,
|
||||||
|
message: format!(
|
||||||
|
"Attribute value is invalid UTF-8: {:#?} (value {:?})",
|
||||||
|
e, val
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.map(str::to_owned)
|
||||||
|
}
|
||||||
|
let get_attribute = |name| {
|
||||||
|
attributes
|
||||||
|
.get(name)
|
||||||
|
.map(Vec::as_slice)
|
||||||
|
.map(decode_attribute_value)
|
||||||
|
};
|
||||||
|
self.backend_handler
|
||||||
|
.create_user(CreateUserRequest {
|
||||||
|
user_id,
|
||||||
|
email: get_attribute("mail")
|
||||||
|
.or_else(|| get_attribute("email"))
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or_default(),
|
||||||
|
display_name: get_attribute("cn").transpose()?,
|
||||||
|
first_name: get_attribute("givenname").transpose()?,
|
||||||
|
last_name: get_attribute("sn").transpose()?,
|
||||||
|
avatar: attributes
|
||||||
|
.get("avatar")
|
||||||
|
.map(Vec::as_slice)
|
||||||
|
.map(JpegPhoto::try_from)
|
||||||
|
.transpose()
|
||||||
|
.map_err(|e| LdapError {
|
||||||
|
code: LdapResultCode::ConstraintViolation,
|
||||||
|
message: format!("Invalid JPEG photo: {:#?}", e),
|
||||||
|
})?,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| LdapError {
|
||||||
|
code: LdapResultCode::OperationsError,
|
||||||
|
message: format!("Could not create user: {:#?}", e),
|
||||||
|
})?;
|
||||||
|
Ok(vec![make_add_error(LdapResultCode::Success, String::new())])
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn handle_ldap_message(&mut self, ldap_op: LdapOp) -> Option<Vec<LdapOp>> {
|
pub async fn handle_ldap_message(&mut self, ldap_op: LdapOp) -> Option<Vec<LdapOp>> {
|
||||||
Some(match ldap_op {
|
Some(match ldap_op {
|
||||||
LdapOp::BindRequest(request) => {
|
LdapOp::BindRequest(request) => {
|
||||||
@ -456,6 +553,10 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
LdapOp::ExtendedRequest(request) => self.do_extended_request(&request).await,
|
LdapOp::ExtendedRequest(request) => self.do_extended_request(&request).await,
|
||||||
|
LdapOp::AddRequest(request) => self
|
||||||
|
.do_create_user(request)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|e: LdapError| vec![make_add_error(e.code, e.message)]),
|
||||||
op => vec![make_extended_response(
|
op => vec![make_extended_response(
|
||||||
LdapResultCode::UnwillingToPerform,
|
LdapResultCode::UnwillingToPerform,
|
||||||
format!("Unsupported operation: {:#?}", op),
|
format!("Unsupported operation: {:#?}", op),
|
||||||
@ -1930,4 +2031,46 @@ mod tests {
|
|||||||
])
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_user() {
|
||||||
|
let mut mock = MockTestBackendHandler::new();
|
||||||
|
mock.expect_create_user()
|
||||||
|
.with(eq(CreateUserRequest {
|
||||||
|
user_id: UserId::new("bob"),
|
||||||
|
email: "".to_owned(),
|
||||||
|
display_name: Some("Bob".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
}))
|
||||||
|
.times(1)
|
||||||
|
.return_once(|_| Ok(()));
|
||||||
|
let ldap_handler = setup_bound_admin_handler(mock).await;
|
||||||
|
let request = LdapAddRequest {
|
||||||
|
dn: "uid=bob,ou=people,dc=example,dc=com".to_owned(),
|
||||||
|
attributes: vec![LdapPartialAttribute {
|
||||||
|
atype: "cn".to_owned(),
|
||||||
|
vals: vec![b"Bob".to_vec()],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
ldap_handler.do_create_user(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;
|
||||||
|
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()],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
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() })
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user