samlesa 2.17.0 → 2.17.1
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.
Potentially problematic release.
This version of samlesa might be problematic. Click here for more details.
- package/build/index.js +2 -1
- package/build/src/binding-artifact.js +330 -146
- package/build/src/entity-sp.js +21 -94
- package/build/src/extractor.js +32 -0
- package/build/src/flow.js +23 -112
- package/build/src/libsaml.js +325 -127
- package/build/src/libsamlSoap.js +115 -0
- package/build/src/schemaValidator.js +1 -5
- package/build/src/soap.js +123 -3
- package/package.json +77 -75
- package/types/api.d.ts +15 -0
- package/types/api.d.ts.map +1 -0
- package/types/binding-post.d.ts +48 -0
- package/types/binding-post.d.ts.map +1 -0
- package/types/binding-redirect.d.ts +54 -0
- package/types/binding-redirect.d.ts.map +1 -0
- package/types/binding-simplesign.d.ts +41 -0
- package/types/binding-simplesign.d.ts.map +1 -0
- package/types/entity-idp.d.ts +38 -0
- package/types/entity-idp.d.ts.map +1 -0
- package/types/entity-sp.d.ts +38 -0
- package/types/entity-sp.d.ts.map +1 -0
- package/types/entity.d.ts +100 -0
- package/types/entity.d.ts.map +1 -0
- package/types/extractor.d.ts +26 -0
- package/types/extractor.d.ts.map +1 -0
- package/types/flow.d.ts +7 -0
- package/types/flow.d.ts.map +1 -0
- package/types/index.d.ts +2 -1
- package/types/index.d.ts.map +1 -1
- package/types/libsaml.d.ts +208 -0
- package/types/libsaml.d.ts.map +1 -0
- package/types/metadata-idp.d.ts +25 -0
- package/types/metadata-idp.d.ts.map +1 -0
- package/types/metadata-sp.d.ts +37 -0
- package/types/metadata-sp.d.ts.map +1 -0
- package/types/metadata.d.ts +58 -0
- package/types/metadata.d.ts.map +1 -0
- package/types/src/binding-artifact.d.ts +24 -29
- package/types/src/binding-artifact.d.ts.map +1 -1
- package/types/src/binding-post.d.ts.map +1 -1
- package/types/src/entity-sp.d.ts +13 -24
- package/types/src/entity-sp.d.ts.map +1 -1
- package/types/src/extractor.d.ts +22 -0
- package/types/src/extractor.d.ts.map +1 -1
- package/types/src/flow.d.ts +1 -0
- package/types/src/flow.d.ts.map +1 -1
- package/types/src/libsaml.d.ts +4 -3
- package/types/src/libsaml.d.ts.map +1 -1
- package/types/src/libsamlSoap.d.ts +7 -0
- package/types/src/libsamlSoap.d.ts.map +1 -0
- package/types/src/schemaValidator.d.ts.map +1 -1
- package/types/src/soap.d.ts +33 -0
- package/types/src/soap.d.ts.map +1 -1
- package/types/src/validator.d.ts.map +1 -1
- package/types/types.d.ts +128 -0
- package/types/types.d.ts.map +1 -0
- package/types/urn.d.ts +195 -0
- package/types/urn.d.ts.map +1 -0
- package/types/utility.d.ts +133 -0
- package/types/utility.d.ts.map +1 -0
- package/types/validator.d.ts +4 -0
- package/types/validator.d.ts.map +1 -0
- package/build/src/schema/XMLSchema.dtd +0 -402
- package/build/src/schema/datatypes.dtd +0 -203
package/build/src/entity-sp.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @desc Declares the actions taken by service provider
|
|
5
5
|
*/
|
|
6
6
|
import Entity from './entity.js';
|
|
7
|
-
import
|
|
7
|
+
import Artifact from './binding-artifact.js';
|
|
8
8
|
import { namespace } from './urn.js';
|
|
9
9
|
import redirectBinding from './binding-redirect.js';
|
|
10
10
|
import postBinding from './binding-post.js';
|
|
@@ -61,12 +61,6 @@ export class ServiceProvider extends Entity {
|
|
|
61
61
|
// Object context = {id, context, signature, sigAlg}
|
|
62
62
|
context = simpleSignBinding.base64LoginRequest({ idp, sp: this }, customTagReplacement);
|
|
63
63
|
break;
|
|
64
|
-
case nsBinding.artifact:
|
|
65
|
-
context = artifactSignBinding.base64LoginRequest("/*[local-name(.)='AuthnRequest']", {
|
|
66
|
-
idp,
|
|
67
|
-
sp: this
|
|
68
|
-
}, customTagReplacement);
|
|
69
|
-
break;
|
|
70
64
|
default:
|
|
71
65
|
// Will support artifact in the next release
|
|
72
66
|
throw new Error('ERR_SP_LOGIN_REQUEST_UNDEFINED_BINDING');
|
|
@@ -78,39 +72,20 @@ export class ServiceProvider extends Entity {
|
|
|
78
72
|
type: 'SAMLRequest',
|
|
79
73
|
};
|
|
80
74
|
}
|
|
81
|
-
|
|
82
|
-
* @desc Generates the Art login request for developers to design their own method
|
|
83
|
-
* @param {IdentityProvider} idp object of identity provider
|
|
84
|
-
* @param {string} binding protocol binding
|
|
85
|
-
* @param {function} customTagReplacement used when developers have their own login response template
|
|
86
|
-
*/
|
|
87
|
-
createLoginRequestArt(idp, binding = 'redirect', customTagReplacement) {
|
|
75
|
+
async createLoginSoapRequest(idp, binding = 'artifact', config) {
|
|
88
76
|
const nsBinding = namespace.binding;
|
|
89
77
|
const protocol = nsBinding[binding];
|
|
90
78
|
if (this.entityMeta.isAuthnRequestSigned() !== idp.entityMeta.isWantAuthnRequestsSigned()) {
|
|
91
79
|
throw new Error('ERR_METADATA_CONFLICT_REQUEST_SIGNED_FLAG');
|
|
92
80
|
}
|
|
93
81
|
let context = null;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
soap: true
|
|
102
|
-
}, customTagReplacement);
|
|
103
|
-
break;
|
|
104
|
-
default:
|
|
105
|
-
// Will support artifact in the next release
|
|
106
|
-
throw new Error('ERR_SP_LOGIN_REQUEST_UNDEFINED_BINDING');
|
|
107
|
-
}
|
|
108
|
-
return {
|
|
109
|
-
...context,
|
|
110
|
-
relayState: this.entitySetting.relayState,
|
|
111
|
-
entityEndpoint: idp.entityMeta.getSingleSignOnService(binding),
|
|
112
|
-
type: 'SAMLRequest',
|
|
113
|
-
};
|
|
82
|
+
context = await artifactSignBinding.soapLoginRequest("/*[local-name(.)='AuthnRequest']", {
|
|
83
|
+
idp,
|
|
84
|
+
sp: this,
|
|
85
|
+
inResponse: config?.inResponseTo,
|
|
86
|
+
relayState: config?.relayState,
|
|
87
|
+
}, config?.customTagReplacement);
|
|
88
|
+
return context;
|
|
114
89
|
}
|
|
115
90
|
/**
|
|
116
91
|
* @desc Validation of the parsed the URL parameters
|
|
@@ -136,68 +111,20 @@ export class ServiceProvider extends Entity {
|
|
|
136
111
|
* @param {string} binding protocol binding
|
|
137
112
|
* @param {request} req request
|
|
138
113
|
*/
|
|
139
|
-
|
|
114
|
+
parseLoginRequestResolve(idp, xml) {
|
|
140
115
|
const self = this;
|
|
141
|
-
return
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
checkSignature: true, // saml response must have signature
|
|
146
|
-
parserType: 'SAMLResponse',
|
|
147
|
-
type: 'login',
|
|
148
|
-
binding: binding,
|
|
149
|
-
request: request
|
|
116
|
+
return Artifact.parseLoginRequestResolve({
|
|
117
|
+
idp: idp,
|
|
118
|
+
sp: self,
|
|
119
|
+
xml: xml
|
|
150
120
|
});
|
|
151
121
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// 1. 固定类型代码 (0x0004 - 2字节)
|
|
160
|
-
const typeCode = Buffer.from([0x00, 0x04]);
|
|
161
|
-
// 2. 端点索引 (2字节,大端序)
|
|
162
|
-
if (endpointIndex < 0 || endpointIndex > 65535) {
|
|
163
|
-
throw new Error('Endpoint index must be between 0 and 65535');
|
|
164
|
-
}
|
|
165
|
-
const endpointBuf = Buffer.alloc(2);
|
|
166
|
-
endpointBuf.writeUInt16BE(endpointIndex);
|
|
167
|
-
// 3. Source ID - 实体ID的SHA-1哈希 (20字节)
|
|
168
|
-
const sourceId = crypto.createHash('sha1')
|
|
169
|
-
.update(sourceEntityId)
|
|
170
|
-
.digest();
|
|
171
|
-
// 4. Message Handler - 20字节随机值
|
|
172
|
-
const messageHandler = crypto.randomBytes(20);
|
|
173
|
-
// 组合所有组件 (2+2+20+20 = 44字节)
|
|
174
|
-
const artifact = Buffer.concat([typeCode, endpointBuf, sourceId, messageHandler]);
|
|
175
|
-
// 返回Base64编码的Artifact
|
|
176
|
-
return artifact.toString('base64');
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* @desc generate Art id
|
|
180
|
-
* @param artifact
|
|
181
|
-
*/
|
|
182
|
-
parseArt(artifact) {
|
|
183
|
-
// 解码 Base64
|
|
184
|
-
const decoded = Buffer.from(artifact, 'base64');
|
|
185
|
-
// 确保长度正确(SAML 工件固定为 44 字节)
|
|
186
|
-
if (decoded.length !== 44) {
|
|
187
|
-
throw new Error(`Invalid artifact length: ${decoded.length}, expected 44 bytes`);
|
|
188
|
-
}
|
|
189
|
-
// 读取前 4 字节(TypeCode + EndpointIndex)
|
|
190
|
-
const typeCode = decoded.readUInt16BE(0);
|
|
191
|
-
const endpointIndex = decoded.readUInt16BE(2);
|
|
192
|
-
// 使用 Buffer.from() 替代 slice()
|
|
193
|
-
const sourceId = Buffer.from(decoded.buffer, // 底层 ArrayBuffer
|
|
194
|
-
decoded.byteOffset + 4, // 起始偏移量
|
|
195
|
-
20 // 长度
|
|
196
|
-
).toString('hex');
|
|
197
|
-
const messageHandle = Buffer.from(decoded.buffer, // 底层 ArrayBuffer
|
|
198
|
-
decoded.byteOffset + 24, // 起始偏移量
|
|
199
|
-
20 // 长度
|
|
200
|
-
).toString('hex');
|
|
201
|
-
return { typeCode, endpointIndex, sourceId, messageHandle };
|
|
122
|
+
parseLoginResponseResolve(idp, art, request) {
|
|
123
|
+
const self = this;
|
|
124
|
+
return Artifact.parseLoginResponseResolve({
|
|
125
|
+
idp: idp,
|
|
126
|
+
sp: self,
|
|
127
|
+
art: art
|
|
128
|
+
});
|
|
202
129
|
}
|
|
203
130
|
}
|
package/build/src/extractor.js
CHANGED
|
@@ -54,6 +54,38 @@ export const loginRequestFields = [
|
|
|
54
54
|
context: true
|
|
55
55
|
}
|
|
56
56
|
];
|
|
57
|
+
export const artifactResolveFields = [
|
|
58
|
+
{
|
|
59
|
+
key: 'request',
|
|
60
|
+
localPath: ['ArtifactResolve'],
|
|
61
|
+
attributes: ['ID', 'IssueInstant', 'Version']
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: 'issuer', localPath: ['ArtifactResolve', 'Issuer'], attributes: []
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
key: 'Artifact', localPath: ['ArtifactResolve', 'Artifact'], attributes: []
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
key: 'signature', localPath: ['ArtifactResolve', 'Signature'], attributes: [], context: true
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
export const artifactResponseFields = [
|
|
74
|
+
{
|
|
75
|
+
key: 'request',
|
|
76
|
+
localPath: ['Envelope', 'Body', 'ArtifactResolve'],
|
|
77
|
+
attributes: ['ID', 'IssueInstant', 'Version']
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: 'issuer', localPath: ['Envelope', 'Body', 'ArtifactResolve', 'Issuer'], attributes: []
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
key: 'Artifact', localPath: ['Envelope', 'Body', 'ArtifactResolve', 'Artifact'], attributes: []
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
key: 'signature', localPath: ['Envelope', 'Body', 'ArtifactResolve', 'Signature'], attributes: [], context: true
|
|
87
|
+
},
|
|
88
|
+
];
|
|
57
89
|
// support two-tiers status code
|
|
58
90
|
export const loginResponseStatusFields = [
|
|
59
91
|
{
|
package/build/src/flow.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { base64Decode } from './utility.js';
|
|
2
2
|
import { verifyTime } from './validator.js';
|
|
3
3
|
import libsaml from './libsaml.js';
|
|
4
|
-
import * as uuid from 'uuid';
|
|
5
|
-
import { select } from 'xpath';
|
|
6
|
-
import { DOMParser } from '@xmldom/xmldom';
|
|
7
|
-
import { sendArtifactResolve } from "./soap.js";
|
|
8
4
|
import { extract, loginRequestFields, loginResponseFields, loginResponseStatusFields, loginArtifactResponseStatusFields, logoutRequestFields, logoutResponseFields, logoutResponseStatusFields } from './extractor.js';
|
|
9
5
|
import { BindingNamespace, ParserType, StatusCode, wording } from './urn.js';
|
|
10
6
|
const bindDict = wording.binding;
|
|
@@ -126,58 +122,15 @@ async function redirectFlow(options) {
|
|
|
126
122
|
}
|
|
127
123
|
// proceed the post flow
|
|
128
124
|
async function postFlow(options) {
|
|
129
|
-
const {
|
|
125
|
+
const { request, from, self, parserType, checkSignature = true } = options;
|
|
130
126
|
const { body } = request;
|
|
131
127
|
const direction = libsaml.getQueryParamByType(parserType);
|
|
132
128
|
let encodedRequest = '';
|
|
133
129
|
let samlContent = '';
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
samlContent = String(base64Decode(encodedRequest));
|
|
138
|
-
}
|
|
130
|
+
encodedRequest = body[direction];
|
|
131
|
+
// @ts-ignore
|
|
132
|
+
samlContent = String(base64Decode(encodedRequest));
|
|
139
133
|
/** 增加判断是不是Soap 工件绑定*/
|
|
140
|
-
if (soap) {
|
|
141
|
-
const metadata = {
|
|
142
|
-
idp: from.entityMeta,
|
|
143
|
-
sp: self.entityMeta,
|
|
144
|
-
};
|
|
145
|
-
const spSetting = self.entitySetting;
|
|
146
|
-
let ID = '_' + uuid.v4();
|
|
147
|
-
let url = metadata.idp.getArtifactResolutionService(bindDict.soap);
|
|
148
|
-
let samlSoapRaw = libsaml.replaceTagsByValue(libsaml.defaultArtifactResolveTemplate.context, {
|
|
149
|
-
ID: ID,
|
|
150
|
-
Destination: url,
|
|
151
|
-
Issuer: metadata.sp.getEntityID(),
|
|
152
|
-
IssueInstant: new Date().toISOString(),
|
|
153
|
-
Art: request.Art
|
|
154
|
-
});
|
|
155
|
-
if (!metadata.idp.isWantAuthnRequestsSigned()) {
|
|
156
|
-
samlContent = await sendArtifactResolve(url, samlSoapRaw);
|
|
157
|
-
}
|
|
158
|
-
if (metadata.idp.isWantAuthnRequestsSigned()) {
|
|
159
|
-
const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = spSetting;
|
|
160
|
-
let signatureSoap = libsaml.constructSAMLSignature({
|
|
161
|
-
referenceTagXPath: "//*[local-name(.)='ArtifactResolve']",
|
|
162
|
-
isMessageSigned: false,
|
|
163
|
-
isBase64Output: false,
|
|
164
|
-
transformationAlgorithms: transformationAlgorithms,
|
|
165
|
-
privateKey,
|
|
166
|
-
privateKeyPass,
|
|
167
|
-
signatureAlgorithm,
|
|
168
|
-
rawSamlMessage: samlSoapRaw,
|
|
169
|
-
signingCert: metadata.sp.getX509Certificate('signing'),
|
|
170
|
-
signatureConfig: {
|
|
171
|
-
prefix: 'ds',
|
|
172
|
-
location: {
|
|
173
|
-
reference: "//*[local-name(.)='Issuer']",
|
|
174
|
-
action: 'after'
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
samlContent = await sendArtifactResolve(url, signatureSoap);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
134
|
const verificationOptions = {
|
|
182
135
|
metadata: from.entityMeta,
|
|
183
136
|
signatureAlgorithm: from.entitySetting.requestSignatureAlgorithm,
|
|
@@ -186,7 +139,7 @@ async function postFlow(options) {
|
|
|
186
139
|
let decryptRequired = from.entitySetting.isAssertionEncrypted;
|
|
187
140
|
let extractorFields = [];
|
|
188
141
|
// validate the xml first
|
|
189
|
-
let res = await libsaml.isValidXml(samlContent
|
|
142
|
+
let res = await libsaml.isValidXml(samlContent).catch((error) => {
|
|
190
143
|
return Promise.reject('ERR_EXCEPTION_VALIDATE_XML');
|
|
191
144
|
});
|
|
192
145
|
if (res !== true) {
|
|
@@ -196,67 +149,25 @@ async function postFlow(options) {
|
|
|
196
149
|
extractorFields = getDefaultExtractorFields(parserType, null);
|
|
197
150
|
}
|
|
198
151
|
// check status based on different scenarios
|
|
199
|
-
await checkStatus(samlContent, parserType
|
|
152
|
+
await checkStatus(samlContent, parserType);
|
|
200
153
|
/**检查签名顺序 */
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (!decryptRequired) {
|
|
208
|
-
extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
|
|
209
|
-
}
|
|
210
|
-
if (parserType === 'SAMLResponse' && decryptRequired) {
|
|
211
|
-
// 1. 解密断言
|
|
212
|
-
const [decryptedSAML, decryptedAssertion] = await libsaml.decryptAssertionSoap(self, samlContent);
|
|
213
|
-
// 2. 检查解密后的断言是否包含签名
|
|
214
|
-
const assertionDoc = new DOMParser().parseFromString(decryptedAssertion, 'application/xml');
|
|
215
|
-
// @ts-ignore
|
|
216
|
-
const assertionSignatureNodes = select("./*[local-name()='Signature']", assertionDoc.documentElement);
|
|
217
|
-
// 3. 如果存在签名则验证
|
|
218
|
-
if (assertionSignatureNodes.length > 0) {
|
|
219
|
-
// 3.1 创建新的验证选项(保持原配置)
|
|
220
|
-
const assertionVerificationOptions = {
|
|
221
|
-
...verificationOptions,
|
|
222
|
-
isAssertion: true // 添加标识表示正在验证断言
|
|
223
|
-
};
|
|
224
|
-
// 3.2 验证断言签名
|
|
225
|
-
const [assertionVerified, result] = libsaml.verifySignatureSoap(decryptedAssertion, assertionVerificationOptions);
|
|
226
|
-
if (!assertionVerified) {
|
|
227
|
-
return Promise.reject('ERR_FAIL_TO_VERIFY_ASSERTION_SIGNATURE');
|
|
228
|
-
}
|
|
229
|
-
if (assertionVerified) {
|
|
230
|
-
// @ts-ignore
|
|
231
|
-
samlContent = result;
|
|
232
|
-
extractorFields = getDefaultExtractorFields(parserType, result);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
samlContent = decryptedAssertion;
|
|
237
|
-
extractorFields = getDefaultExtractorFields(parserType, decryptedAssertion);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
154
|
+
const [verified, verifiedAssertionNode, isDecryptRequired, noSignature] = libsaml.verifySignature(samlContent, verificationOptions);
|
|
155
|
+
decryptRequired = isDecryptRequired;
|
|
156
|
+
if (isDecryptRequired && noSignature) {
|
|
157
|
+
const result = await libsaml.decryptAssertion(self, samlContent);
|
|
158
|
+
samlContent = result[0];
|
|
159
|
+
extractorFields = getDefaultExtractorFields(parserType, result[1]);
|
|
240
160
|
}
|
|
241
|
-
if (!
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
if (!decryptRequired) {
|
|
253
|
-
extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
|
|
254
|
-
}
|
|
255
|
-
if (parserType === 'SAMLResponse' && decryptRequired && !noSignature) {
|
|
256
|
-
const result = await libsaml.decryptAssertion(self, samlContent);
|
|
257
|
-
samlContent = result[0];
|
|
258
|
-
extractorFields = getDefaultExtractorFields(parserType, result[1]);
|
|
259
|
-
}
|
|
161
|
+
if (!verified && !noSignature && !isDecryptRequired) {
|
|
162
|
+
return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
|
|
163
|
+
}
|
|
164
|
+
if (!isDecryptRequired) {
|
|
165
|
+
extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
|
|
166
|
+
}
|
|
167
|
+
if (parserType === 'SAMLResponse' && isDecryptRequired && !noSignature) {
|
|
168
|
+
const result = await libsaml.decryptAssertion(self, samlContent);
|
|
169
|
+
samlContent = result[0];
|
|
170
|
+
extractorFields = getDefaultExtractorFields(parserType, result[1]);
|
|
260
171
|
}
|
|
261
172
|
const parseResult = {
|
|
262
173
|
samlContent: samlContent,
|
|
@@ -493,7 +404,7 @@ async function postSimpleSignFlow(options) {
|
|
|
493
404
|
}
|
|
494
405
|
return Promise.resolve(parseResult);
|
|
495
406
|
}
|
|
496
|
-
function checkStatus(content, parserType, soap) {
|
|
407
|
+
export function checkStatus(content, parserType, soap) {
|
|
497
408
|
// only check response parser
|
|
498
409
|
if (parserType !== urlParams.samlResponse && parserType !== urlParams.logoutResponse) {
|
|
499
410
|
return Promise.resolve('SKIPPED');
|