diff --git a/src/AppContainer.jsx b/src/AppContainer.jsx
index 4b64651d3..f53e0f72a 100644
--- a/src/AppContainer.jsx
+++ b/src/AppContainer.jsx
@@ -83,7 +83,12 @@ const fullEdgeList = [
'AddKeyCredentialLink',
'DumpSMSAPassword',
'DCSync',
- 'SyncLAPSPassword'
+ 'SyncLAPSPassword',
+ 'Enroll',
+ 'AutoEnroll',
+ 'ManageCa',
+ 'ManageCertificates',
+ 'EnabledBy'
];
export default class AppContainer extends Component {
diff --git a/src/components/RawQuery.jsx b/src/components/RawQuery.jsx
index 8f9240e49..77a20b80b 100644
--- a/src/components/RawQuery.jsx
+++ b/src/components/RawQuery.jsx
@@ -24,12 +24,14 @@ const RawQuery = () => {
const onKeyDown = (e) => {
let key = e.keyCode ? e.keyCode : e.which;
- if (key === 13) {
+ if ((key == 10 || key == 13) && e.ctrlKey) {
emitter.emit('query', query);
}
};
const onChange = (e) => {
+ e.target.style.height = 'inherit';
+ e.target.style.height = `${e.target.scrollHeight}px`;
setQueryFromEvent(e.target.value);
};
@@ -59,7 +61,7 @@ const RawQuery = () => {
transition={{ duration: 0.25 }}
animate={open ? 'open' : 'closed'}
>
- {
className={clsx(styles.input, 'form-control')}
autoComplete='off'
placeholder='Enter a cypher query. Your query must return nodes or paths.'
- />
+ /> */}
+
);
diff --git a/src/components/SearchContainer/EdgeFilter/EdgeFilter.jsx b/src/components/SearchContainer/EdgeFilter/EdgeFilter.jsx
index 68f702626..d1edb653b 100644
--- a/src/components/SearchContainer/EdgeFilter/EdgeFilter.jsx
+++ b/src/components/SearchContainer/EdgeFilter/EdgeFilter.jsx
@@ -112,6 +112,21 @@ const EdgeFilter = ({ open }) => {
+
+
+
+
+
{
switch (type) {
case 'Group':
- icon.className = 'fa fa-users';
+ if (item.hasOwnProperty("bl-icon")){
+ icon.className = 'fa '+ item["bl-icon"];
+ }else{
+ icon.className = 'fa fa-users';
+ }
break;
case 'User':
icon.className = 'fa fa-user';
@@ -43,6 +47,12 @@ const SearchRow = ({ item, search }) => {
case 'OU':
icon.className = 'fa fa-sitemap';
break;
+ case 'CA':
+ icon.className = 'fa fa-university';
+ break;
+ case 'CertificateTemplate':
+ icon.className = 'fa fa-id-card';
+ break;
case 'Container':
icon.className = 'fa fa-box'
break
@@ -104,8 +114,12 @@ const SearchRow = ({ item, search }) => {
icon.className = 'fa fa-window-restore'
break
default:
- icon.className = 'fa fa-question';
- type = 'Base';
+ if (item.hasOwnProperty("bl-icon")){
+ icon.className = 'fa '+ item["bl-icon"];
+ }else{
+ icon.className = 'fa fa-question';
+ type = 'Base';
+ }
break;
}
diff --git a/src/components/SearchContainer/TabContainer.jsx b/src/components/SearchContainer/TabContainer.jsx
index 3fa5b51d3..5d97b5be2 100644
--- a/src/components/SearchContainer/TabContainer.jsx
+++ b/src/components/SearchContainer/TabContainer.jsx
@@ -8,6 +8,8 @@ import ComputerNodeData from './Tabs/ComputerNodeData';
import DomainNodeData from './Tabs/DomainNodeData';
import GpoNodeData from './Tabs/GPONodeData';
import OuNodeData from './Tabs/OUNodeData';
+import CaNodeData from './Tabs/CANodeData';
+import TemplateNodeData from './Tabs/TemplateNodeData';
import AZGroupNodeData from './Tabs/AZGroupNodeData';
import AZUserNodeData from './Tabs/AZUserNodeData';
import AZContainerRegistryNodeData from './Tabs/AZContainerRegistryNodeData';
@@ -48,6 +50,8 @@ class TabContainer extends Component {
domainVisible: false,
gpoVisible: false,
ouVisible: false,
+ caVisible: false,
+ templateVisible: false,
containerVisible: false,
azGroupVisible: false,
azUserVisible: false,
@@ -92,6 +96,10 @@ class TabContainer extends Component {
this._domainNodeClicked();
} else if (type === 'OU') {
this._ouNodeClicked();
+ } else if (type === 'CA') {
+ this._caNodeClicked();
+ } else if (type === 'CertificateTemplate') {
+ this._templateNodeClicked();
} else if (type === 'GPO') {
this._gpoNodeClicked();
} else if (type === 'AZGroup') {
@@ -225,6 +233,22 @@ class TabContainer extends Component {
});
}
+ _caNodeClicked() {
+ this.clearVisible()
+ this.setState({
+ caVisible: true,
+ selected: 2
+ });
+ }
+
+ _templateNodeClicked() {
+ this.clearVisible()
+ this.setState({
+ templateVisible: true,
+ selected: 2
+ });
+ }
+
_azGroupNodeClicked() {
this.clearVisible()
this.setState({
@@ -405,6 +429,8 @@ class TabContainer extends Component {
!this.state.domainVisible &&
!this.state.gpoVisible &&
!this.state.ouVisible &&
+ !this.state.caVisible &&
+ !this.state.templateVisible &&
!this.state.azGroupVisible &&
!this.state.azUserVisible &&
!this.state.azContainerRegistryVisible &&
@@ -436,6 +462,8 @@ class TabContainer extends Component {
+
+
diff --git a/src/components/SearchContainer/Tabs/CANodeData.jsx b/src/components/SearchContainer/Tabs/CANodeData.jsx
new file mode 100644
index 000000000..8e4c6ef7b
--- /dev/null
+++ b/src/components/SearchContainer/Tabs/CANodeData.jsx
@@ -0,0 +1,171 @@
+/******************************************************************************************
+* The credit goes to https://github.com/ly4k/BloodHound. Thank you for the excellent work!
+*/
+import clsx from 'clsx';
+import React, { useContext, useEffect, useState } from 'react';
+import { Table } from 'react-bootstrap';
+import { AppContext } from '../../../AppContext';
+import CollapsibleSection from './Components/CollapsibleSection';
+import ExtraNodeProps from './Components/ExtraNodeProps';
+import MappedNodeProps from './Components/MappedNodeProps';
+import NodeCypherLink from './Components/NodeCypherLink';
+import NodePlayCypherLink from './Components/NodePlayCypherLink';
+import NodeCypherNoNumberLink from './Components/NodeCypherNoNumberLink';
+import styles from './NodeData.module.css';
+
+const CANodeData = () => {
+ const [visible, setVisible] = useState(false);
+ const [objectid, setObjectid] = useState(null);
+ const [label, setLabel] = useState(null);
+ const [domain, setDomain] = useState(null);
+ const [nodeProps, setNodeProps] = useState({});
+ const [blocksInheritance, setBlocksInheritance] = useState(false);
+ const context = useContext(AppContext);
+
+ useEffect(() => {
+ emitter.on('nodeClicked', nodeClickEvent);
+
+ return () => {
+ emitter.removeListener('nodeClicked', nodeClickEvent);
+ };
+ }, []);
+
+ const nodeClickEvent = (type, id, blocksinheritance, domain) => {
+ if (type === 'CA') {
+ setVisible(true);
+ setObjectid(id);
+ setDomain(domain);
+ setBlocksInheritance(blocksinheritance);
+ let session = driver.session();
+ session
+ .run(`MATCH (n:CA {objectid: $objectid}) RETURN n AS node`, {
+ objectid: id,
+ })
+ .then((r) => {
+ let props = r.records[0].get('node').properties;
+ setNodeProps(props);
+ setLabel(props.name);
+ session.close();
+ });
+ } else {
+ setObjectid(null);
+ setVisible(false);
+ }
+ };
+
+ const displayMap = {
+ objectid: 'Object ID',
+ 'CA Name': 'CA Name',
+ };
+
+ return objectid === null ? (
+
+ ) : (
+
+
+
{label || objectid}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (u1:CA {objectid: $objectid}) WHERE r.isacl=true'
+ }
+ end={label}
+ distinct
+ />
+ (g:Group)-[r1:ManageCa|ManageCertificates|Auditor|Operator|Owns]->(u:CA {objectid: $objectid}) WITH LENGTH(p) as pathLength, p, n WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.objectid = u.objectid) AND NOT n.objectid = u.objectid'
+ }
+ end={label}
+ distinct
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ (u1:CA {objectid: $objectid}) WHERE r.isacl=true'
+ }
+ end={label}
+ distinct
+ />
+ (g:Group)-[r1:Read|Enroll]->(u:CA {objectid: $objectid}) WITH LENGTH(p) as pathLength, p, n WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.objectid = u.objectid) AND NOT n.objectid = u.objectid'
+ }
+ end={label}
+ distinct
+ />
+
+
+
+
+
+
+
+
+ );
+};
+
+CANodeData.propTypes = {};
+export default CANodeData;
diff --git a/src/components/SearchContainer/Tabs/DatabaseDataDisplay.jsx b/src/components/SearchContainer/Tabs/DatabaseDataDisplay.jsx
index a411c2829..17e3176bb 100644
--- a/src/components/SearchContainer/Tabs/DatabaseDataDisplay.jsx
+++ b/src/components/SearchContainer/Tabs/DatabaseDataDisplay.jsx
@@ -113,6 +113,20 @@ const DatabaseDataDisplay = () => {
index={index}
label={'Computers'}
/>
+
+
(n:CertificateTemplate)) WHERE g<>n and n.`Enrollee Supplies Subject` = true and n.`Client Authentication` = true and n.`Enabled` = true and NONE(rel in r WHERE type(rel) in ['EnabledBy','Read','ManageCa','ManageCertificates']) return p"
+ }
+ ]
+ },
+ {
+ "name": "Find Misconfigured Certificate Templates (ESC2)",
+ "category": "PKI",
+ "queryList": [
+ {
+ "final": true,
+ "query": "MATCH (n:CertificateTemplate) WHERE n.`Enabled` = true and n.`Any Purpose` = true RETURN n"
+ }
+ ]
+ },
+ {
+ "name": "Shortest Paths to Misconfigured Certificate Templates from Owned Principals (ESC2)",
+ "category": "PKI",
+ "queryList": [
+ {
+ "final": true,
+ "query": "MATCH p=allShortestPaths((g {owned:true})-[r*1..]->(n:CertificateTemplate)) WHERE g<>n and n.`Enabled` = true and n.`Any Purpose` = true and NONE(rel in r WHERE type(rel) in ['EnabledBy','Read','ManageCa','ManageCertificates']) return p"
+ }
+ ]
+ },
+ {
+ "name": "Find Enrollment Agent Templates (ESC3)",
+ "category": "PKI",
+ "queryList": [
+ {
+ "final": true,
+ "query": "MATCH (n:CertificateTemplate) WHERE n.`Enabled` = true and n.`Enrollment Agent` = true RETURN n"
+ }
+ ]
+ },
+ {
+ "name": "Shortest Paths to Enrollment Agent Templates from Owned Principals (ESC3)",
+ "category": "PKI",
+ "queryList": [
+ {
+ "final": true,
+ "query": "MATCH p=allShortestPaths((g {owned:true})-[r*1..]->(n:CertificateTemplate)) WHERE g<>n and n.`Enabled` = true and n.`Enrollment Agent` = true and NONE(rel in r WHERE type(rel) in ['EnabledBy','Read','ManageCa','ManageCertificates']) return p"
+ }
+ ]
+ },
+ {
+ "name": "Shortest Paths to Vulnerable Certificate Template Access Control (ESC4)",
+ "category": "PKI",
+ "queryList": [
+ {
+ "final": true,
+ "query": "MATCH p=shortestPath((g)-[r*1..]->(n:CertificateTemplate)) WHERE g<>n and n.`Enabled` = true and NONE(rel in r WHERE type(rel) in ['EnabledBy','Read','ManageCa','ManageCertificates','Enroll','AutoEnroll']) RETURN p"
+ }
+ ]
+ },
+ {
+ "name": "Shortest Paths to Vulnerable Certificate Template Access Control from Owned Principals (ESC4)",
+ "category": "PKI",
+ "queryList": [
+ {
+ "final": true,
+ "query": "MATCH p=allShortestPaths((g {owned:true})-[r*1..]->(n:CertificateTemplate)) WHERE g<>n and n.Enabled = true and NONE(rel in r WHERE type(rel) in ['EnabledBy','Read','ManageCa','ManageCertificates','Enroll','AutoEnroll']) return p"
+ }
+ ]
+ },
+ {
+ "name": "Find Certificate Authorities with User Specified SAN (ESC6)",
+ "category": "PKI",
+ "queryList": [
+ {
+ "final": true,
+ "query": "MATCH (n:CA) WHERE n.`User Specified SAN` = 'Enabled' RETURN n"
+ }
+ ]
+ },
+ {
+ "name": "Shortest Paths to Vulnerable Certificate Authority Access Control (ESC7)",
+ "category": "PKI",
+ "queryList": [
+ {
+ "final": true,
+ "query": "MATCH p=allShortestPaths((g)-[r*1..]->(n:CA)) WHERE g<>n and NONE(rel in r WHERE type(rel) in ['EnabledBy','Read','Enroll','AutoEnroll']) RETURN p"
+ }
+ ]
+ },
+ {
+ "name": "Shortest Paths to Vulnerable Certificate Authority Access Control from Owned Principals (ESC7)",
+ "category": "PKI",
+ "queryList": [
+ {
+ "final": true,
+ "query": "MATCH p=allShortestPaths((g {owned:true})-[r*1..]->(n:CA)) WHERE g<>n and NONE(rel in r WHERE type(rel) in ['EnabledBy','Read','Enroll','AutoEnroll']) RETURN p"
+ }
+ ]
+ },
+ {
+ "name": "Find Certificate Authorities with HTTP Web Enrollment (ESC8)",
+ "category": "PKI",
+ "queryList": [
+ {
+ "final": true,
+ "query": "MATCH (n:CA) WHERE n.`Web Enrollment` = 'Enabled' RETURN n"
+ }
+ ]
+ },
+ {
+ "name": "Find Unsecured Certificate Templates (ESC9)",
+ "category": "PKI",
+ "queryList": [
+ {
+ "final": true,
+ "query": "MATCH (n:CertificateTemplate) WHERE 'NoSecurityExtension' in n.`Enrollment Flag` and n.`Enabled` = true RETURN n"
+ }
+ ]
+ },
+ {
+ "name": "Shortest Paths to Unsecured Certificate Templates from Owned Principals (ESC9)",
+ "category": "PKI",
+ "queryList": [
+ {
+ "final": true,
+ "query": "MATCH p=allShortestPaths((g {owned:true})-[r*1..]->(n:CertificateTemplate)) WHERE g<>n and 'NoSecurityExtension' in n.`Enrollment Flag` and n.`Enabled` = true and NONE(rel in r WHERE type(rel) in ['EnabledBy','Read','ManageCa','ManageCertificates']) return p"
+ }
+ ]
}
]
}
diff --git a/src/components/SearchContainer/Tabs/PrebuiltQueryNode.jsx b/src/components/SearchContainer/Tabs/PrebuiltQueryNode.jsx
index 7aa7853f3..68c24580e 100644
--- a/src/components/SearchContainer/Tabs/PrebuiltQueryNode.jsx
+++ b/src/components/SearchContainer/Tabs/PrebuiltQueryNode.jsx
@@ -1,7 +1,37 @@
-import React, { Component } from 'react';
+import React, { Component, useContext } from 'react';
import './PrebuiltQueries.module.css';
+import { motion } from 'framer-motion';
+import { AppContext } from '../../../AppContext';
+
+import GlyphiconSpan from '../../GlyphiconSpan';
+import Icon from '../../Icon';
+import { withAlert } from 'react-alert';
+
export default class PrebuiltQueryNode extends Component {
+ static contextType = AppContext;
+
+ constructor(props) {
+ super(props)
+
+ this.state = {
+ hovered: false
+ }
+ }
+
+ enterQuery() {
+ this.setState({
+ hovered: true
+ })
+ };
+
+
+ exitQuery() {
+ this.setState({
+ hovered: false
+ })
+ };
+
render() {
let c;
@@ -14,11 +44,77 @@ export default class PrebuiltQueryNode extends Component {
}
}.bind(this);
+ const copyQuery = (e) => {
+ let containsProps = false;
+ let queries = []
+ let queryList = this.props.info.queryList
+
+ let containsMultipleQueries = queryList.length > 1;
+
+ for (var i = 0; i < queryList.length; i++) {
+ let query = queryList[i].query;
+
+ if (queries.length > 0) {
+ queries.push("");
+ }
+
+ let props = queryList[i].props;
+ if (props) {
+ containsProps = true;
+ for (const [key, value] of Object.entries(props)) {
+ query = query.replace(`\$${key}`, JSON.stringify(value))
+ }
+ }
+ queries.push(query);
+ }
+
+ navigator.clipboard.writeText(queries.join("\n"))
+
+ this.props.alert.info("Copied query")
+
+ if (containsProps) {
+ this.props.alert.show(WARNING Query contains props. These might not be properly formatted, { type: 'error' })
+ }
+
+ if (containsMultipleQueries) {
+ this.props.alert.show(WARNING Query contains multiple queries, { type: 'error' })
+ }
+ };
+
+ const variants = {
+ open: { height: 'auto', opacity: 1 },
+ closed: { height: 0, opacity: 0 },
+ };
+
return (
-
- {this.props.info.name} |
- |
-
+
+ {this.props.info.name} |
+
+
+ copyQuery()}
+ >
+
+
+
+
+
+ |
+
);
}
}
+//export default withAlert()(PrebuiltQueryNode)
diff --git a/src/components/SearchContainer/Tabs/TemplateNodeData.jsx b/src/components/SearchContainer/Tabs/TemplateNodeData.jsx
new file mode 100644
index 000000000..572dbe226
--- /dev/null
+++ b/src/components/SearchContainer/Tabs/TemplateNodeData.jsx
@@ -0,0 +1,171 @@
+/******************************************************************************************
+* The credit goes to https://github.com/ly4k/BloodHound. Thank you for the excellent work!
+*/
+import clsx from 'clsx';
+import React, { useContext, useEffect, useState } from 'react';
+import { Table } from 'react-bootstrap';
+import { AppContext } from '../../../AppContext';
+import CollapsibleSection from './Components/CollapsibleSection';
+import ExtraNodeProps from './Components/ExtraNodeProps';
+import MappedNodeProps from './Components/MappedNodeProps';
+import NodeCypherLink from './Components/NodeCypherLink';
+import NodeCypherNoNumberLink from './Components/NodeCypherNoNumberLink';
+import styles from './NodeData.module.css';
+
+const TemplateNodeData = () => {
+ const [visible, setVisible] = useState(false);
+ const [objectid, setObjectid] = useState(null);
+ const [label, setLabel] = useState(null);
+ const [domain, setDomain] = useState(null);
+ const [nodeProps, setNodeProps] = useState({});
+ const [blocksInheritance, setBlocksInheritance] = useState(false);
+ const context = useContext(AppContext);
+
+ useEffect(() => {
+ emitter.on('nodeClicked', nodeClickEvent);
+
+ return () => {
+ emitter.removeListener('nodeClicked', nodeClickEvent);
+ };
+ }, []);
+
+ const nodeClickEvent = (type, id, blocksinheritance, domain) => {
+ if (type === 'CertificateTemplate') {
+ setVisible(true);
+ setObjectid(id);
+ setDomain(domain);
+ setBlocksInheritance(blocksinheritance);
+ let session = driver.session();
+ session
+ .run(`MATCH (n:CertificateTemplate {objectid: $objectid}) RETURN n AS node`, {
+ objectid: id,
+ })
+ .then((r) => {
+ let props = r.records[0].get('node').properties;
+ setNodeProps(props);
+ setLabel(props.name);
+ session.close();
+ });
+ } else {
+ setObjectid(null);
+ setVisible(false);
+ }
+ };
+
+ const displayMap = {
+ objectid: 'Object ID',
+ 'Template Name': 'Template Name',
+ 'Display Name': 'Display Name',
+ };
+
+ return objectid === null ? (
+
+ ) : (
+
+
+
{label || objectid}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (u1:CertificateTemplate {objectid: $objectid}) WHERE r.isacl=true'
+ }
+ end={label}
+ distinct
+ />
+ (g:Group)-[r1:AllExtendedRights|GenericAll|GenericWrite|WriteDacl|WriteOwner|WriteProperty|Owns]->(u:CertificateTemplate {objectid: $objectid}) WITH LENGTH(p) as pathLength, p, n WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.objectid = u.objectid) AND NOT n.objectid = u.objectid'
+ }
+ end={label}
+ distinct
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ (u1:CertificateTemplate {objectid: $objectid}) WHERE r.isacl=true'
+ }
+ end={label}
+ distinct
+ />
+ (g:Group)-[r1:AutoEnroll|Enroll|GenericAll]->(u:CertificateTemplate {objectid: $objectid}) WITH LENGTH(p) as pathLength, p, n WHERE NONE (x in NODES(p)[1..(pathLength-1)] WHERE x.objectid = u.objectid) AND NOT n.objectid = u.objectid'
+ }
+ end={label}
+ distinct
+ />
+
+
+
+
+
+
+
+
+ );
+};
+
+TemplateNodeData.propTypes = {};
+export default TemplateNodeData;
diff --git a/src/index.js b/src/index.js
index 03e5815a4..a5df54f48 100644
--- a/src/index.js
+++ b/src/index.js
@@ -142,6 +142,18 @@ global.appStore = {
scale: 1.25,
color: '#FFAA00',
},
+ CA: {
+ font: "'Font Awesome 5 Free'",
+ content: '\uF19C',
+ scale: 1.25,
+ color: '#FFAA00',
+ },
+ CertificateTemplate: {
+ font: "'Font Awesome 5 Free'",
+ content: '\uF2C2',
+ scale: 1.25,
+ color: '#73E6A1',
+ },
Container: {
font: "'Font Awesome 5 Free'",
content: '\uF466',
diff --git a/src/js/newingestion.js b/src/js/newingestion.js
index 9e91bfddb..4002bbde4 100644
--- a/src/js/newingestion.js
+++ b/src/js/newingestion.js
@@ -20,6 +20,8 @@ export const ADLabels = {
Computer: 'Computer',
OU: 'OU',
GPO: 'GPO',
+ CertificateTemplate: 'CertificateTemplate',
+ CA: 'CA',
Domain: 'Domain',
Container: 'Container',
MemberOf: 'MemberOf',
@@ -34,6 +36,7 @@ export const ADLabels = {
Contains: 'Contains',
GPLink: 'GPLink',
TrustedBy: 'TrustedBy',
+ EnabledBy: 'EnabledBy',
DumpSMSAPassword: 'DumpSMSAPassword',
};
@@ -224,6 +227,65 @@ export function buildGroupJsonNew(chunk) {
return queries;
}
+/**
+ *
+ * @param {Array.} chunk
+ * @returns {{}}
+ */
+export function buildTemplateJsonNew(chunk) {
+ let queries = {};
+
+ queries.properties = {};
+ queries.properties.statement = PROP_QUERY.format(ADLabels.CertificateTemplate);
+ queries.properties.props = [];
+
+ for (let template of chunk) {
+ let properties = template.Properties;
+ let identifier = template.ObjectIdentifier;
+ let aces = template.Aces;
+ let cas = template.cas_ids;
+
+ queries.properties.props.push({ objectid: identifier, map: properties });
+
+ processAceArrayNew(aces, identifier, ADLabels.CertificateTemplate, queries);
+
+ if (cas) {
+ let format = [ADLabels.CertificateTemplate, ADLabels.CA, ADLabels.EnabledBy, NON_ACL_PROPS];
+ let props = cas.map((ca) => {
+ return { source: identifier, target: ca };
+ });
+ insertNew(queries, format, props);
+ }
+ }
+
+ return queries;
+}
+
+/**
+ *
+ * @param {Array.} chunk
+ * @returns {{}}
+ */
+export function buildCaJsonNew(chunk) {
+ let queries = {};
+
+ queries.properties = {};
+ queries.properties.statement = PROP_QUERY.format(ADLabels.CA);
+ queries.properties.props = [];
+
+ for (let ca of chunk) {
+ let properties = ca.Properties;
+ let identifier = ca.ObjectIdentifier;
+ let aces = ca.Aces;
+
+ queries.properties.props.push({ objectid: identifier, map: properties });
+
+ processAceArrayNew(aces, identifier, ADLabels.CA, queries);
+ }
+
+ return queries;
+}
+
/**
*
* @param {Array.} chunk