@@ -0,0 +1,191 @@
+// Copyright (c) 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use crate::iq::{IqSetPayload, IqGetPayload, IqResultPayload};
+use crate::util::helpers::Base64;
+
+generate_elem_id!(
+ /// The name of a certificate.
+ Name, "name", SASL_CERT
+);
+
+generate_element!(
+ /// An X.509 certificate.
+ Cert, "x509cert", SASL_CERT,
+ text: (
+ /// The BER X.509 data.
+ data: Base64<Vec<u8>>
+ )
+);
+
+generate_element!(
+ /// For the client to upload an X.509 certificate.
+ Append, "append", SASL_CERT,
+ children: [
+ /// The name of this certificate.
+ name: Required<Name> = ("name", SASL_CERT) => Name,
+
+ /// The X.509 certificate to set.
+ cert: Required<Cert> = ("x509cert", SASL_CERT) => Cert,
+
+ /// This client is forbidden from managing certificates.
+ no_cert_management: Present<_> = ("no-cert-management", SASL_CERT) => bool
+ ]
+);
+
+impl IqSetPayload for Append {}
+
+generate_empty_element!(
+ /// Client requests the current list of X.509 certificates.
+ ListCertsQuery, "items", SASL_CERT
+);
+
+impl IqGetPayload for ListCertsQuery {}
+
+generate_elem_id!(
+ /// One resource currently using a certificate.
+ Resource, "resource", SASL_CERT
+);
+
+generate_element!(
+ /// A list of resources currently using this certificate.
+ Users, "users", SASL_CERT,
+ children: [
+ /// Resources currently using this certificate.
+ resources: Vec<Resource> = ("resource", SASL_CERT) => Resource
+ ]
+);
+
+generate_element!(
+ /// An X.509 certificate being set for this user.
+ Item, "item", SASL_CERT,
+ children: [
+ /// The name of this certificate.
+ name: Required<Name> = ("name", SASL_CERT) => Name,
+
+ /// The X.509 certificate to set.
+ cert: Required<Cert> = ("x509cert", SASL_CERT) => Cert,
+
+ /// This client is forbidden from managing certificates.
+ no_cert_management: Present<_> = ("no-cert-management", SASL_CERT) => bool,
+
+ /// List of resources currently using this certificate.
+ users: Option<Users> = ("users", SASL_CERT) => Users
+ ]
+);
+
+generate_element!(
+ /// Server answers with the current list of X.509 certificates.
+ ListCertsResponse, "items", SASL_CERT,
+ children: [
+ /// List of certificates.
+ items: Vec<Item> = ("item", SASL_CERT) => Item
+ ]
+);
+
+impl IqResultPayload for ListCertsResponse {}
+
+generate_element!(
+ /// Client disables an X.509 certificate.
+ Disable, "disable", SASL_CERT,
+ children: [
+ /// Name of the certificate to disable.
+ name: Required<Name> = ("name", SASL_CERT) => Name
+ ]
+);
+
+impl IqSetPayload for Disable {}
+
+generate_element!(
+ /// Client revokes an X.509 certificate.
+ Revoke, "revoke", SASL_CERT,
+ children: [
+ /// Name of the certificate to revoke.
+ name: Required<Name> = ("name", SASL_CERT) => Name
+ ]
+);
+
+impl IqSetPayload for Revoke {}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use minidom::Element;
+ use std::convert::TryFrom;
+ use std::str::FromStr;
+ use crate::ns;
+
+ #[test]
+ fn test_size() {
+ assert_size!(Append, 56);
+ }
+
+ #[test]
+ fn simple() {
+ let elem: Element = "<append xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name><x509cert>AAAA</x509cert></append>".parse().unwrap();
+ let append = Append::try_from(elem).unwrap();
+ assert_eq!(append.name.0, "Mobile Client");
+ assert_eq!(append.cert.data, b"\0\0\0");
+
+ let elem: Element = "<disable xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name></disable>".parse().unwrap();
+ let disable = Disable::try_from(elem).unwrap();
+ assert_eq!(disable.name.0, "Mobile Client");
+
+ let elem: Element = "<revoke xmlns='urn:xmpp:saslcert:1'><name>Mobile Client</name></revoke>".parse().unwrap();
+ let revoke = Revoke::try_from(elem).unwrap();
+ assert_eq!(revoke.name.0, "Mobile Client");
+ }
+
+ #[test]
+ fn list() {
+ let elem: Element = r#"
+ <items xmlns='urn:xmpp:saslcert:1'>
+ <item>
+ <name>Mobile Client</name>
+ <x509cert>AAAA</x509cert>
+ <users>
+ <resource>Phone</resource>
+ </users>
+ </item>
+ <item>
+ <name>Laptop</name>
+ <x509cert>BBBB</x509cert>
+ </item>
+ </items>"#.parse().unwrap();
+ let mut list = ListCertsResponse::try_from(elem).unwrap();
+ assert_eq!(list.items.len(), 2);
+
+ let item = list.items.pop().unwrap();
+ assert_eq!(item.name.0, "Laptop");
+ assert_eq!(item.cert.data, [4, 16, 65]);
+ assert!(item.users.is_none());
+
+ let item = list.items.pop().unwrap();
+ assert_eq!(item.name.0, "Mobile Client");
+ assert_eq!(item.cert.data, b"\0\0\0");
+ assert_eq!(item.users.unwrap().resources.len(), 1);
+ }
+
+ #[test]
+ fn test_serialise() {
+ let append = Append {
+ name: Name::from_str("Mobile Client").unwrap(),
+ cert: Cert { data: b"\0\0\0".to_vec() },
+ no_cert_management: false,
+ };
+ let elem: Element = append.into();
+ assert!(elem.is("append", ns::SASL_CERT));
+
+ let disable = Disable {
+ name: Name::from_str("Mobile Client").unwrap(),
+ };
+ let elem: Element = disable.into();
+ assert!(elem.is("disable", ns::SASL_CERT));
+ let elem = elem.children().cloned().collect::<Vec<_>>().pop().unwrap();
+ assert!(elem.is("name", ns::SASL_CERT));
+ assert_eq!(elem.text(), "Mobile Client");
+ }
+}
@@ -494,6 +494,9 @@ macro_rules! start_decl {
(Required, $type:ty) => (
$type
);
+ (Present, $type:ty) => (
+ bool
+ );
}
macro_rules! start_parse_elem {
@@ -506,6 +509,9 @@ macro_rules! start_parse_elem {
($temp:ident: Required) => {
let mut $temp = None;
};
+ ($temp:ident: Present) => {
+ let mut $temp = false;
+ };
}
macro_rules! do_parse {
@@ -548,6 +554,18 @@ macro_rules! do_parse_elem {
}
$temp = Some(do_parse!($elem, $constructor));
};
+ ($temp:ident: Present = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
+ if $temp {
+ return Err(crate::util::error::Error::ParseError(concat!(
+ "Element ",
+ $parent_name,
+ " must not have more than one ",
+ $name,
+ " child."
+ )));
+ }
+ $temp = true;
+ };
}
macro_rules! finish_parse_elem {
@@ -566,6 +584,9 @@ macro_rules! finish_parse_elem {
" element."
)))?
};
+ ($temp:ident: Present = $name:tt, $parent_name:tt) => {
+ $temp
+ };
}
macro_rules! generate_serialiser {
@@ -595,6 +616,9 @@ macro_rules! generate_serialiser {
($builder:ident, $parent:ident, $elem:ident, Vec, $constructor:ident, ($name:tt, $ns:ident)) => {
$builder.append_all($parent.$elem.into_iter())
};
+ ($builder:ident, $parent:ident, $elem:ident, Present, $constructor:ident, ($name:tt, $ns:ident)) => {
+ $builder.append(::minidom::Node::Element(::minidom::Element::builder($name).ns(crate::ns::$ns).build()))
+ };
($builder:ident, $parent:ident, $elem:ident, $_:ident, $constructor:ident, ($name:tt, $ns:ident)) => {
$builder.append(::minidom::Node::Element(::minidom::Element::from($parent.$elem)))
};