Summary
parse_uds_directory() extracts each field with a bare-tag regex:
m = re.search(rf'<{tag}>([^<]*)</{tag}>', block)
That requires the element to have no attributes. But the v14 schema gives <directoryUri> an optional exist attribute — from UDS-xsd-14.zip, xsd/pub/users/users.get.xsd:
<xs:complexType name="directoryUri">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:boolean" name="exist" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
So when CUCM returns <directoryUri exist="true">alice@corp.example</directoryUri>, the regex does not match and the field is silently recorded as ''.
Reproduced
xml = ('<users totalCount="1"><user><userName>alice</userName>'
'<directoryUri exist="true">alice@corp.example</directoryUri>'
'</user></users>')
thief.parse_uds_directory(xml)[0]['directory_uri']
# -> '' (expected 'alice@corp.example')
Impact
directoryUri is the user's SIP/Jabber URI — one of the higher-value fields in the corporate directory harvest, since it is frequently the address used for Jabber/Webex targeting and often mirrors the mail address. Losing it is silent: --directory and --userenum still report success, the CSV still has a directory_uri column, and it is simply empty for every record. An operator has no signal that the field was dropped rather than genuinely unset on the cluster, and the existing tests all use attribute-free fixtures so nothing catches it.
Whether it bites depends on whether the target cluster populates exist, which is exactly the kind of thing that varies between engagements — so this can look fine in testing and lose data in the field.
Fix
Allow attributes on the element, anchored with \b so a short tag cannot match a longer one that merely starts with it (<id> vs <identityHint>):
m = re.search(rf'<{tag}\b[^>]*>([^<]*)</{tag}>', block)
This affects only the extraction pattern, and applies to every field in _UDS_DIRECTORY_FIELDS rather than special-casing directoryUri, since the schema permits attributes generally.
Notes
Found during the UDS schema-conformance audit (#33, #35, #36, #37) by a code-quality review pass against the official v14 XSDs. Pre-existing — not introduced by that work — but the same class of defect, so it is fixed in the same PR with regression tests covering the attribute case, the no-attribute case, and the <id>/<identityHint> prefix-collision guard.
Summary
parse_uds_directory()extracts each field with a bare-tag regex:That requires the element to have no attributes. But the v14 schema gives
<directoryUri>an optionalexistattribute — fromUDS-xsd-14.zip,xsd/pub/users/users.get.xsd:So when CUCM returns
<directoryUri exist="true">alice@corp.example</directoryUri>, the regex does not match and the field is silently recorded as''.Reproduced
Impact
directoryUriis the user's SIP/Jabber URI — one of the higher-value fields in the corporate directory harvest, since it is frequently the address used for Jabber/Webex targeting and often mirrors the mail address. Losing it is silent:--directoryand--userenumstill report success, the CSV still has adirectory_uricolumn, and it is simply empty for every record. An operator has no signal that the field was dropped rather than genuinely unset on the cluster, and the existing tests all use attribute-free fixtures so nothing catches it.Whether it bites depends on whether the target cluster populates
exist, which is exactly the kind of thing that varies between engagements — so this can look fine in testing and lose data in the field.Fix
Allow attributes on the element, anchored with
\bso a short tag cannot match a longer one that merely starts with it (<id>vs<identityHint>):This affects only the extraction pattern, and applies to every field in
_UDS_DIRECTORY_FIELDSrather than special-casingdirectoryUri, since the schema permits attributes generally.Notes
Found during the UDS schema-conformance audit (#33, #35, #36, #37) by a code-quality review pass against the official v14 XSDs. Pre-existing — not introduced by that work — but the same class of defect, so it is fixed in the same PR with regression tests covering the attribute case, the no-attribute case, and the
<id>/<identityHint>prefix-collision guard.