UNPKG

snyk-nuget-plugin

Version:
345 lines 18.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildDepGraphFromFiles = buildDepGraphFromFiles; exports.buildDepTreeFromFiles = buildDepTreeFromFiles; const fs = require("fs"); const path = require("path"); const csProjParser = require("./parsers/csproj-parser"); const debugModule = require("debug"); const depsParser = require("dotnet-deps-parser"); const dotnetCoreV3Parser = require("./parsers/dotnet-core-v3-parser"); const dotnetCoreParser = require("./parsers/dotnet-core-parser"); const dotnetCoreV2Parser = require("./parsers/dotnet-core-v2-parser"); const dotnetFrameworkParser = require("./parsers/dotnet-framework-parser"); const projectJsonParser = require("./parsers/project-json-parser"); const packagesConfigParser = require("./parsers/packages-config-parser"); const errors_1 = require("../errors"); const types_1 = require("./types"); const dotnet = require("./cli/dotnet"); const nugetFrameworksParser = require("./csharp/nugetframeworks_parser"); const runtimeAssemblyV2 = require("./runtime-assembly-v2"); const runtimeAssembly = require("./runtime-assembly"); const runtime_assembly_v2_1 = require("./runtime-assembly-v2"); const debug = debugModule('snyk'); const PROJECTSDK = 'Microsoft.NET.Sdk'; const PARSERS = { 'dotnet-core': { depParser: dotnetCoreParser, fileContentParser: JSON, }, 'dotnet-core-v2': { depParser: dotnetCoreV2Parser, fileContentParser: JSON, }, 'dotnet-core-v3': { depParser: dotnetCoreV3Parser, fileContentParser: JSON, }, 'packages.config': { depParser: dotnetFrameworkParser, fileContentParser: packagesConfigParser, }, 'project.json': { depParser: dotnetFrameworkParser, fileContentParser: projectJsonParser, }, }; function getPackagesFolder(packagesFolder, projectRootFolder) { if (packagesFolder) { return path.resolve(process.cwd(), packagesFolder); } return path.resolve(projectRootFolder, 'packages'); } function getRootName(root, projectRootFolder, projectNamePrefix) { const defaultRootName = path.basename(root || projectRootFolder || ''); if (projectNamePrefix) { return projectNamePrefix + defaultRootName; } return defaultRootName; } function getFileContents(fileContentPath) { try { debug(`Parsing content of ${fileContentPath}`); return fs.readFileSync(fileContentPath, 'utf-8'); } catch (error) { throw new errors_1.FileNotProcessableError(error); } } function tryToGetFileByName(dir, filename) { const depsFilePath = path.join(dir, filename); try { const depsFile = fs.readFileSync(depsFilePath); if (depsFile) return depsFile; } catch (_) { // Due to race conditions, fs docs suggests to not use .stat or .access to check if a file exists // but instead we should to try and read it. // https://nodejs.org/api/fs.html#fsstatpath-options-callback } return null; } // `dotnet` can publish the .deps file to a variety of places inside the publish folder, depending on what you're // including and targeting. Instead of trying different directories, just scan them all. In most cases, the file // will be in the root directory. (See https://github.com/Azure/azure-functions-vs-build-sdk/issues/518) function findDepsFileInPublishDir(dir, filename) { let renamedFile = null; // Try to get the file via full path. const namedFile = tryToGetFileByName(dir, filename); if (namedFile) return namedFile; for (const item of fs.readdirSync(dir)) { const itemPath = path.join(dir, item); // The file is usually <project>.deps.json, but in edge cases, `dotnet` names it for you. if (itemPath.endsWith('deps.json')) { renamedFile = fs.readFileSync(itemPath); } if (!fs.statSync(itemPath).isDirectory()) { continue; } // Otherwise, look in a nested dir for the same thing. const foundFile = findDepsFileInPublishDir(itemPath, filename); if (!foundFile) { continue; } return foundFile; } return renamedFile || null; } async function getResultsWithPublish(decidedTargetFrameworks, projectPath, safeRoot, projectNameFromManifestFile, nugetFrameworksParserLocation, useFixForImprovedDotnetFalsePositives, resolvedProjectName, projectAssets) { const parser = PARSERS['dotnet-core-v2']; // Loop through all TargetFrameworks supplied and generate a dependency graph for each. const results = []; for (const decidedTargetFramework of decidedTargetFrameworks) { // Run `dotnet publish` to create a self-contained publishable binary with included .dlls for assembly version inspection. const publishDir = await dotnet.publish( // Attempt to feed it the full path to the project file itself, as multiple could exist. If that fails, don't break the flow, just send the folder as previously projectPath || safeRoot, decidedTargetFramework); // Then inspect the dependency graph for the runtimepackage's assembly versions. const filename = `${projectNameFromManifestFile}.deps.json`; const depsFile = findDepsFileInPublishDir(publishDir, filename); if (!depsFile) { throw new errors_1.CliCommandError(`unable to locate ${filename} anywhere inside ${publishDir}, file is needed for runtime resolution to occur, aborting`); } const publishedProjectDeps = JSON.parse(depsFile.toString('utf-8')); // Parse the TargetFramework using Nuget.Frameworks itself, instead of trying to reinvent the wheel, thus ensuring // we have maximum context to use later when building the depGraph. const response = await dotnet.run(nugetFrameworksParserLocation, [ decidedTargetFramework, ]); const targetFrameworkInfo = JSON.parse(response); if (targetFrameworkInfo.IsUnsupported) { throw new errors_1.InvalidManifestError(`dotnet was not able to parse the target framework ${decidedTargetFramework}, it was reported unsupported by the dotnet runtime`); } let assemblyVersions = {}; if (!decidedTargetFramework.includes('netstandard')) { assemblyVersions = runtimeAssembly.generateRuntimeAssemblies(publishedProjectDeps); // Specifically targeting .NET Standard frameworks will not provide any specific runtime assembly information in // the published artifacts files, and can thus not be read more precisely than the .deps file will tell us up-front. // This probably makes sense when looking at https://dotnet.microsoft.com/en-us/platform/dotnet-standard#versions. // As such, we don't generate any runtime assemblies and generate the dependency graph without it. if (useFixForImprovedDotnetFalsePositives) { let projectFolder = ''; // Get the project folder path if (projectPath) { projectFolder = path.dirname(projectPath); } // An important failure point here will be a reference to a version of the dotnet SDK that is // not installed in the environment. Ex: global.json specifies 6.0.100, but the only version install in the env is 8.0.100 // https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet#options-for-displaying-environment-information-and-available-commands await dotnet.execute(['--version'], projectFolder); assemblyVersions = await runtimeAssemblyV2.generateRuntimeAssemblies(projectFolder || safeRoot, assemblyVersions); } } const depGraph = parser.depParser.parse(resolvedProjectName, projectAssets, publishedProjectDeps, assemblyVersions, useFixForImprovedDotnetFalsePositives); results.push({ dependencyGraph: depGraph, targetFramework: decidedTargetFramework, }); } return results; } async function getResultsWithoutPublish(decidedTargetFrameworks, projectPath, safeRoot, nugetFrameworksParserLocation, resolvedProjectName, projectAssets) { const parser = PARSERS['dotnet-core-v3']; const projectFolder = projectPath ? path.dirname(projectPath) : safeRoot; const { sdkVersion, sdkPath } = await (0, runtime_assembly_v2_1.extractSdkInfo)(projectFolder); const localRuntimes = await dotnet.execute(['--list-runtimes'], projectFolder); const runtimeVersion = (0, runtime_assembly_v2_1.findLatestMatchingVersion)(localRuntimes, sdkVersion); const overridesAssemblies = {}; try { const overridesPath = `${path.dirname(sdkPath)}${runtime_assembly_v2_1.PACKS_PATH}${runtimeVersion}/${runtime_assembly_v2_1.PACKAGE_OVERRIDES_FILE}`; const overridesText = fs.readFileSync(overridesPath, 'utf-8'); for (const pkg of overridesText.split('\n')) { if (pkg) { const [name, version] = pkg.split('|'); // Trim any carriage return overridesAssemblies[name] = version.trim(); } } } catch (err) { throw new errors_1.FileNotProcessableError(`Failed to read PackageOverrides.txt, error: ${err}`); } // Loop through all TargetFrameworks supplied and generate a dependency graph for each. const results = []; for (const decidedTargetFramework of decidedTargetFrameworks) { // Parse the TargetFramework using Nuget.Frameworks itself, instead of trying to reinvent the wheel, thus ensuring // we have maximum context to use later when building the depGraph. const response = await dotnet.run(nugetFrameworksParserLocation, [ decidedTargetFramework, ]); const targetFrameworkInfo = JSON.parse(response); if (targetFrameworkInfo.IsUnsupported) { throw new errors_1.InvalidManifestError(`dotnet was not able to parse the target framework ${decidedTargetFramework}, it was reported unsupported by the dotnet runtime`); } const overrides = { overridesAssemblies, overrideVersion: targetFrameworkInfo.Version.split('.') .slice(0, -1) .join('.'), }; let targetFramework = decidedTargetFramework; if (targetFrameworkInfo.Framework === '.NETStandard') { targetFramework = targetFrameworkInfo.DotNetFrameworkName; } const depGraph = parser.depParser.parse(resolvedProjectName, targetFramework, projectAssets, overrides); results.push({ dependencyGraph: depGraph, targetFramework: decidedTargetFramework, }); } return results; } async function buildDepGraphFromFiles(root, targetFile, manifestType, useProjectNameFromAssetsFile, useFixForImprovedDotnetFalsePositives, useImprovedDotnetWithoutPublish, projectNamePrefix, targetFramework) { const safeRoot = root || '.'; const safeTargetFile = targetFile || '.'; const fileContentPath = path.resolve(safeRoot, safeTargetFile); const fileContent = getFileContents(fileContentPath); const parser = PARSERS['dotnet-core-v2']; const projectAssets = await parser.fileContentParser.parse(fileContent); if (!projectAssets.project?.frameworks) { throw new errors_1.FileNotProcessableError(`unable to detect any target framework in manifest file ${safeTargetFile}, a valid one is needed to continue down this path.`); } // Scan all 'frameworks' detected in the project.assets.json file, and use the targetAlias if detected and // otherwise the raw key name, as it's not guaranteed that all framework objects contains a targetAlias. const targetFrameworks = Object.entries(projectAssets.project.frameworks).map(([key, value]) => ('targetAlias' in value ? value.targetAlias : key)); if (targetFrameworks.length <= 0) { throw new errors_1.FileNotProcessableError(`unable to detect a target framework in ${safeTargetFile}, a valid one is needed to continue down this path.`); } if (targetFramework && !targetFrameworks.includes(targetFramework)) { console.warn(`\x1b[33m⚠ WARNING\x1b[0m: Supplied targetframework \x1b[1m${targetFramework}\x1b[0m was not detected in the supplied manifest file. Available targetFrameworks detected was \x1b[1m${targetFrameworks.join(',')}\x1b[0m. Will attempt to build dependency graph anyway, but the operation might fail.`); } let resolvedProjectName = getRootName(root, safeRoot, projectNamePrefix); const projectNameFromManifestFile = projectAssets?.project?.restore?.projectName; if (manifestType === types_1.ManifestType.DOTNET_CORE && useProjectNameFromAssetsFile) { if (projectNameFromManifestFile) { resolvedProjectName = projectNameFromManifestFile; } else { debug(`project.assets.json file doesn't contain a value for 'projectName'. Using default value: ${resolvedProjectName}`); } } // If a specific targetFramework has been requested, only query that, otherwise try to do them all const decidedTargetFrameworks = targetFramework ? [targetFramework] : targetFrameworks.filter((framework) => { // Passing a const value as the project sdk. Why? The targetFile it's project.assets.json, which gets generated // only for the sdk style projects. The assets file won't get generated for projects which rely on packages.config. // The reason behind deciding to call this method is because maybe in the future we want to not support some specific // target frameworks. if (useImprovedDotnetWithoutPublish) { if (!depsParser.isSupportedByV3GraphGeneration(framework, PROJECTSDK)) { return false; } return true; } if (!depsParser.isSupportedByV2GraphGeneration(framework)) { console.warn(`\x1b[33m⚠ WARNING\x1b[0m: The runtime resolution flag is currently only supported for the following TargetFrameworks: .NET versions 6 and higher, all versions of .NET Core and all versions of .NET Standard. Detected a TargetFramework: \x1b[1m${framework}\x1b[0m, which will be skipped.`); return false; } return true; }); if (decidedTargetFrameworks.length == 0) { throw new errors_1.InvalidManifestError(`Was not able to find any supported TargetFrameworks to scan, aborting`); } // Ensure `dotnet` is installed on the system or fail trying. await dotnet.validate(); // Write a .NET Framework Parser to a temporary directory for validating TargetFrameworks. const nugetFrameworksParserLocation = nugetFrameworksParser.generate(); await dotnet.restore(nugetFrameworksParserLocation); // Access the path of this project, to ensure we get the right .csproj file, in case of multiples present const projectPath = projectAssets.project.restore.projectPath; if (!projectPath) { console.warn(`\x1b[33m⚠ WARNING\x1b[0m: Could not detect any projectPath in the project assets file, if your solution contains multiple projects in the same folder, this operation might fail.`); } if (useImprovedDotnetWithoutPublish) { return getResultsWithoutPublish(decidedTargetFrameworks, projectPath, safeRoot, nugetFrameworksParserLocation, resolvedProjectName, projectAssets); } return getResultsWithPublish(decidedTargetFrameworks, projectPath, safeRoot, projectNameFromManifestFile, nugetFrameworksParserLocation, useFixForImprovedDotnetFalsePositives, resolvedProjectName, projectAssets); } async function buildDepTreeFromFiles(root, targetFile, packagesFolderPath, manifestType, useProjectNameFromAssetsFile, projectNamePrefix) { const safeRoot = root || '.'; const safeTargetFile = targetFile || '.'; const fileContentPath = path.resolve(safeRoot, safeTargetFile); const fileContent = getFileContents(fileContentPath); const projectRootFolder = path.resolve(fileContentPath, '../../'); const packagesFolder = getPackagesFolder(packagesFolderPath, projectRootFolder); const tree = { dependencies: {}, meta: {}, name: getRootName(root, projectRootFolder, projectNamePrefix), packageFormatVersion: 'nuget:0.0.0', version: '0.0.0', }; let targetFrameworks; try { if (manifestType === types_1.ManifestType.DOTNET_CORE) { targetFrameworks = csProjParser.getTargetFrameworksFromProjFile(projectRootFolder); } else { // .csproj is in the same directory as packages.config or project.json const fileContentParentDirectory = path.resolve(fileContentPath, '../'); targetFrameworks = csProjParser.getTargetFrameworksFromProjFile(fileContentParentDirectory); // finally, for the .NETFramework project, try to assume the framework using dotnet-deps-parser if (targetFrameworks.length <= 0) { // currently only process packages.config files if (manifestType === types_1.ManifestType.PACKAGES_CONFIG) { const minimumTargetFramework = await packagesConfigParser.getMinimumTargetFramework(fileContent); if (minimumTargetFramework) { targetFrameworks = [minimumTargetFramework]; } } } } } catch (error) { return Promise.reject(error); } // Only supports the first targetFramework we find. // Use the newer `buildDepGraphFromFiles` for better support for multiple target frameworks. const targetFramework = targetFrameworks.length > 0 ? targetFrameworks[0].original : undefined; tree.meta = { targetFramework: targetFramework, }; const parser = PARSERS[manifestType]; const manifest = await parser.fileContentParser.parse(fileContent, tree); if (manifestType === types_1.ManifestType.DOTNET_CORE && useProjectNameFromAssetsFile) { const projectName = manifest?.project?.restore?.projectName; if (projectName) { tree.name = projectName; } else { debug("project.assets.json file doesn't contain a value for 'projectName'. Using default value: " + tree.name); } } return parser.depParser.parse(tree, manifest, targetFramework, packagesFolder); } //# sourceMappingURL=index.js.map