Skip to content

Commit f6ce27b

Browse files
authored
refactor(security): extend SAML prefix handling (twentyhq#10047)
Revised parsing logic to handle multiple XML prefixes for SAML metadata, improving flexibility in handling diverse metadata structures. Added corresponding test case to ensure robustness of the implementation.
1 parent 700eb2d commit f6ce27b

File tree

2 files changed

+43
-9
lines changed

2 files changed

+43
-9
lines changed

packages/twenty-front/src/modules/settings/security/utils/__tests__/parseSAMLMetadataFromXMLFile.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,31 @@ describe('parseSAMLMetadataFromXMLFile', () => {
2828
},
2929
});
3030
});
31+
it('should parse SAML metadata from XML file with prefix', () => {
32+
const xmlString = `<?xml version="1.0" encoding="UTF-8"?><ns0:EntityDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://test.com" validUntil="2026-02-04T17:46:23.000Z">
33+
<ns0:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
34+
<ns0:KeyDescriptor use="signing">
35+
<ns2:KeyInfo xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
36+
<ns2:X509Data>
37+
<ns2:X509Certificate>test</ns2:X509Certificate>
38+
</ns2:X509Data>
39+
</ns2:KeyInfo>
40+
</ns0:KeyDescriptor>
41+
<ns0:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</ns0:NameIDFormat>
42+
<ns0:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://test.com"/>
43+
<ns0:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://test.com"/>
44+
</ns0:IDPSSODescriptor>
45+
</ns0:EntityDescriptor>`;
46+
const result = parseSAMLMetadataFromXMLFile(xmlString);
47+
expect(result).toEqual({
48+
success: true,
49+
data: {
50+
entityID: 'https://test.com',
51+
ssoUrl: 'https://test.com',
52+
certificate: 'test',
53+
},
54+
});
55+
});
3156
it('should return error if XML is invalid', () => {
3257
const xmlString = 'invalid xml';
3358
const result = parseSAMLMetadataFromXMLFile(xmlString);

packages/twenty-front/src/modules/settings/security/utils/parseSAMLMetadataFromXMLFile.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,36 @@ const validator = z.object({
88
certificate: z.string().min(1),
99
});
1010

11+
const allPrefix = ['md', 'ns0', 'ns2', 'dsig', 'ds'];
12+
1113
const getByPrefixAndKey = (
1214
xmlDoc: Document | Element,
1315
key: string,
14-
prefix = 'md',
16+
prefixList = [...allPrefix],
1517
): Element | undefined => {
18+
if (prefixList.length === 0) return undefined;
1619
return (
17-
xmlDoc.getElementsByTagName(`${prefix}:${key}`)?.[0] ??
18-
xmlDoc.getElementsByTagName(`${key}`)?.[0]
20+
xmlDoc.getElementsByTagName(`${prefixList[0]}:${key}`)?.[0] ??
21+
getByPrefixAndKey(xmlDoc, key, prefixList.slice(1)) ??
22+
xmlDoc.getElementsByTagName(key)?.[0]
1923
);
2024
};
2125

2226
const getAllByPrefixAndKey = (
2327
xmlDoc: Document | Element,
2428
key: string,
25-
prefix = 'md',
26-
) => {
27-
const withPrefix = xmlDoc.getElementsByTagName(`${prefix}:${key}`);
29+
prefixList = [...allPrefix],
30+
): Array<Element> => {
31+
const withPrefix = xmlDoc.getElementsByTagName(`${prefixList[0]}:${key}`);
32+
2833
if (withPrefix.length !== 0) {
2934
return Array.from(withPrefix);
3035
}
36+
37+
if (prefixList.length > 0) {
38+
return getAllByPrefixAndKey(xmlDoc, key, prefixList.slice(1));
39+
}
40+
3141
return Array.from(xmlDoc.getElementsByTagName(`${key}`));
3242
};
3343

@@ -52,16 +62,15 @@ export const parseSAMLMetadataFromXMLFile = (
5262
const keyDescriptors = getByPrefixAndKey(IDPSSODescriptor, 'KeyDescriptor');
5363
if (!keyDescriptors) throw new Error('No KeyDescriptor found');
5464

55-
const keyInfo = getByPrefixAndKey(keyDescriptors, 'KeyInfo', 'ds');
65+
const keyInfo = getByPrefixAndKey(keyDescriptors, 'KeyInfo');
5666
if (!keyInfo) throw new Error('No KeyInfo found');
5767

58-
const x509Data = getByPrefixAndKey(keyInfo, 'X509Data', 'ds');
68+
const x509Data = getByPrefixAndKey(keyInfo, 'X509Data');
5969
if (!x509Data) throw new Error('No X509Data found');
6070

6171
const x509Certificate = getByPrefixAndKey(
6272
x509Data,
6373
'X509Certificate',
64-
'ds',
6574
)?.textContent?.trim();
6675
if (!x509Certificate) throw new Error('No X509Certificate found');
6776

0 commit comments

Comments
 (0)