@microsoft/m365-spec-parser 0.2.4-alpha.e84e1682b.0 → 0.2.4-alpha.f33089c4c.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.
@@ -5,6 +5,7 @@ import fs from 'fs-extra';
5
5
  import path from 'path';
6
6
  import { ManifestUtil } from '@microsoft/teams-manifest';
7
7
  import { createHash } from 'crypto';
8
+ import { $RefParser } from '@apidevtools/json-schema-ref-parser';
8
9
 
9
10
  // Copyright (c) Microsoft Corporation.
10
11
  /**
@@ -59,6 +60,7 @@ var WarningType;
59
60
  WarningType["ConvertSwaggerToOpenAPI"] = "convert-swagger-to-openapi";
60
61
  WarningType["FuncDescriptionTooLong"] = "function-description-too-long";
61
62
  WarningType["OperationIdContainsSpecialCharacters"] = "operationid-contains-special-characters";
63
+ WarningType["UnsupportedAuthType"] = "unsupported-auth-type";
62
64
  WarningType["GenerateJsonDataFailed"] = "generate-json-data-failed";
63
65
  WarningType["Unknown"] = "unknown";
64
66
  })(WarningType || (WarningType = {}));
@@ -98,6 +100,7 @@ ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please conve
98
100
  ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
99
101
  ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
100
102
  ConstantString.OperationIdContainsSpecialCharacters = "Operation id '%s' in OpenAPI description document contained special characters and was renamed to '%s'.";
103
+ ConstantString.AuthTypeIsNotSupported = "Unsupported authorization type in API '%s'. No authorization will be used.";
101
104
  ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
102
105
  ConstantString.FuncDescriptionTooLong = "The description of the function '%s' is too long. The current length is %s characters, while the maximum allowed length is %s characters.";
103
106
  ConstantString.GenerateJsonDataFailed = "Failed to generate JSON data for api: %s due to %s.";
@@ -112,12 +115,7 @@ ConstantString.AdaptiveCardType = "AdaptiveCard";
112
115
  ConstantString.TextBlockType = "TextBlock";
113
116
  ConstantString.ImageType = "Image";
114
117
  ConstantString.ContainerType = "Container";
115
- ConstantString.RegistrationIdPostfix = {
116
- apiKey: "REGISTRATION_ID",
117
- oauth2: "CONFIGURATION_ID",
118
- http: "REGISTRATION_ID",
119
- openIdConnect: "REGISTRATION_ID",
120
- };
118
+ ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
121
119
  ConstantString.ResponseCodeFor20X = [
122
120
  "200",
123
121
  "201",
@@ -129,6 +127,7 @@ ConstantString.ResponseCodeFor20X = [
129
127
  "207",
130
128
  "208",
131
129
  "226",
130
+ "2XX",
132
131
  "default",
133
132
  ];
134
133
  ConstantString.AllOperationMethods = [
@@ -206,11 +205,32 @@ class Utils {
206
205
  static isAPIKeyAuth(authScheme) {
207
206
  return authScheme.type === "apiKey";
208
207
  }
208
+ static isAPIKeyAuthButNotInCookie(authScheme) {
209
+ return authScheme.type === "apiKey" && authScheme.in !== "cookie";
210
+ }
209
211
  static isOAuthWithAuthCodeFlow(authScheme) {
210
212
  return !!(authScheme.type === "oauth2" &&
211
213
  authScheme.flows &&
212
214
  authScheme.flows.authorizationCode);
213
215
  }
216
+ static isNotSupportedAuth(authSchemeArray) {
217
+ if (authSchemeArray.length === 0) {
218
+ return false;
219
+ }
220
+ if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
221
+ return true;
222
+ }
223
+ for (const auths of authSchemeArray) {
224
+ if (auths.length === 1) {
225
+ if (Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme) ||
226
+ Utils.isBearerTokenAuth(auths[0].authScheme) ||
227
+ Utils.isAPIKeyAuthButNotInCookie(auths[0].authScheme)) {
228
+ return false;
229
+ }
230
+ }
231
+ }
232
+ return true;
233
+ }
214
234
  static getAuthArray(securities, spec) {
215
235
  var _a;
216
236
  const result = [];
@@ -235,6 +255,20 @@ class Utils {
235
255
  result.sort((a, b) => a[0].name.localeCompare(b[0].name));
236
256
  return result;
237
257
  }
258
+ static getAuthMap(spec) {
259
+ const authMap = {};
260
+ for (const url in spec.paths) {
261
+ for (const method in spec.paths[url]) {
262
+ const operation = spec.paths[url][method];
263
+ const authArray = Utils.getAuthArray(operation.security, spec);
264
+ if (authArray && authArray.length > 0) {
265
+ const currentAuth = authArray[0][0];
266
+ authMap[operation.operationId] = currentAuth;
267
+ }
268
+ }
269
+ }
270
+ return authMap;
271
+ }
238
272
  static getAuthInfo(spec) {
239
273
  let authInfo = undefined;
240
274
  for (const url in spec.paths) {
@@ -705,6 +739,9 @@ class Validator {
705
739
  reason: [ErrorType.MultipleAuthNotSupported],
706
740
  };
707
741
  }
742
+ if (this.projectType === ProjectType.Copilot) {
743
+ return { isValid: true, reason: [] };
744
+ }
708
745
  for (const auths of authSchemeArray) {
709
746
  if (auths.length === 1) {
710
747
  if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
@@ -1656,7 +1693,7 @@ function inferProperties(card) {
1656
1693
 
1657
1694
  // Copyright (c) Microsoft Corporation.
1658
1695
  class ManifestUpdater {
1659
- static async updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options, authInfo, existingPluginManifestInfo) {
1696
+ static async updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options, authMap, existingPluginManifestInfo) {
1660
1697
  const manifest = await fs.readJSON(manifestPath);
1661
1698
  const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
1662
1699
  const useCopilotExtensionsInSchema = await ManifestUtil.useCopilotExtensionsInSchema(manifest);
@@ -1686,7 +1723,7 @@ class ManifestUpdater {
1686
1723
  }
1687
1724
  const appName = this.removeEnvs(manifest.name.short);
1688
1725
  const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
1689
- const [apiPlugin, warnings] = await ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options, existingPluginManifestInfo);
1726
+ const [apiPlugin, warnings] = await ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authMap, options, existingPluginManifestInfo);
1690
1727
  return [manifest, apiPlugin, warnings];
1691
1728
  }
1692
1729
  static updateManifestDescription(manifest, spec) {
@@ -1708,28 +1745,13 @@ class ManifestUpdater {
1708
1745
  throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(schema)), ErrorType.UpdateManifestFailed);
1709
1746
  }
1710
1747
  }
1711
- static async generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options, existingPluginManifestInfo) {
1748
+ static async generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authMap, options, existingPluginManifestInfo) {
1712
1749
  var _a, _b, _c, _d;
1713
1750
  const warnings = [];
1714
1751
  const functions = [];
1715
- const functionNames = [];
1752
+ const functionNamesMap = {};
1716
1753
  const conversationStarters = [];
1717
1754
  const paths = spec.paths;
1718
- const pluginAuthObj = {
1719
- type: "None",
1720
- };
1721
- if (authInfo) {
1722
- if (Utils.isOAuthWithAuthCodeFlow(authInfo.authScheme)) {
1723
- pluginAuthObj.type = "OAuthPluginVault";
1724
- }
1725
- else if (Utils.isBearerTokenAuth(authInfo.authScheme)) {
1726
- pluginAuthObj.type = "ApiKeyPluginVault";
1727
- }
1728
- if (pluginAuthObj.type !== "None") {
1729
- const safeRegistrationIdName = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix[authInfo.authScheme.type]}`);
1730
- pluginAuthObj.reference_id = `\${{${safeRegistrationIdName}}}`;
1731
- }
1732
- }
1733
1755
  for (const pathUrl in paths) {
1734
1756
  const pathItem = paths[pathUrl];
1735
1757
  if (pathItem) {
@@ -1812,7 +1834,29 @@ class ManifestUpdater {
1812
1834
  }
1813
1835
  }
1814
1836
  functions.push(funcObj);
1815
- functionNames.push(safeFunctionName);
1837
+ const authInfo = authMap[operationId];
1838
+ let key = "None";
1839
+ let authName = "None";
1840
+ if (authInfo) {
1841
+ if (Utils.isOAuthWithAuthCodeFlow(authInfo.authScheme)) {
1842
+ key = "OAuthPluginVault";
1843
+ authName = authInfo.name;
1844
+ }
1845
+ else if (Utils.isBearerTokenAuth(authInfo.authScheme) ||
1846
+ Utils.isAPIKeyAuthButNotInCookie(authInfo.authScheme)) {
1847
+ key = "ApiKeyPluginVault";
1848
+ authName = authInfo.name;
1849
+ }
1850
+ }
1851
+ if (functionNamesMap[key]) {
1852
+ functionNamesMap[key].functionNames.push(safeFunctionName);
1853
+ }
1854
+ else {
1855
+ functionNamesMap[key] = {
1856
+ functionNames: [safeFunctionName],
1857
+ authName: authName,
1858
+ };
1859
+ }
1816
1860
  const conversationStarterStr = (summary !== null && summary !== void 0 ? summary : description).slice(0, ConstantString.ConversationStarterMaxLens);
1817
1861
  if (conversationStarterStr) {
1818
1862
  conversationStarters.push(conversationStarterStr);
@@ -1822,6 +1866,12 @@ class ManifestUpdater {
1822
1866
  }
1823
1867
  }
1824
1868
  }
1869
+ if (Object.keys(functionNamesMap).length === 0) {
1870
+ functionNamesMap["None"] = {
1871
+ functionNames: [],
1872
+ authName: "None",
1873
+ };
1874
+ }
1825
1875
  let apiPlugin;
1826
1876
  if (await fs.pathExists(apiPluginFilePath)) {
1827
1877
  apiPlugin = await fs.readJSON(apiPluginFilePath);
@@ -1857,24 +1907,35 @@ class ManifestUpdater {
1857
1907
  const relativePath = ManifestUpdater.getRelativePath(existingPluginManifestInfo.manifestPath, existingPluginManifestInfo.specPath);
1858
1908
  apiPlugin.runtimes = apiPlugin.runtimes.filter((runtime) => runtime.spec.url !== relativePath);
1859
1909
  }
1860
- const index = apiPlugin.runtimes.findIndex((runtime) => {
1861
- var _a, _b;
1862
- return runtime.spec.url === specRelativePath &&
1863
- runtime.type === "OpenApi" &&
1864
- ((_b = (_a = runtime.auth) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : "None") === pluginAuthObj.type;
1865
- });
1866
- if (index === -1) {
1867
- apiPlugin.runtimes.push({
1868
- type: "OpenApi",
1869
- auth: pluginAuthObj,
1870
- spec: {
1871
- url: specRelativePath,
1872
- },
1873
- run_for_functions: functionNames,
1910
+ for (const authType in functionNamesMap) {
1911
+ const pluginAuthObj = {
1912
+ type: authType,
1913
+ };
1914
+ const authName = functionNamesMap[authType].authName;
1915
+ if (pluginAuthObj.type !== "None") {
1916
+ const safeRegistrationIdName = Utils.getSafeRegistrationIdEnvName(`${authName}_${ConstantString.RegistrationIdPostfix}`);
1917
+ pluginAuthObj.reference_id = `\${{${safeRegistrationIdName}}}`;
1918
+ }
1919
+ const functionNamesInfo = functionNamesMap[authType];
1920
+ const index = apiPlugin.runtimes.findIndex((runtime) => {
1921
+ var _a, _b;
1922
+ return runtime.spec.url === specRelativePath &&
1923
+ runtime.type === "OpenApi" &&
1924
+ ((_b = (_a = runtime.auth) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : "None") === authType;
1874
1925
  });
1875
- }
1876
- else {
1877
- apiPlugin.runtimes[index].run_for_functions = functionNames;
1926
+ if (index === -1) {
1927
+ apiPlugin.runtimes.push({
1928
+ type: "OpenApi",
1929
+ auth: pluginAuthObj,
1930
+ spec: {
1931
+ url: specRelativePath,
1932
+ },
1933
+ run_for_functions: functionNamesInfo.functionNames,
1934
+ });
1935
+ }
1936
+ else {
1937
+ apiPlugin.runtimes[index].run_for_functions = functionNamesInfo.functionNames;
1938
+ }
1878
1939
  }
1879
1940
  if (!apiPlugin.name_for_human) {
1880
1941
  apiPlugin.name_for_human = appName;
@@ -1915,7 +1976,7 @@ class ManifestUpdater {
1915
1976
  };
1916
1977
  if (authInfo) {
1917
1978
  const auth = authInfo.authScheme;
1918
- const safeRegistrationIdName = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix[authInfo.authScheme.type]}`);
1979
+ const safeRegistrationIdName = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`);
1919
1980
  if (Utils.isAPIKeyAuth(auth) || Utils.isBearerTokenAuth(auth)) {
1920
1981
  composeExtension.authorization = {
1921
1982
  authType: "apiSecretServiceAuth",
@@ -2042,6 +2103,7 @@ class SpecParser {
2042
2103
  };
2043
2104
  this.pathOrSpec = pathOrDoc;
2044
2105
  this.parser = new SwaggerParser();
2106
+ this.refParser = new $RefParser();
2045
2107
  this.options = Object.assign(Object.assign({}, this.defaultOptions), (options !== null && options !== void 0 ? options : {}));
2046
2108
  }
2047
2109
  /**
@@ -2054,12 +2116,15 @@ class SpecParser {
2054
2116
  let hash = "";
2055
2117
  try {
2056
2118
  await this.loadSpec();
2057
- if (!this.parser.$refs.circular) {
2119
+ if (!this.refParser.$refs.circular) {
2058
2120
  await this.parser.validate(this.spec);
2059
2121
  }
2060
2122
  else {
2123
+ // The following code hangs for Graph API, support will be added when SwaggerParser is updated.
2124
+ /*
2061
2125
  const clonedUnResolveSpec = JSON.parse(JSON.stringify(this.unResolveSpec));
2062
2126
  await this.parser.validate(clonedUnResolveSpec);
2127
+ */
2063
2128
  }
2064
2129
  }
2065
2130
  catch (e) {
@@ -2087,7 +2152,7 @@ class SpecParser {
2087
2152
  };
2088
2153
  }
2089
2154
  // Remote reference not supported
2090
- const refPaths = this.parser.$refs.paths();
2155
+ const refPaths = this.refParser.$refs.paths();
2091
2156
  // refPaths [0] is the current spec file path
2092
2157
  if (refPaths.length > 1) {
2093
2158
  errors.push({
@@ -2216,7 +2281,7 @@ class SpecParser {
2216
2281
  throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
2217
2282
  }
2218
2283
  const clonedUnResolveSpec = JSON.parse(JSON.stringify(newUnResolvedSpec));
2219
- const newSpec = (await this.parser.dereference(clonedUnResolveSpec));
2284
+ const newSpec = await this.deReferenceSpec(clonedUnResolveSpec);
2220
2285
  return [newUnResolvedSpec, newSpec];
2221
2286
  }
2222
2287
  catch (err) {
@@ -2226,6 +2291,10 @@ class SpecParser {
2226
2291
  throw new SpecParserError(err.toString(), ErrorType.GetSpecFailed);
2227
2292
  }
2228
2293
  }
2294
+ async deReferenceSpec(spec) {
2295
+ const result = await this.refParser.dereference(spec);
2296
+ return result;
2297
+ }
2229
2298
  /**
2230
2299
  * Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
2231
2300
  * @param manifestPath A file path of the Teams app manifest file to update.
@@ -2242,7 +2311,6 @@ class SpecParser {
2242
2311
  const newSpecs = await this.getFilteredSpecs(filter, signal);
2243
2312
  const newUnResolvedSpec = newSpecs[0];
2244
2313
  const newSpec = newSpecs[1];
2245
- const authInfo = Utils.getAuthInfo(newSpec);
2246
2314
  const paths = newUnResolvedSpec.paths;
2247
2315
  for (const pathUrl in paths) {
2248
2316
  const operations = paths[pathUrl];
@@ -2250,15 +2318,22 @@ class SpecParser {
2250
2318
  const operationItem = operations[method];
2251
2319
  const operationId = operationItem.operationId;
2252
2320
  const containsSpecialCharacters = /[^a-zA-Z0-9_]/.test(operationId);
2253
- if (!containsSpecialCharacters) {
2254
- continue;
2321
+ if (containsSpecialCharacters) {
2322
+ operationItem.operationId = operationId.replace(/[^a-zA-Z0-9]/g, "_");
2323
+ result.warnings.push({
2324
+ type: WarningType.OperationIdContainsSpecialCharacters,
2325
+ content: Utils.format(ConstantString.OperationIdContainsSpecialCharacters, operationId, operationItem.operationId),
2326
+ data: operationId,
2327
+ });
2328
+ }
2329
+ const authArray = Utils.getAuthArray(operationItem.security, newSpec);
2330
+ if (Utils.isNotSupportedAuth(authArray)) {
2331
+ result.warnings.push({
2332
+ type: WarningType.UnsupportedAuthType,
2333
+ content: Utils.format(ConstantString.AuthTypeIsNotSupported, operationId),
2334
+ data: operationId,
2335
+ });
2255
2336
  }
2256
- operationItem.operationId = operationId.replace(/[^a-zA-Z0-9]/g, "_");
2257
- result.warnings.push({
2258
- type: WarningType.OperationIdContainsSpecialCharacters,
2259
- content: Utils.format(ConstantString.OperationIdContainsSpecialCharacters, operationId, operationItem.operationId),
2260
- data: operationId,
2261
- });
2262
2337
  }
2263
2338
  }
2264
2339
  await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
@@ -2271,7 +2346,8 @@ class SpecParser {
2271
2346
  specPath: this.pathOrSpec,
2272
2347
  }
2273
2348
  : undefined;
2274
- const [updatedManifest, apiPlugin, warnings] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options, authInfo, existingPluginManifestInfo);
2349
+ const authMap = Utils.getAuthMap(newSpec);
2350
+ const [updatedManifest, apiPlugin, warnings] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options, authMap, existingPluginManifestInfo);
2275
2351
  result.warnings.push(...warnings);
2276
2352
  await fs.outputJSON(manifestPath, updatedManifest, { spaces: 4 });
2277
2353
  await fs.outputJSON(pluginFilePath, apiPlugin, { spaces: 4 });
@@ -2359,7 +2435,7 @@ class SpecParser {
2359
2435
  this.isSwaggerFile = true;
2360
2436
  }
2361
2437
  const clonedUnResolveSpec = JSON.parse(JSON.stringify(this.unResolveSpec));
2362
- this.spec = (await this.parser.dereference(clonedUnResolveSpec));
2438
+ this.spec = await this.deReferenceSpec(clonedUnResolveSpec);
2363
2439
  }
2364
2440
  }
2365
2441
  getAPIs(spec) {