Closed johanfleury closed 4 years ago
Using the Password Modify exop is indeed the cleanest option if your LDAP infrastructure is set up to support it. Your sketch of a Password Modify issuing function has the right elements, but several details would either create an invalid exop encoding, or make it not conform to the expected structure of an exop.
The first thing to note is that, according to spec, all components of a PasswordModifyRequestValue
are optional. The presence of an individual field is signalled by tagging it with a context-specific numeric tag. For example, userIdentity
is specified as userIdentity [0] OCTET STRING OPTIONAL
, with the numeric tag of zero when present. The correct encoding in the source would be:
Tag::OctetString(OctetString {
id: 0,
class: TagClass::Context,
inner: Vec::from(...),
}
Furthermore, if all components are absent, which the spec allows, the value element of the encoded exop should not be present at all.
If you look at the implentations of existing exops in the library, you'll see that all of them have an exop-specific struct and a From<CustomStruct> for Exop
implementation. Putting this all together, the request side could be written as follows:
use bytes::BytesMut;
use ldap3::asn1::{write, ASNTag, OctetString, Sequence, Tag, TagClass};
use ldap3::exop::Exop;
pub const PASSMOD_OID: &str = "1.3.6.1.4.1.4203.1.11.1";
struct PasswordModify<'a> {
user_id: Option<&'a str>,
old_pass: Option<&'a str>,
new_pass: Option<&'a str>,
}
impl<'a> From<PasswordModify<'a>> for Exop {
fn from(pm: PasswordModify<'a>) -> Exop {
let pm_val = if pm.user_id.is_none() && pm.old_pass.is_none() && pm.new_pass.is_none() {
None
} else {
let mut pm_vec = vec![];
if let Some(user_id) = pm.user_id {
pm_vec.push(Tag::OctetString(OctetString {
id: 0,
class: TagClass::Context,
inner: Vec::from(user_id.as_bytes()),
}));
}
if let Some(old_pass) = pm.old_pass {
pm_vec.push(Tag::OctetString(OctetString {
id: 1,
class: TagClass::Context,
inner: Vec::from(old_pass.as_bytes()),
}));
}
if let Some(new_pass) = pm.new_pass {
pm_vec.push(Tag::OctetString(OctetString {
id: 2,
class: TagClass::Context,
inner: Vec::from(new_pass.as_bytes()),
}));
}
let pm_val = Tag::Sequence(Sequence {
inner: pm_vec,
..Default::default()
})
.into_structure();
let mut buf = BytesMut::new();
write::encode_into(&mut buf, pm_val).expect("encoded");
Some(Vec::from(&buf[..]))
};
Exop {
name: Some(PASSMOD_OID.to_owned()),
val: pm_val,
}
}
}
This should be enough to get you started. Password Modify has the response structure, which will contain a server-generated password if you ask for one by not supplying the new password value in the request, but that's a bit of a niche use case, and not something I would recommend on security grounds.
I will add a full Password Modify implementation to the library, and I'll keep this issue open until I do so (soon, I hope).
Thank you very much for your reply and for your work on this library, I understand a bit more how the RFC translates to Rust code within your library and and how the crate is structured.
I will add a full Password Modify implementation to the library, and I'll keep this issue open until I do so (soon, I hope).
If you feel that this can be a good first issue for a beginner, let me know :)
If you feel that this can be a good first issue for a beginner, let me know :)
It might have been, but the code above is 75% of the feature already :slightly_smiling_face: Commit incoming.
Hello.
I’m working on a web service where I’d like the user to be able to change their LDAP password and I understand that the best way to do this is by using the Password Modify extended operation defined in RFC 3062.
I’m new to LDAP and ANS1, but I’ve had a look at the doc and I think a
modify_password
function could look like this (not tested):I’m willing to work on this feature if needed, but this is a whole new world to me so I might need a bit of help.