samlesa 4.3.4 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -87,7 +87,7 @@ function base64LoginRequest(referenceTagXPath, entity, customTagReplacement) {
87
87
  * @param AttributeStatement
88
88
  * @param idpInit
89
89
  */
90
- async function base64LoginResponse({ requestInfo = {}, entity, user = {}, customTagReplacement, encryptThenSign = false, AttributeStatement = [], idpInit }) {
90
+ async function base64LoginResponse({ requestInfo = {}, entity, user = {}, customTagReplacement, encryptThenSign = false, AttributeStatement = [], idpInit, destinationBinding = binding.post, }) {
91
91
  const idpSetting = entity.idp.entitySetting;
92
92
  const spSetting = entity.sp.entitySetting;
93
93
  // @ts-ignore
@@ -100,7 +100,9 @@ async function base64LoginResponse({ requestInfo = {}, entity, user = {}, custom
100
100
  const nameIDFormat = idpSetting.nameIDFormat;
101
101
  const selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : namespace.format.unspecified;
102
102
  if (metadata && metadata.idp && metadata.sp) {
103
- const base = metadata.sp.getAssertionConsumerService(binding.post);
103
+ const base = destinationBinding === binding.artifact
104
+ ? metadata.sp.getAssertionConsumerService(binding.artifact, { mode: 'lenient' })
105
+ : metadata.sp.getAssertionConsumerService(binding.post);
104
106
  let rawSamlResponse;
105
107
  const nowTime = new Date();
106
108
  const spEntityID = metadata.sp.getEntityID();
@@ -108,7 +110,9 @@ async function base64LoginResponse({ requestInfo = {}, entity, user = {}, custom
108
110
  oneMinutesLaterTime.setMinutes(oneMinutesLaterTime.getMinutes() + 5);
109
111
  const OneMinutesLater = oneMinutesLaterTime.toISOString();
110
112
  const now = nowTime.toISOString();
111
- const acl = metadata.sp.getAssertionConsumerService(binding.post);
113
+ const acl = destinationBinding === binding.artifact
114
+ ? metadata.sp.getAssertionConsumerService(binding.artifact, { mode: 'lenient' })
115
+ : metadata.sp.getAssertionConsumerService(binding.post);
112
116
  // @ts-ignore
113
117
  const sessionID = idpSetting?.generateID() ?? `_${randomUUID()}`;
114
118
  const sessionIndex = 'session' + sessionID; // 这个是当前系统的会话索引,用于单点注销
@@ -3,8 +3,6 @@
3
3
  * @author tngan
4
4
  * @desc Declares the actions taken by identity provider
5
5
  */
6
- import { wording, } from './urn.js';
7
- const binding = wording.binding;
8
6
  import Entity from './entity.js';
9
7
  import { namespace } from './urn.js';
10
8
  import postBinding from './binding-post.js';
@@ -68,7 +66,7 @@ export class IdentityProvider extends Entity {
68
66
  sp,
69
67
  }, user, relayState, customTagReplacement, AttributeStatement);
70
68
  case namespace.binding.artifact:
71
- context = await artifactBinding.soapLoginResponse({
69
+ context = await artifactBinding.createLoginResponse({
72
70
  requestInfo,
73
71
  entity: {
74
72
  idp: this,
@@ -91,6 +89,49 @@ export class IdentityProvider extends Entity {
91
89
  type: 'SAMLResponse'
92
90
  };
93
91
  }
92
+ createArtifactResolveRequest(sp, artifact) {
93
+ return artifactBinding.createArtifactResolveRequest({
94
+ requester: this,
95
+ responder: sp,
96
+ artifact,
97
+ });
98
+ }
99
+ async createArtifactResolveResponse(params) {
100
+ const { sp, samlMessage, requestInfo = {}, user = {}, customTagReplacement, encryptThenSign = false, AttributeStatement = [], idpInit = false, inResponseTo = '', } = params;
101
+ const resolvedMessage = samlMessage ?? (await artifactBinding.createLoginResponse({
102
+ requestInfo,
103
+ entity: {
104
+ idp: this,
105
+ sp,
106
+ },
107
+ user,
108
+ customTagReplacement,
109
+ encryptThenSign,
110
+ AttributeStatement,
111
+ idpInit,
112
+ })).samlContent;
113
+ return artifactBinding.createArtifactResolveResponse({
114
+ requester: sp,
115
+ responder: this,
116
+ inResponseTo,
117
+ samlMessage: resolvedMessage,
118
+ });
119
+ }
120
+ parseArtifactResolveRequest(sp, xml) {
121
+ return artifactBinding.parseArtifactResolveRequest({
122
+ requester: sp,
123
+ responder: this,
124
+ xml,
125
+ });
126
+ }
127
+ parseArtifactResolveResponse(sp, xml, inResponseTo) {
128
+ return artifactBinding.parseArtifactResolveResponse({
129
+ requester: this,
130
+ responder: sp,
131
+ xml,
132
+ inResponseTo,
133
+ });
134
+ }
94
135
  /**
95
136
  * Validation of the parsed URL parameters
96
137
  * @param sp ServiceProvider instance
@@ -98,6 +139,13 @@ export class IdentityProvider extends Entity {
98
139
  * @param req RequesmessageSigningOrderst
99
140
  */
100
141
  parseLoginRequest(sp, binding, req) {
142
+ if (binding === namespace.binding.artifact || binding === 'artifact') {
143
+ return artifactBinding.parseLoginRequest({
144
+ idp: this,
145
+ sp,
146
+ request: req,
147
+ });
148
+ }
101
149
  const self = this;
102
150
  return flow({
103
151
  from: sp,
@@ -61,7 +61,7 @@ export class ServiceProvider extends Entity {
61
61
  context = simpleSignBinding.base64LoginRequest({ idp, sp: this }, customTagReplacement);
62
62
  break;
63
63
  case nsBinding.artifact:
64
- context = artifactBinding.soapLoginRequest("/*[local-name(.)='AuthnRequest']", {
64
+ context = artifactBinding.createLoginRequest("/*[local-name(.)='AuthnRequest']", {
65
65
  idp,
66
66
  sp: this
67
67
  }, customTagReplacement);
@@ -76,14 +76,24 @@ export class ServiceProvider extends Entity {
76
76
  type: 'SAMLRequest',
77
77
  };
78
78
  }
79
- async createLoginSoapRequest(idp, binding = 'artifact', config) {
80
- const context = await artifactBinding.soapLoginRequest("/*[local-name(.)='AuthnRequest']", {
79
+ createArtifactResolveRequest(idp, artifact) {
80
+ return artifactBinding.createArtifactResolveRequest({
81
+ requester: this,
82
+ responder: idp,
83
+ artifact,
84
+ });
85
+ }
86
+ async createArtifactResolveResponse(idp, config) {
87
+ const samlMessage = config?.samlMessage ?? artifactBinding.createLoginRequest("/*[local-name(.)='AuthnRequest']", {
81
88
  idp,
82
89
  sp: this,
83
- inResponse: config?.inResponseTo,
84
- relayState: config?.relayState,
85
- }, config?.customTagReplacement);
86
- return context;
90
+ }, config?.customTagReplacement).samlContent;
91
+ return artifactBinding.createArtifactResolveResponse({
92
+ requester: idp,
93
+ responder: this,
94
+ inResponseTo: config?.inResponseTo || '',
95
+ samlMessage,
96
+ });
87
97
  }
88
98
  /**
89
99
  * @desc Validation of the parsed the URL parameters
@@ -92,6 +102,13 @@ export class ServiceProvider extends Entity {
92
102
  * @param {request} req request
93
103
  */
94
104
  parseLoginResponse(idp, binding, request) {
105
+ if (binding === namespace.binding.artifact || binding === 'artifact') {
106
+ return artifactBinding.parseLoginResponse({
107
+ idp,
108
+ sp: this,
109
+ request,
110
+ });
111
+ }
95
112
  const self = this;
96
113
  return flow({
97
114
  from: idp,
@@ -103,31 +120,19 @@ export class ServiceProvider extends Entity {
103
120
  request: request
104
121
  });
105
122
  }
106
- /**
107
- * @desc Parse and validate Artifact Resolve request
108
- * @param {IdentityProvider} idp object of identity provider
109
- * @param {string} xml SOAP request XML string
110
- */
111
- parseLoginRequestResolve(idp, xml) {
112
- const self = this;
113
- return artifactBinding.parseLoginRequestResolve({
114
- idp: idp,
115
- sp: self,
116
- xml: xml
123
+ parseArtifactResolveRequest(idp, xml) {
124
+ return artifactBinding.parseArtifactResolveRequest({
125
+ requester: idp,
126
+ responder: this,
127
+ xml,
117
128
  });
118
129
  }
119
- /**
120
- * @desc Resolve SAML Response by Artifact ID
121
- * @param {IdentityProvider} idp object of identity provider
122
- * @param {string} art Artifact string
123
- * @param {request} req request
124
- */
125
- parseLoginResponseResolve(idp, art, request) {
126
- const self = this;
127
- return artifactBinding.parseLoginResponseResolve({
128
- idp: idp,
129
- sp: self,
130
- art: art
130
+ parseArtifactResolveResponse(idp, xml, inResponseTo) {
131
+ return artifactBinding.parseArtifactResolveResponse({
132
+ requester: this,
133
+ responder: idp,
134
+ xml,
135
+ inResponseTo,
131
136
  });
132
137
  }
133
138
  }
@@ -140,10 +140,27 @@ export const artifactResolveFields = [
140
140
  },
141
141
  ];
142
142
  export const artifactResponseFields = [
143
- { key: 'request', localPath: ['Envelope', 'Body', 'ArtifactResolve'], attributes: ['ID', 'IssueInstant', 'Version'] },
144
- { key: 'issuer', localPath: ['Envelope', 'Body', 'ArtifactResolve', 'Issuer'], attributes: [] },
145
- { key: 'Artifact', localPath: ['Envelope', 'Body', 'ArtifactResolve', 'Artifact'], attributes: [] },
146
- { key: 'signature', localPath: ['Envelope', 'Body', 'ArtifactResolve', 'Signature'], attributes: [], context: true },
143
+ {
144
+ key: 'response',
145
+ localPath: ['Envelope', 'Body', 'ArtifactResponse'],
146
+ attributes: ['ID', 'IssueInstant', 'Version', 'InResponseTo', 'Destination']
147
+ },
148
+ {
149
+ key: 'issuer',
150
+ localPath: ['Envelope', 'Body', 'ArtifactResponse', 'Issuer'],
151
+ attributes: []
152
+ },
153
+ {
154
+ key: 'status',
155
+ localPath: ['Envelope', 'Body', 'ArtifactResponse', 'Status', 'StatusCode'],
156
+ attributes: ['Value']
157
+ },
158
+ {
159
+ key: 'signature',
160
+ localPath: ['Envelope', 'Body', 'ArtifactResponse', 'Signature'],
161
+ attributes: [],
162
+ context: true
163
+ },
147
164
  ];
148
165
  export const loginResponseStatusFields = [
149
166
  { key: 'top', localPath: ['Response', 'Status', 'StatusCode'], attributes: ['Value'] },
@@ -1,122 +1,114 @@
1
- import { getContext } from "./api.js";
2
- import { select } from "xpath";
3
- import { SignedXml } from "xml-crypto-next";
4
- import fs from "fs";
5
- import utility, { flattenDeep } from "./utility.js";
6
- import libsaml from "./libsaml.js";
7
- import { wording } from "./urn.js";
1
+ import fs from 'fs';
8
2
  import { DOMParser } from '@xmldom/xmldom';
3
+ import { select } from 'xpath';
4
+ import { SignedXml } from 'xml-crypto-next';
5
+ import utility, { normalizeCertificates } from './utility.js';
6
+ import libsaml from './libsaml.js';
7
+ import { wording } from './urn.js';
8
+ import { getContext } from './api.js';
9
9
  function toNodeArray(result) {
10
- if (Array.isArray(result))
10
+ if (Array.isArray(result)) {
11
11
  return result;
12
- if (result != null && typeof result === 'object' && 'nodeType' in result)
12
+ }
13
+ if (result != null && typeof result === 'object' && 'nodeType' in result) {
13
14
  return [result];
15
+ }
14
16
  return [];
15
17
  }
16
18
  const certUse = wording.certUse;
17
19
  const docParser = new DOMParser();
18
- async function verifyAndDecryptSoapMessage(xml, opts) {
19
- const { dom } = getContext();
20
- const doc = dom.parseFromString(xml, 'application/xml');
21
- const docParser = new DOMParser();
22
- let type = '';
23
- // 为 SOAP 消息定义 XPath
24
- const artifactResolveXpath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResolve']";
25
- const artifactResponseXpath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResponse']";
26
- // 检测 ArtifactResolve 或 ArtifactResponse 的存在
27
- // @ts-expect-error
28
- const artifactResolveNodes = toNodeArray(select(artifactResolveXpath, doc));
29
- // @ts-expect-error
30
- const artifactResponseNodes = toNodeArray(select(artifactResponseXpath, doc));
31
- // 根据消息类型选择合适的 XPath
32
- let basePath = "";
33
- if (artifactResolveNodes?.length > 0) {
34
- type = 'artifactResolve';
35
- basePath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResolve']";
20
+ function resolvePublicCertificate(signatureNode, opts) {
21
+ if (!opts.keyFile && !opts.metadata) {
22
+ throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
36
23
  }
37
- else if (artifactResponseNodes?.length > 0) {
38
- type = 'artifactResponse';
39
- basePath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResponse']";
24
+ if (opts.keyFile) {
25
+ return fs.readFileSync(opts.keyFile);
40
26
  }
41
- else {
42
- throw new Error('ERR_UNSUPPORTED_SOAP_MESSAGE_TYPE');
27
+ const certificateNode = toNodeArray(select(".//*[local-name(.)='X509Certificate']", signatureNode));
28
+ const metadataCerts = normalizeCertificates(opts.metadata.getX509Certificate(certUse.signing));
29
+ if (certificateNode.length === 0 && metadataCerts.length === 0) {
30
+ throw new Error('NO_SELECTED_CERTIFICATE');
43
31
  }
44
- // 基于 SOAP 结构重新定义 XPath
45
- const messageSignatureXpath = `${basePath}/*[local-name(.)='Signature']`;
46
- // @ts-expect-error
47
- const messageSignatureNode = toNodeArray(select(messageSignatureXpath, doc));
48
- let selection = [];
49
- if (messageSignatureNode?.length > 0) {
50
- selection = selection.concat(messageSignatureNode);
32
+ if (certificateNode.length > 0) {
33
+ const x509CertificateData = certificateNode[0].firstChild?.nodeValue || '';
34
+ const x509Certificate = utility.normalizeCerString(x509CertificateData);
35
+ if (metadataCerts.length > 0 && !metadataCerts.includes(x509Certificate)) {
36
+ throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
37
+ }
38
+ return libsaml.getKeyInfo(x509Certificate).getKey();
51
39
  }
52
- if (selection.length === 0) {
53
- throw new Error('ERR_ZERO_SIGNATURE');
40
+ return libsaml.getKeyInfo(metadataCerts[0]).getKey();
41
+ }
42
+ function extractResolvedMessage(rootNode) {
43
+ const resolvedNodes = toNodeArray(select("./*[local-name()='Response' or local-name()='AuthnRequest' or local-name()='LogoutRequest' or local-name()='LogoutResponse']", rootNode));
44
+ if (resolvedNodes.length === 0) {
45
+ return null;
54
46
  }
55
- return verifySignature(xml, selection, opts);
47
+ return resolvedNodes[0].toString();
56
48
  }
57
- function verifySignature(xml, selection, opts) {
58
- // 尝试所有签名节点
59
- for (const signatureNode of selection) {
49
+ function verifySignature(xml, signatureNodes, opts) {
50
+ for (const signatureNode of signatureNodes) {
60
51
  const sig = new SignedXml();
61
- let verified = false;
62
- sig.signatureAlgorithm = opts.signatureAlgorithm;
63
- if (!opts.keyFile && !opts.metadata) {
64
- throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
65
- }
66
- if (opts.keyFile) {
67
- sig.publicCert = fs.readFileSync(opts.keyFile);
68
- }
69
- if (opts.metadata) {
70
- const certificateNode = select(".//*[local-name(.)='X509Certificate']", signatureNode);
71
- // 证书处理逻辑
72
- let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
73
- if (Array.isArray(metadataCert)) {
74
- metadataCert = flattenDeep(metadataCert);
75
- }
76
- else if (typeof metadataCert === 'string') {
77
- metadataCert = [metadataCert];
78
- }
79
- metadataCert = metadataCert.map(utility.normalizeCerString);
80
- // 没有证书的情况
81
- if (certificateNode.length === 0 && metadataCert.length === 0) {
82
- throw new Error('NO_SELECTED_CERTIFICATE');
83
- }
84
- if (certificateNode.length !== 0) {
85
- const x509CertificateData = certificateNode[0].firstChild.data;
86
- const x509Certificate = utility.normalizeCerString(x509CertificateData);
87
- if (metadataCert.length >= 1 && !metadataCert.includes(x509Certificate)) {
88
- throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
89
- }
90
- sig.publicCert = libsaml.getKeyInfo(x509Certificate).getKey();
91
- }
92
- else {
93
- sig.publicCert = libsaml.getKeyInfo(metadataCert[0]).getKey();
94
- }
95
- }
52
+ sig.publicCert = resolvePublicCertificate(signatureNode, opts);
96
53
  sig.loadSignature(signatureNode);
97
- verified = sig.checkSignature(xml); // 使用原始XML验证
54
+ const verified = sig.checkSignature(xml);
98
55
  if (!verified) {
99
56
  throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE');
100
57
  }
101
- if (sig.getSignedReferences().length < 1) {
58
+ const signedReferences = sig.getSignedReferences();
59
+ if (signedReferences.length < 1) {
102
60
  throw new Error('NO_SIGNATURE_REFERENCES');
103
61
  }
104
- const signedVerifiedXML = sig.getSignedReferences()[0];
105
- const rootNode = docParser.parseFromString(signedVerifiedXML, 'application/xml').documentElement;
106
- // 处理签名的内容
107
- switch (rootNode?.localName) {
108
- case 'ArtifactResolve':
109
- return [true, rootNode.toString(), false, false];
110
- case 'ArtifactResponse':
111
- // @ts-expect-error
112
- const Response = select("/*[local-name()='ArtifactResponse']/*[local-name()='Response']", rootNode);
113
- return [true, Response?.[0].toString(), false, false]; // 签名验证成功但未找到断言
114
- default:
115
- return [true, null, false, true]; // 签名验证成功但未找到可识别的内容
62
+ const signedXml = signedReferences[0];
63
+ const rootNode = docParser.parseFromString(signedXml, 'application/xml').documentElement;
64
+ if (!rootNode) {
65
+ throw new Error('ERR_INVALID_SOAP_PAYLOAD');
116
66
  }
67
+ if (rootNode.localName === 'ArtifactResolve') {
68
+ return {
69
+ verified: true,
70
+ soapContent: xml,
71
+ message: rootNode.toString(),
72
+ type: 'ArtifactResolve',
73
+ resolvedMessage: null,
74
+ };
75
+ }
76
+ if (rootNode.localName === 'ArtifactResponse') {
77
+ return {
78
+ verified: true,
79
+ soapContent: xml,
80
+ message: rootNode.toString(),
81
+ type: 'ArtifactResponse',
82
+ resolvedMessage: extractResolvedMessage(rootNode),
83
+ };
84
+ }
85
+ }
86
+ throw new Error('ERR_UNSUPPORTED_SOAP_MESSAGE_TYPE');
87
+ }
88
+ async function verifyAndDecryptSoapMessage(xml, opts) {
89
+ const { dom } = getContext();
90
+ const doc = dom.parseFromString(xml, 'application/xml');
91
+ const artifactResolveXpath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResolve']";
92
+ const artifactResponseXpath = "/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ArtifactResponse']";
93
+ const artifactResolveNodes = toNodeArray(select(artifactResolveXpath, doc));
94
+ const artifactResponseNodes = toNodeArray(select(artifactResponseXpath, doc));
95
+ let basePath = '';
96
+ if (artifactResolveNodes.length > 0) {
97
+ basePath = artifactResolveXpath;
98
+ }
99
+ else if (artifactResponseNodes.length > 0) {
100
+ basePath = artifactResponseXpath;
101
+ }
102
+ else {
103
+ throw new Error('ERR_UNSUPPORTED_SOAP_MESSAGE_TYPE');
104
+ }
105
+ const messageSignatureXpath = `${basePath}/*[local-name(.)='Signature']`;
106
+ const messageSignatureNodes = toNodeArray(select(messageSignatureXpath, doc));
107
+ if (messageSignatureNodes.length === 0) {
108
+ throw new Error('ERR_ZERO_SIGNATURE');
117
109
  }
118
- return [false, null, false, true];
110
+ return verifySignature(xml, messageSignatureNodes, opts);
119
111
  }
120
112
  export default {
121
- verifyAndDecryptSoapMessage
113
+ verifyAndDecryptSoapMessage,
122
114
  };
@@ -13,7 +13,6 @@ export default class Metadata {
13
13
  * @param {object} extraParse for custom metadata extractor
14
14
  */
15
15
  constructor(xml, extraParse = []) {
16
- this.xmlString = xml.toString();
17
16
  this.xmlString = xml.toString();
18
17
  this.meta = extract(this.xmlString, extraParse.concat([
19
18
  {
package/build/src/soap.js CHANGED
@@ -1,144 +1,73 @@
1
1
  import axios from 'axios';
2
- import https from 'node:https';
3
- import crypto from "node:crypto";
4
2
  import { Builder } from 'xml2js';
5
3
  import iconv from 'iconv-lite';
6
- // 2. 配置 Axios 实例(处理自签名证书)
4
+ import { generateArtifactId, parseArtifact } from './artifact.js';
7
5
  const axiosInstance = axios.create({
8
- httpsAgent: new https.Agent({
9
- rejectUnauthorized: false // 允许自签名证书
10
- })
6
+ timeout: 5000,
11
7
  });
12
- export async function sendArtifactResolve(url, soapRequest) {
13
- try {
14
- const response = await axiosInstance.post(url, soapRequest, {
15
- headers: {
16
- 'Content-Type': 'text/xml',
17
- 'SOAPAction': '"ArtifactResolve"'
18
- },
19
- timeout: 5000 // 5秒超时
20
- });
21
- return response.data;
8
+ function getAxiosErrorPayload(error) {
9
+ if (error?.response?.data) {
10
+ return error.response.data;
22
11
  }
23
- catch (error) {
24
- throw error.response.data;
12
+ if (error instanceof Error) {
13
+ return error;
25
14
  }
15
+ return new Error('ERR_SOAP_REQUEST_FAILED');
26
16
  }
27
- export async function sendArtifactResponse(url, soapRequest) {
17
+ async function sendSoapRequest(url, soapRequest, soapAction) {
28
18
  try {
29
19
  const response = await axiosInstance.post(url, soapRequest, {
30
20
  headers: {
31
- 'Content-Type': 'text/xml',
32
- 'SOAPAction': '"ArtifactResponse"'
21
+ 'Content-Type': 'text/xml; charset=utf-8',
22
+ SOAPAction: `"${soapAction}"`,
33
23
  },
34
- timeout: 5000 // 5秒超时
35
24
  });
36
25
  return response.data;
37
26
  }
38
27
  catch (error) {
39
- throw error.response.data;
28
+ throw getAxiosErrorPayload(error);
40
29
  }
41
30
  }
42
- /**
43
- * @desc generate Art id
44
- *
45
- * @param entityIDString
46
- * @param endpointIndex
47
- */
31
+ export async function sendArtifactResolve(url, soapRequest) {
32
+ return sendSoapRequest(url, soapRequest, 'ArtifactResolve');
33
+ }
34
+ export async function sendArtifactResponse(url, soapRequest) {
35
+ return sendSoapRequest(url, soapRequest, 'ArtifactResponse');
36
+ }
48
37
  export function createArt(entityIDString, endpointIndex = 0) {
49
- // 安全获取 sourceEntityId
50
- let sourceEntityId;
51
- if (typeof entityIDString === "string") {
52
- sourceEntityId = entityIDString;
53
- }
54
- else {
55
- // 确保只在非字符串类型上访问 entityMeta
56
- sourceEntityId = entityIDString.entityMeta.getEntityID();
57
- }
58
- // 1. 固定类型代码 (0x0004 - 2字节)
59
- const typeCode = Buffer.from([0x00, 0x04]);
60
- // 2. 端点索引 (2字节,大端序)
61
- if (endpointIndex < 0 || endpointIndex > 65535) {
62
- throw new Error("Endpoint index must be between 0 and 65535");
63
- }
64
- const endpointBuf = Buffer.alloc(2);
65
- endpointBuf.writeUInt16BE(endpointIndex);
66
- // 3. Source ID - 实体ID的SHA-1哈希 (20字节)
67
- const sourceId = crypto
68
- .createHash("sha1")
69
- .update(sourceEntityId)
70
- .digest();
71
- // 4. Message Handler - 20字节随机值
72
- const messageHandler = crypto.randomBytes(20);
73
- // 组合所有组件 (2+2+20+20 = 44字节)
74
- const artifact = Buffer.concat([
75
- typeCode,
76
- endpointBuf,
77
- sourceId,
78
- messageHandler,
79
- ]);
80
- // 返回Base64编码的Artifact
38
+ const sourceEntityId = typeof entityIDString === 'string'
39
+ ? entityIDString
40
+ : entityIDString.entityMeta.getEntityID();
41
+ const artifact = generateArtifactId(sourceEntityId, endpointIndex);
42
+ const origin = parseArtifact(artifact);
81
43
  return {
82
- artifact: artifact.toString("base64"),
44
+ artifact,
83
45
  origin: {
84
- typeCode: typeCode.readUInt16BE(0), // 改为整数值
85
- endpointIndex: endpointIndex, // 修复字段名并赋正确的值
86
- sourceId: sourceId.toString("hex"), // 转为十六进制
87
- messageHandle: messageHandler.toString("hex"), // 转为十六进制
46
+ typeCode: origin.typeCode,
47
+ endpointIndex: origin.endpointIndex,
48
+ sourceId: origin.sourceId,
49
+ messageHandle: origin.messageHandle,
88
50
  },
89
51
  };
90
52
  }
91
- /**
92
- * @desc generate Art id
93
- * @param artifact
94
- */
95
53
  export function parseArt(artifact) {
96
- // 解码 Base64
97
- if (Object.prototype.toString.call(artifact) !== '[object String]') {
98
- return;
99
- }
100
- const decoded = Buffer.from(artifact, 'base64');
101
- // 确保长度正确(SAML 工件固定为 44 字节)
102
- if (decoded.length !== 44) {
103
- throw new Error(`Invalid artifact length: ${decoded.length}, expected 44 bytes`);
104
- }
105
- // 读取前 4 字节(TypeCode + EndpointIndex)
106
- const typeCode = decoded.readUInt16BE(0);
107
- const endpointIndex = decoded.readUInt16BE(2);
108
- // 使用 Buffer.from() 替代 slice()
109
- const sourceId = Buffer.from(decoded.buffer, // 底层 ArrayBuffer
110
- decoded.byteOffset + 4, // 起始偏移量
111
- 20 // 长度
112
- ).toString('hex');
113
- const messageHandle = Buffer.from(decoded.buffer, // 底层 ArrayBuffer
114
- decoded.byteOffset + 24, // 起始偏移量
115
- 20 // 长度
116
- ).toString('hex');
117
- return { typeCode, endpointIndex, sourceId, messageHandle };
54
+ return parseArtifact(artifact);
118
55
  }
119
- /**
120
- * 将对象转换为 ISO-8859-1 编码的 XML 字符串
121
- * @param {Object} data - 要转换的数据对象
122
- * @returns {Buffer} - ISO-8859-1 编码的 XML 数据 (Buffer)
123
- */
124
56
  export function encodeXmlToIso88591(data) {
125
57
  try {
126
- // 1. 创建 XML 构建器
127
58
  const builder = new Builder({
128
- headless: false, // 包含 XML 声明
129
- renderOpts: { 'pretty': false }, // 紧凑格式
59
+ headless: false,
60
+ renderOpts: { pretty: false },
130
61
  xmldec: {
131
62
  version: '1.0',
132
63
  encoding: 'ISO-8859-1',
133
- standalone: true
134
- }
64
+ standalone: true,
65
+ },
135
66
  });
136
- // 2. 构建 XML 字符串 (UTF-8 格式)
137
67
  const utf8Xml = builder.buildObject(data);
138
- // 3. 转换为 ISO-8859-1 编码的 Buffer
139
68
  return iconv.encode(utf8Xml, 'iso-8859-1');
140
69
  }
141
70
  catch (error) {
142
- throw new Error(`XML 编码失败: ${error.message}`);
71
+ throw new Error(`XML 缂栫爜澶辫触: ${error.message}`);
143
72
  }
144
73
  }