rusthound_ce/objects/
gpo.rs

1use serde_json::value::Value;
2use serde::{Deserialize, Serialize};
3use ldap3::SearchEntry;
4use log::{debug, trace};
5use std::collections::HashMap;
6use std::error::Error;
7
8use crate::objects::common::{LdapObject, AceTemplate, Link, SPNTarget, Member};
9use crate::enums::decode_guid_le;
10use crate::enums::acl::parse_ntsecuritydescriptor;
11use crate::utils::date::string_to_epoch;
12
13/// Gpo structure
14#[derive(Debug, Clone, Deserialize, Serialize, Default)]
15pub struct Gpo {
16    #[serde(rename = "Properties")]
17    properties: GpoProperties,
18    #[serde(rename = "Aces")]
19    aces: Vec<AceTemplate>,
20    #[serde(rename = "ObjectIdentifier")]
21    object_identifier: String,
22    #[serde(rename = "IsDeleted")]
23    is_deleted: bool,
24    #[serde(rename = "IsACLProtected")]
25    is_acl_protected: bool,
26    #[serde(rename = "ContainedBy")]
27    contained_by: Option<Member>,
28    #[serde(rename = "Links")]
29    links: Vec<Link>,
30}
31
32impl Gpo {
33    // New gpo.
34    pub fn new() -> Self { 
35        Self { ..Default::default() } 
36    }
37    
38    /// Function to parse and replace value for GPO object.
39    /// <https://bloodhound.readthedocs.io/en/latest/further-reading/json.html#gpos>
40    pub fn parse(
41        &mut self,
42        result: SearchEntry,
43        domain: &str,
44        dn_sid: &mut HashMap<String, String>,
45        sid_type: &mut HashMap<String, String>,
46        domain_sid: &str
47    ) -> Result<(), Box<dyn Error>> {
48        let result_dn: String = result.dn.to_uppercase();
49        let result_attrs: HashMap<String, Vec<String>> = result.attrs;
50        let result_bin: HashMap<String, Vec<Vec<u8>>> = result.bin_attrs;
51
52        // Debug for current object
53        debug!("Parse gpo: {result_dn}");
54
55        // Trace all result attributes
56        for (key, value) in &result_attrs {
57            trace!("  {key:?}:{value:?}");
58        }
59        // Trace all bin result attributes
60        for (key, value) in &result_bin {
61            trace!("  {key:?}:{value:?}");
62        }
63
64        // Change all values...
65        self.properties.domain = domain.to_uppercase();
66        self.properties.distinguishedname = result_dn;
67        self.properties.domainsid = domain_sid.to_string();
68
69        // Check and replace value
70        for (key, value) in &result_attrs {
71            match key.as_str() {
72                "displayName" => {
73                    let name = &value[0];
74                    let email = format!("{}@{}", name.to_owned(), domain);
75                    self.properties.name = email.to_uppercase();
76                }
77                "description" => {
78                    self.properties.description = value.first().cloned();
79                }
80                "whenCreated" => {
81                    let epoch = string_to_epoch(&value[0])?;
82                    if epoch.is_positive() {
83                        self.properties.whencreated = epoch;
84                    }
85                }
86                "gPCFileSysPath" => {
87                    self.properties.gpcpath = value[0].to_owned();
88                }
89                "IsDeleted" => {
90                    self.is_deleted = true;
91                }
92                _ => {}
93            }
94        }
95
96        // For all, bins attributes
97        for (key, value) in &result_bin {
98            match key.as_str() {
99                "objectGUID" => {
100                    // objectGUID raw to string
101                    self.object_identifier = decode_guid_le(&value[0]).to_owned();
102                }
103                "nTSecurityDescriptor" => {
104                    // nTSecurityDescriptor raw to string
105                    let relations_ace = parse_ntsecuritydescriptor(
106                        self,
107                        &value[0],
108                        "Gpo",
109                        &result_attrs,
110                        &result_bin,
111                        domain,
112                    );
113                    self.aces = relations_ace;
114                }
115                _ => {}
116            }
117        }
118
119        // Push DN and SID in HashMap
120        dn_sid.insert(
121            self.properties.distinguishedname.to_string(),
122            self.object_identifier.to_string(),
123        );
124        // Push DN and Type
125        sid_type.insert(
126            self.object_identifier.to_string(),
127            "Gpo".to_string(),
128        );
129
130        // Trace and return Gpo struct
131        // trace!("JSON OUTPUT: {:?}",serde_json::to_string(&self).unwrap());
132        Ok(())
133    }
134}
135
136impl LdapObject for Gpo {
137    // To JSON
138    fn to_json(&self) -> Value {
139        serde_json::to_value(self).unwrap()
140    }
141
142    // Get values
143    fn get_object_identifier(&self) -> &String {
144        &self.object_identifier
145    }
146    fn get_is_acl_protected(&self) -> &bool {
147        &self.is_acl_protected
148    }
149    fn get_aces(&self) -> &Vec<AceTemplate> {
150        &self.aces
151    }
152    fn get_spntargets(&self) -> &Vec<SPNTarget> {
153        panic!("Not used by current object.");
154    }
155    fn get_allowed_to_delegate(&self) -> &Vec<Member> {
156        panic!("Not used by current object.");
157    }
158    fn get_links(&self) -> &Vec<Link> {
159        panic!("Not used by current object.");
160    }
161    fn get_contained_by(&self) -> &Option<Member> {
162        &self.contained_by
163    }
164    fn get_child_objects(&self) -> &Vec<Member> {
165        panic!("Not used by current object.");
166    }
167    fn get_haslaps(&self) -> &bool {
168        &false
169    }
170    
171    // Get mutable values
172    fn get_aces_mut(&mut self) -> &mut Vec<AceTemplate> {
173        &mut self.aces
174    }
175    fn get_spntargets_mut(&mut self) -> &mut Vec<SPNTarget> {
176        panic!("Not used by current object.");
177    }
178    fn get_allowed_to_delegate_mut(&mut self) -> &mut Vec<Member> {
179        panic!("Not used by current object.");
180    }
181    
182    // Edit values
183    fn set_is_acl_protected(&mut self, is_acl_protected: bool) {
184        self.is_acl_protected = is_acl_protected;
185        self.properties.isaclprotected = is_acl_protected;
186    }
187    fn set_aces(&mut self, aces: Vec<AceTemplate>) {
188        self.aces = aces;
189    }
190    fn set_spntargets(&mut self, _spn_targets: Vec<SPNTarget>) {
191        // Not used by current object.
192    }
193    fn set_allowed_to_delegate(&mut self, _allowed_to_delegate: Vec<Member>) {
194        // Not used by current object.
195    }
196    fn set_links(&mut self, links: Vec<Link>) {
197        self.links = links;
198    }
199    fn set_contained_by(&mut self, contained_by: Option<Member>) {
200        self.contained_by = contained_by;
201    }
202    fn set_child_objects(&mut self, _child_objects: Vec<Member>) {
203        // Not used by current object.
204    }
205}
206
207// Gpo properties structure
208#[derive(Debug, Clone, Deserialize, Serialize, Default)]
209pub struct GpoProperties {
210   domain: String,
211   name: String,
212   distinguishedname: String,
213   domainsid: String,
214   isaclprotected: bool,
215   highvalue: bool,
216   description: Option<String>,
217   whencreated: i64,
218   gpcpath: String
219}