diff --git a/media/yarn.lock b/media/yarn.lock index f8fe047..e2a9037 100644 --- a/media/yarn.lock +++ b/media/yarn.lock @@ -5669,16 +5669,7 @@ string-convert@^0.2.0: resolved "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97" integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: version "4.2.3" resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -5749,14 +5740,7 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== diff --git a/package.json b/package.json index bdb8c75..ac06b6a 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,13 @@ ], "activationEvents": [ "onStartFinished", - "onWebviewPanel:alicloud-api-webview" + "onWebviewPanel:alicloud-api-webview", + "onLanguage:javascript", + "onLanguage:typescript", + "onLanguage:go", + "onLanguage:php", + "onLanguage:python", + "onLanguage:java" ], "main": "./dist/extension", "contributes": { @@ -24,6 +30,10 @@ "command": "alicloud.api.autoImport", "title": "导入依赖" }, + { + "command": "alicloud.api.akSecurityHelper", + "title": "凭据的安全使用方案" + }, { "command": "alicloud.api.feedback", "title": "反馈", diff --git a/src/commands.ts b/src/commands.ts index b9bc2c3..5894b5e 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -2,6 +2,7 @@ import { PontManager } from "pontx-manager"; import * as _ from "lodash"; import * as vscode from "vscode"; import { + AKHelperWithLanguage, findAlicloudAPIConfig, findInterface, getSpecInfoFromName, @@ -200,39 +201,6 @@ export class AlicloudApiCommands { }); }); - // vscode.commands.registerCommand("alicloud.api.regenerateAPIMocks", async (event) => { - // const filePaths: string[] = (event.path || "")?.split("/"); - // const lastMocksIndex = filePaths.lastIndexOf("mocks"); - // const names = filePaths.slice(lastMocksIndex + 1); - // names.push(names.pop().replace(".ts", "")); - - // if (service.pontManager.localPontSpecs?.[0]?.name) { - // showProgress("重新生成 API Mocks", service.pontManager, async (log) => { - // log("代码生成中..."); - // await wait(100); - - // let specName, modName, apiName; - // if (names.length === 3) { - // [specName, modName, apiName] = names; - // } else { - // [specName, apiName] = names; - // } - // const mocksPlugin = await service.pontManager.innerManagerConfig.plugins.mocks?.instance; - // const mocksOptions = await service.pontManager.innerManagerConfig.plugins.mocks?.options; - // const mocksCode = await mocksPlugin.getAPIMockCode( - // service.pontManager, - // mocksOptions, - // apiName, - // modName, - // specName, - // ); - // fs.writeFileSync(event.path, mocksCode, "utf-8"); - - // log("API Mocks 生成成功!"); - // vscode.window.showInformationMessage("API Mocks生成成功!"); - // }); - // } - // }); vscode.commands.registerCommand("alicloud.api.autoImport", (...argus) => { const diagnostic = argus[0]; const missingDep = argus[1]; @@ -243,6 +211,17 @@ export class AlicloudApiCommands { } }); + vscode.commands.registerCommand("alicloud.api.akSecurityHelper", (...argus) => { + const document = argus[0]; + if (AKHelperWithLanguage[document.languageId]) { + vscode.env.openExternal(vscode.Uri.parse(AKHelperWithLanguage[document.languageId])); + } else { + vscode.env.openExternal( + vscode.Uri.parse("https://help.aliyun.com/zh/sdk/developer-reference/ak-security-scheme"), + ); + } + }); + vscode.commands.registerCommand("alicloud.api.fetchRemote", (config) => { const pontManager = service.pontManager; @@ -329,23 +308,5 @@ export class AlicloudApiCommands { spec: spec?.apis?.[`${result.apiKey}`], }); }); - // vscode.commands.registerTextEditorCommand("alicloud.api.viewMocks", async (editor, edit) => { - // const isSingleSpec = PontManager.checkIsSingleSpec(service.pontManager); - // const result = (await findInterface(editor, !isSingleSpec, service.pontManager)) || ({} as any); - // const spec = PontManager.getSpec(service.pontManager, result.specName); - - // if (!result.apiName) { - // vscode.window.showErrorMessage("未找到该 OpenAPI"); - // return; - // } - - // const namespace = [result.specName, result.modName, result.apiName].filter((id) => id).join("/"); - // const mocksFilePath = path.join(service.pontManager.innerManagerConfig.outDir, "mocks", namespace + ".ts"); - // vscode.commands.executeCommand("vscode.open", vscode.Uri.file(mocksFilePath)); - // }); - - // vscode.commands.registerCommand('alicloud.api.refreshPontExplorer', () => { - // service.treeDataProvider.refresh(); - // }); } } diff --git a/src/extension.ts b/src/extension.ts index d995aea..e4b2282 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,4 @@ "use strict"; -// The module 'vscode' contains the VS Code extensibility API -// Import the module and reference it with the alias vscode in your code below import { PontManager } from "pontx-manager"; import PontMetaFetchPlugin from "pontx-meta-fetch-plugin"; import * as vscode from "vscode"; @@ -16,12 +14,9 @@ import autoCompletion from "./provider/autoCompletion"; import autofix from "./provider/autofix"; import hoverInfo from "./provider/hoverProvider"; import { getProfileInfoInstance } from "./profileManager"; +import { registerLinter } from "./provider/linter"; export async function activate(context: vscode.ExtensionContext) { - // if (!vscode.workspace.rootPath) { - // return; - // } - // registerConfigSchema(context); const pontxConfig = await findAlicloudAPIConfig(context); if (!pontxConfig) { @@ -87,6 +82,8 @@ export async function activate(context: vscode.ExtensionContext) { autofix(context); // hover提示 hoverInfo(context); + // 代码诊断 + registerLinter(context); } } catch (e) { vscode.window.showErrorMessage(e.message); diff --git a/src/provider/autofix.ts b/src/provider/autofix.ts index 0030ab0..b283902 100644 --- a/src/provider/autofix.ts +++ b/src/provider/autofix.ts @@ -2,9 +2,10 @@ * @description: Quick fix need to be used with a Linter */ import * as vscode from "vscode"; -import { containsAnySubstring, fileSel, getSpecInfoFromName } from "../utils"; +import { containsAnySubstring, fileSel, getSpecInfoFromName, SDKLanguageLabel } from "../utils"; import { getDepsByLanguage } from "../common/generateImport"; import { alicloudAPIMessageService } from "../Service"; +import { LintRules } from "./linter"; class CodeActionProvider { provideCodeActions( @@ -21,16 +22,13 @@ class CodeActionProvider { const document = editor.document; const errorText = document.getText(item?.range); const service = alicloudAPIMessageService; - const products = service.pontManager.localPontSpecs - .map((pontSpec) => { - return getSpecInfoFromName(pontSpec?.name)[0]; - }) + const products = service.pontManager.localPontSpecs.map((pontSpec) => { + return getSpecInfoFromName(pontSpec?.name)[0]; + }); + // 未导入依赖的诊断建议 if (getDepsByLanguage(errorText, item.range)?.includes(errorText) || containsAnySubstring(errorText, products)) { - const autoImportAction = new vscode.CodeAction( - item.message + "导入依赖", - vscode.CodeActionKind.QuickFix, - ); + const autoImportAction = new vscode.CodeAction(item.message + "导入依赖", vscode.CodeActionKind.QuickFix); const newDiagnostic = new vscode.Diagnostic(item.range, "importLists", vscode.DiagnosticSeverity.Error); // 自动修复命令注册 autoImportAction.command = { @@ -40,6 +38,23 @@ class CodeActionProvider { }; return autoImportAction; } + + // AccessKey 可能泄露的诊断建议 + const lintResult = LintRules?.find((rule) => rule.source === item.source); + if (lintResult) { + // 自动修复命令注册 + const codeAction = new vscode.CodeAction( + `${lintResult.methods[0]?.title}(${SDKLanguageLabel[document.languageId] || "阿里云 SDK"})`, + vscode.CodeActionKind.QuickFix, + ); + codeAction.command = { + title: `${lintResult.methods[0]?.title}(${SDKLanguageLabel[document.languageId] || "阿里云 SDK"})`, + command: lintResult.methods[0]?.command, + arguments: [document], + }; + + return codeAction; + } }); return result; diff --git a/src/provider/linter.ts b/src/provider/linter.ts new file mode 100644 index 0000000..9efdaba --- /dev/null +++ b/src/provider/linter.ts @@ -0,0 +1,108 @@ +/** + * @description: Linter of the Code + */ +import * as vscode from "vscode"; +import { fileSel } from "../utils"; + +class Rule { + LintName: string; + pattern: string; + message: string; + source: string; + information: string; + methods: Array<{ + title: string; + command: string; + }>; +} +export const LintRules: Array = [ + { + LintName: "AccessKey-NewAK", + source: "Alicloud Access Key Lint", + information: "在此处透露了 Access Key。", + pattern: `^LTAI(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)[A-Za-z\\d]{12}$|^LTAI(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)[A-Za-z\\d]{16}$|^LTAI(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)[A-Za-z\\d]{18}$|^LTAI(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)[A-Za-z\\d]{20}$|^LTAI(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)[A-Za-z\\d]{22}$`, + message: "在工程中硬编码 Access Key ID/Secret 容易发生凭证数据泄漏,并进而威胁到您账号下所有资源的安全性。", + methods: [{ title: "凭据的安全使用方案", command: "alicloud.api.akSecurityHelper" }], + }, + { + LintName: "AccessSecret", + source: "Alicloud AccessKey Lint", + information: "在此处透露了 Access Secret。", + pattern: `[a-zA-Z0-9]{30}`, + message: "在工程中硬编码 Access Key ID/Secret 容易发生凭证数据泄漏,并进而威胁到您账号下所有资源的安全性。", + methods: [{ title: "凭据的安全使用方案", command: "alicloud.api.akSecurityHelper" }], + }, +]; + +export function searchCode( + diagnosticCollection: vscode.Diagnostic[], + rule: Rule, + text: string, + document: vscode.TextDocument, +) { + const regex = new RegExp(rule.pattern, "gi"); + const strRegex = new RegExp(`[\'\"\`](.*?)[\'\"\`]`, "gi"); + let strMatch; + let matchTexts = []; + while ((strMatch = strRegex.exec(text)) !== null) { + const range = new vscode.Range(document.positionAt(strMatch.index), document.positionAt(strRegex.lastIndex)); + const matchText = document.getText(range); + const pureString = matchText.substring(1, matchText.length - 1); + const isMatch = regex.test(pureString); + if (isMatch) { + matchTexts.push(pureString); + diagnosticCollection.push({ + code: "", + message: rule.message, + range: range, + severity: vscode.DiagnosticSeverity.Error, + source: rule.source, + relatedInformation: [ + new vscode.DiagnosticRelatedInformation(new vscode.Location(document.uri, range), rule.information), + ], + }); + } + } + return matchTexts; +} + +export async function updateDiagnostics( + document: vscode.TextDocument, + collection: vscode.DiagnosticCollection, +): Promise { + if ((fileSel as any)?.find((sel) => sel.language === document.languageId)) { + const text = document.getText(); + + let diagnosticCollection: vscode.Diagnostic[] = []; + + LintRules.forEach((rule) => { + searchCode(diagnosticCollection, rule, text, document); + }); + + collection.set(document.uri, diagnosticCollection); + } else { + collection.clear(); + } +} + +export async function registerLinter(context: vscode.ExtensionContext) { + // 插件诊断器 + const collection = vscode.languages.createDiagnosticCollection("alicloud-linter"); + if (vscode.window.activeTextEditor) { + updateDiagnostics(vscode.window.activeTextEditor.document, collection); + } + context.subscriptions.push( + vscode.window.onDidChangeActiveTextEditor((editor) => { + if (editor) { + updateDiagnostics(editor.document, collection); + } + }), + ); + context.subscriptions.push( + vscode.workspace.onDidChangeTextDocument((editor) => { + if (editor) { + updateDiagnostics(editor.document, collection); + } + }), + ); +} diff --git a/src/test/suite/extension.test.ts b/src/test/suite/extension.test.ts index fe844ca..f368f9b 100644 --- a/src/test/suite/extension.test.ts +++ b/src/test/suite/extension.test.ts @@ -25,9 +25,4 @@ suite("Extension Test Suite", () => { console.log("alicloud.api.restart successfully executed"); assert.strictEqual("ok", result); }); - - test("Sample test", () => { - assert.strictEqual(-1, [1, 2, 3].indexOf(5)); - assert.strictEqual(-1, [1, 2, 3].indexOf(0)); - }); }); diff --git a/src/test/suite/linter.test.ts b/src/test/suite/linter.test.ts new file mode 100644 index 0000000..e74ce34 --- /dev/null +++ b/src/test/suite/linter.test.ts @@ -0,0 +1,68 @@ +import * as assert from "assert"; +import path from "path"; +import * as vscode from "vscode"; +import { LintRules, searchCode } from "../../provider/linter"; + +suite("Alicloud linter Test Suite", function () { + let document; + + // 运行前的初始化 + setup(async function () { + const text = `String[] tests = { + "LTAIAbcd1234abcd", // Length 16, should match + "LTAIAbcd1234abcdefgh", // Length 20, should match + "LTAIAbcd1234abcdefghij", // Length 22, should match + "LTAIAbcd1234abcdefghijkl", // Length 24, should match + "LTAIAbcd1234abcdefghijklmn", // Length 26, should match + "LTAIabc", // Too short, should not match + "LTAIabcdefghijklm", // Too short, should not match + "LTAIAbcd1234abcdefghijklmnop", // Too long, should not match + "LTAIabcd1234abcdefghij", // Length 22, missing uppercase letter, should not match + "LTAIABCD1234ABCDEFGHIJ", // Length 22, missing lowercase letter, should not match + "LTAIAbcdabcdabcdefghij", // Length 22, missing digit, should not match + "LTAIAbcd1234abcdefghiJ", // Length 22, should match + "ltaiAbcd1234abcdefghij", // Length 22, does not start with "LTAI", should not match + "LTAIAbcd1234abcdefghi", // Length 21, should not match + };`; + + const uri = vscode.Uri.file(path.join(__dirname, "testDocument.txt")); + + // 写入自定义文本到文件 + await vscode.workspace.fs.writeFile(uri, Buffer.from(text, "utf8")); + + // 打开文件作为 TextDocument + document = await vscode.workspace.openTextDocument(uri); + }); + + // 测试指定单词是否存在于文档中 + test("Check if document contains the AK", async function () { + const text = document.getText(); + + let diagnosticCollection: vscode.Diagnostic[] = []; + + let matches = []; + + LintRules.forEach((rule) => { + matches = matches.concat(matches, searchCode(diagnosticCollection, rule, text, document)); + }); + + const result = Array.from(new Set(matches)); + + const expected = [ + "LTAIAbcd1234abcd", + "LTAIAbcd1234abcdefghij", + "LTAIAbcd1234abcdefghijklmn", + "LTAIabcd1234abcdefghij", + "LTAIAbcd1234abcdefghiJ", + ]; + + assert.equal(result.toString(), expected.toString()); + // assert.ok(matches, `test failed`); + }); + + // 清理工作 + teardown(async function () { + const uri = vscode.Uri.file(path.join(__dirname, "testDocument.txt")); + await vscode.workspace.fs.delete(uri, { useTrash: true }); + }); +}); diff --git a/src/utils.ts b/src/utils.ts index faae7c1..a9b0b59 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -487,6 +487,7 @@ export const getRequiredParamsValue = (product: string, version: string, api: st export const fileSel: vscode.DocumentSelector = [ { scheme: "file", language: "typescript" }, + { scheme: "file", language: "javascript" }, { scheme: "file", language: "go" }, { scheme: "file", language: "java" }, { scheme: "file", language: "csharp" }, @@ -494,6 +495,24 @@ export const fileSel: vscode.DocumentSelector = [ { scheme: "file", language: "php" }, ]; +export const SDKLanguageLabel = { + typescript: "V2.0 Node.js SDK", + javascript: "V2.0 Node.js SDK", + go: "V2.0 Go SDK", + java: "V2.0 Java SDK", + python: "V2.0 Python SDK", + php: "V2.0 PHP SDK", +}; + +export const AKHelperWithLanguage = { + typescript: "https://help.aliyun.com/zh/sdk/developer-reference/v2-manage-node-js-access-credentials", + javascript: "https://help.aliyun.com/zh/sdk/developer-reference/v2-manage-node-js-access-credentials", + java: "https://help.aliyun.com/zh/sdk/developer-reference/v2-manage-access-credentials", + go: "https://help.aliyun.com/zh/sdk/developer-reference/v2-manage-go-access-credentials", + python: "https://help.aliyun.com/zh/sdk/developer-reference/v2-manage-python-access-credentials", + php: "https://help.aliyun.com/zh/sdk/developer-reference/v2-manage-php-access-credentials", +}; + export const containsAnySubstring = (targetStr, substrings) => { const language = vscode.window.activeTextEditor?.document.languageId; if (language !== "typescript") {