Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate manifest installers (MSIX) #2215

Merged
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b8499ed
Init manifest installer validation
AmelBawa-msft May 24, 2022
ec4610f
Init test cases and misc enhancements
AmelBawa-msft May 25, 2022
5b80e3d
Adding more unit tests
AmelBawa-msft May 26, 2022
0a89996
Updated manifest validation code
AmelBawa-msft May 27, 2022
22522b3
Updated messages and misc enhancements
AmelBawa-msft May 27, 2022
52ca0de
Added UT for version number struct
AmelBawa-msft May 27, 2022
93e2fc6
Fixed unit test
AmelBawa-msft May 28, 2022
c1378dd
Added flag and handle installer error
AmelBawa-msft May 31, 2022
b32f12b
Updated validate command default options
AmelBawa-msft May 31, 2022
54f4fe8
Addressed comment
AmelBawa-msft Jun 1, 2022
dcd26ab
Addressed comment
AmelBawa-msft Jun 1, 2022
caf62d2
Addressed comment
AmelBawa-msft Jun 1, 2022
73efae4
Addressed comments
AmelBawa-msft Jun 1, 2022
c74feb7
Addressed comment
AmelBawa-msft Jun 1, 2022
b4a7a2b
Check if package is contained within bundle
AmelBawa-msft Jun 2, 2022
07df02b
Adddressed comment and added treatErrorAsWarning
AmelBawa-msft Jun 2, 2022
ed858a0
Updated UT for versions
AmelBawa-msft Jun 3, 2022
43590bd
Addressed comments
AmelBawa-msft Jun 6, 2022
c4de272
Exclude msixbundle for spelling checker
AmelBawa-msft Jun 6, 2022
ba4a49c
Addressed comments
AmelBawa-msft Jun 7, 2022
91a0445
Addressed comments
AmelBawa-msft Jun 8, 2022
0773673
Addressed comments
AmelBawa-msft Jun 9, 2022
4a8848c
Addressed comments
AmelBawa-msft Jun 9, 2022
88ecf0d
Addressed comments
AmelBawa-msft Jun 10, 2022
64c6b3e
Addressed comments
AmelBawa-msft Jun 10, 2022
1cae946
Updated error message
AmelBawa-msft Jun 13, 2022
8f72c2c
Merge branch 'master' into validate-manifest-installer
AmelBawa-msft Jun 14, 2022
d39cd33
Fixed Versions merge conflict
AmelBawa-msft Jun 14, 2022
27aad45
Addressed comments + added one more UT
AmelBawa-msft Jun 20, 2022
4de9034
Updated test yaml package id
AmelBawa-msft Jun 20, 2022
4bd81b5
Addressed comment
AmelBawa-msft Jun 21, 2022
a1ee0ab
Addressed comment
AmelBawa-msft Jun 21, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@
<ClCompile Include="JsonHelper.cpp" />
<ClCompile Include="MsiExecArguments.cpp" />
<ClCompile Include="MsixInfo.cpp" />
<ClCompile Include="MsixManifest.cpp" />
<ClCompile Include="NameNormalization.cpp" />
<ClCompile Include="PackageCollection.cpp" />
<ClCompile Include="PackageTrackingCatalog.cpp" />
Expand Down Expand Up @@ -560,7 +561,7 @@
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\UpdateFlowTest_Portable.yaml">
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\InputNames.txt">
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
Expand Down Expand Up @@ -630,6 +631,21 @@
<CopyFileToFolders Include="TestData\Installer_Exe_DependenciesMultideclaration.yaml">
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\InstallerManifestValidation.msix">
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\InstallerManifestValidation.msixbundle">
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\Manifest-Good-MsixInstaller.yaml">
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\Manifest-Bad-MsixInstaller.yaml">
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\Manifest-Missing-MsixInstallerFields.yaml">
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AppInstallerCLICore\AppInstallerCLICore.vcxproj">
Expand Down
18 changes: 18 additions & 0 deletions src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@
<ClCompile Include="Correlation.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="MsixManifest.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
Expand Down Expand Up @@ -585,5 +588,20 @@
<CopyFileToFolders Include="TestData\InputARPData.txt">
<Filter>TestData</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\InstallerManifestValidation.msix">
<Filter>TestData</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\InstallerManifestValidation.msixbundle">
<Filter>TestData</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\Manifest-Good-MsixInstaller.yaml">
<Filter>TestData</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\Manifest-Bad-MsixInstaller.yaml">
<Filter>TestData</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\Manifest-Missing-MsixInstallerFields.yaml">
<Filter>TestData</Filter>
</CopyFileToFolders>
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion src/AppInstallerCLITests/MsixInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ constexpr std::string_view s_MsixFile_1 = "index.1.0.0.0.msix";
constexpr std::string_view s_MsixFile_2 = "index.2.0.0.0.msix";
constexpr std::string_view s_MsixFileSigned_1 = "index.1.0.0.0.signed.msix";

TEST_CASE("MsixInfo_GetPackageFamilyName", "[msixinfo]")
TEST_CASE("MsixInfo_GetPackageFullName", "[msixinfo]")
{
TestDataFile index(s_MsixFile_1);
Msix::MsixInfo msix(index.GetPath());
Expand Down
88 changes: 88 additions & 0 deletions src/AppInstallerCLITests/MsixManifest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "pch.h"
#include "TestCommon.h"
#include <AppInstallerMsixInfo.h>
#include <AppInstallerDownloader.h>
#include <AppInstallerRuntime.h>
#include <winget/MsixManifest.h>

using namespace std;
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
using namespace TestCommon;
using namespace AppInstaller;
using namespace AppInstaller::Msix;
using namespace Microsoft::WRL;

// Input values
constexpr std::string_view installerManifestValidationMsix = "InstallerManifestValidation.msix";
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
constexpr std::string_view installerManifestValidationMsixBundle = "InstallerManifestValidation.msixbundle";

// Expected
constexpr std::string_view expectedFamilyName = "FakeInstallerForTesting_125rzkzqaqjwj";
PackageVersion expectedPackageVersion = { 0xAAAABBBBCCCCDDDD };
constexpr std::string_view expectedWindowsDesktopName = "Windows.Desktop";
OSVersion expectedWindowsDesktopMinVersion = { 0x000a00003FAB0000 }; // 10.0.16299.0
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
OSVersion expectedWindowsUniversalMinVersion = { 0x000a000000000000 }; // 10.0.0.0

TEST_CASE("MsixManifest_ValidateFieldsParsedFromManifestReader", "[MsixManifest]")
{
ComPtr<IAppxManifestReader> manifestReader;
if (!GetMsixPackageManifestReader(installerManifestValidationMsix, &manifestReader))
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
{
FAIL();
}

Msix::MsixPackageManifest msixManifest(manifestReader);
REQUIRE(expectedFamilyName == msixManifest.GetIdentity().GetPackageFamilyName());
REQUIRE(expectedPackageVersion == msixManifest.GetIdentity().GetVersion());
REQUIRE(2 == msixManifest.GetTargetDeviceFamilies().size());
REQUIRE(expectedWindowsUniversalMinVersion == msixManifest.GetMinimumOSVersion().value());

auto targets = msixManifest.GetTargetDeviceFamilies();
auto windowsDesktop = std::find_if(targets.begin(), targets.end(), [](auto& t) { return t.GetMinVersion() == expectedWindowsDesktopMinVersion; });
REQUIRE(windowsDesktop != targets.end());

auto windowsUniversal = std::find_if(targets.begin(), targets.end(), [](auto& t) { return t.GetMinVersion() == expectedWindowsUniversalMinVersion; });
REQUIRE(windowsUniversal != targets.end());
}

TEST_CASE("MsixManifest_ValidateFieldsParsedFromMsix", "[MsixManifest]")
{
TestDataFile testFile(installerManifestValidationMsix);
MsixInfo msixInfo(testFile.GetPath());

auto appPackageManifests = msixInfo.GetAppPackageManifests();
REQUIRE(1 == appPackageManifests.size());

auto &appPackageManifest = appPackageManifests[0];
REQUIRE(expectedFamilyName == appPackageManifest.GetIdentity().GetPackageFamilyName());
REQUIRE(expectedPackageVersion == appPackageManifest.GetIdentity().GetVersion());
REQUIRE(2 == appPackageManifest.GetTargetDeviceFamilies().size());
REQUIRE(expectedWindowsUniversalMinVersion == appPackageManifest.GetMinimumOSVersion().value());

auto targets = appPackageManifest.GetTargetDeviceFamilies();
auto windowsDesktop = std::find_if(targets.begin(), targets.end(), [](auto& t) { return t.GetMinVersion() == expectedWindowsDesktopMinVersion; });
REQUIRE(windowsDesktop != targets.end());

auto windowsUniversal = std::find_if(targets.begin(), targets.end(), [](auto& t) { return t.GetMinVersion() == expectedWindowsUniversalMinVersion; });
REQUIRE(windowsUniversal != targets.end());
}

TEST_CASE("MsixManifest_ValidateFieldsParsedFromMsixBundle", "[MsixManifest]")
{
TestDataFile testFile(installerManifestValidationMsixBundle);
MsixInfo msixInfo(testFile.GetPath());

auto appPackageManifests = msixInfo.GetAppPackageManifests();
REQUIRE(2 == appPackageManifests.size());

for (auto& appPackageManifest : appPackageManifests)
{
REQUIRE(expectedFamilyName == appPackageManifest.GetIdentity().GetPackageFamilyName());
REQUIRE(expectedPackageVersion == appPackageManifest.GetIdentity().GetVersion());
REQUIRE(1 == appPackageManifest.GetTargetDeviceFamilies().size());
REQUIRE(expectedWindowsDesktopName == appPackageManifest.GetTargetDeviceFamilies().front().GetName());
REQUIRE(expectedWindowsDesktopMinVersion == appPackageManifest.GetTargetDeviceFamilies().front().GetMinVersion());
REQUIRE(expectedWindowsDesktopMinVersion == appPackageManifest.GetMinimumOSVersion().value());
}
}
16 changes: 16 additions & 0 deletions src/AppInstallerCLITests/TestCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <winget/GroupPolicy.h>
#include <winget/UserSettings.h>
#include <AppInstallerMsixInfo.h>
#include <AppInstallerDownloader.h>

namespace TestCommon
{
Expand Down Expand Up @@ -285,4 +286,19 @@ namespace TestCommon

return false;
}

bool GetMsixPackageManifestReader(const std::string_view testFileName, IAppxManifestReader** manifestReader)
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
{
// Locate test file
TestDataFile testFile(testFileName);
auto path = testFile.GetPath().u8string();

// Get the stream for the test file
auto stream = AppInstaller::Utility::GetStreamFromURI(path);

// Get manifest from package reader
Microsoft::WRL::ComPtr<IAppxPackageReader> packageReader;
return AppInstaller::Msix::GetPackageReader(stream.Get(), &packageReader)
&& SUCCEEDED(packageReader->GetManifest(manifestReader));
}
}
4 changes: 4 additions & 0 deletions src/AppInstallerCLITests/TestCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#pragma once
#include <AppInstallerLogging.h>
#include <AppInstallerProgress.h>
#include <AppxPackaging.h>
#include <winget/UserSettings.h>
#include <wil/result.h>

Expand Down Expand Up @@ -135,4 +136,7 @@ namespace TestCommon
// tests calling these functions should skip when not running with admin.
bool InstallCertFromSignedPackage(const std::filesystem::path& package);
bool UninstallCertFromSignedPackage(const std::filesystem::path& package);

// Get manifest reader from a msix file path
bool GetMsixPackageManifestReader(const std::string_view testFileName, IAppxManifestReader** manifestReader);
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
}
Binary file not shown.
Binary file not shown.
13 changes: 13 additions & 0 deletions src/AppInstallerCLITests/TestData/Manifest-Bad-MsixInstaller.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
PackageIdentifier: AppInstallerCliTest.BadMsixInstaller
PackageVersion: 1.2.3.4 # Mismatching value with msix installer
PackageLocale: es-MX
PackageName: es-MX package name
Publisher: es-MX publisher
PackageFamilyName: FakeInstallerForTesting_Bad # Mismatching value with msix installer
MinimumOSVersion: 5.6.7.8 # Mismatching value with msix installer
InstallerType: msix
Installers:
- Architecture: x64
InstallerUrl: InstallerManifestValidation.msix
ManifestType: merged
ManifestVersion: 1.0.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
PackageIdentifier: AppInstallerCliTest.GoodMsixInstaller
PackageVersion: 43690.48059.52428.56797
PackageLocale: es-MX
PackageName: es-MX package name
Publisher: es-MX publisher
PackageFamilyName: FakeInstallerForTesting_125rzkzqaqjwj
MinimumOSVersion: 10.0.0.0
InstallerType: msix
Installers:
- Architecture: x64
InstallerUrl: InstallerManifestValidation.msix
ManifestType: merged
ManifestVersion: 1.0.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
PackageIdentifier: AppInstallerCliTest.GoodMsixInstaller
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
PackageVersion: 43690.48059.52428.56797
PackageLocale: es-MX
PackageName: es-MX package name
Publisher: es-MX publisher
# PackageFamilyName: FakeInstallerForTesting_125rzkzqaqjwj # Missing field
# MinimumOSVersion: 10.0.16299.0 # Missing field
InstallerType: msix
Installers:
- Architecture: x64
InstallerUrl: InstallerManifestValidation.msix
ManifestType: merged
ManifestVersion: 1.0.0
37 changes: 37 additions & 0 deletions src/AppInstallerCLITests/Versions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,40 @@ TEST_CASE("VersionUnknownLessThanLatest", "[versions]")
{
REQUIRE(Version::CreateUnknown() < Version::CreateLatest());
}

TEST_CASE("FourPartsVersionNumber_Success_FourParts", "[versions]")
{
Version expectedVersion("1.2.3.4");
FourPartsVersionNumber versionNumberFromNumber(0x0001000200030004);
FourPartsVersionNumber versionNumberFromString("1.2.3.4");
REQUIRE(expectedVersion == versionNumberFromNumber);
REQUIRE(expectedVersion == versionNumberFromString);
REQUIRE(expectedVersion.ToString() == versionNumberFromNumber.ToString());
REQUIRE(expectedVersion.ToString() == versionNumberFromString.ToString());
}

TEST_CASE("FourPartsVersionNumber_Success_LessThanFourParts", "[versions]")
{
FourPartsVersionNumber versionNumberFromNumber(0x0001000200030000);
FourPartsVersionNumber versionNumberFromString("1.2.3");
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
REQUIRE(versionNumberFromNumber == versionNumberFromString);
}

TEST_CASE("FourPartsVersionNumber_Fail_OverflowComparison", "[versions]")
{
FourPartsVersionNumber versionNumberFromString("1.0.0.65536"); // 65536 => 0x10000
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
FourPartsVersionNumber versionNumberFromNumber(0x0001000000000000); // 1.0.0.0
REQUIRE(versionNumberFromString != versionNumberFromNumber);
REQUIRE("1.0.0.65536" == versionNumberFromString.ToString());
REQUIRE("1.0.0.0" == versionNumberFromNumber.ToString());
}

TEST_CASE("FourPartsVersionNumber_Fail_MoreThanFourParts", "[versions]")
{
REQUIRE_THROWS(FourPartsVersionNumber("1.0.0.0.1"));
}

TEST_CASE("FourPartsVersionNumber_Fail_NonNumeric", "[versions]")
{
REQUIRE_THROWS(FourPartsVersionNumber("1.0.0.a"));
}
78 changes: 77 additions & 1 deletion src/AppInstallerCLITests/YamlManifest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -836,4 +836,80 @@ TEST_CASE("ManifestLocalizationValidation", "[ManifestValidation]")
errors = ValidateManifest(manifest, false);
REQUIRE(errors.size() == 1);
REQUIRE(errors.at(0).ErrorLevel == ValidationError::Level::Warning);
}
}

TEST_CASE("ReadManifestAndValidateMsixInstallers_Success", "[ManifestValidation]")
{
auto testFile = TestDataFile("Manifest-Good-MsixInstaller.yaml");
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
Manifest manifest = YamlParser::CreateFromPath(testFile);

// Update the installer path for testing
REQUIRE(1 == manifest.Installers.size());
auto msixFile = TestDataFile(manifest.Installers[0].Url.c_str());
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
manifest.Installers[0].Url = msixFile.GetPath().u8string();

auto errors = ValidateManifestInstallers(manifest);
REQUIRE(0 == errors.size());
}

TEST_CASE("ReadManifestAndValidateMsixInstallers_Bad", "[ManifestValidation]")
{
auto testFile = TestDataFile("Manifest-Bad-MsixInstaller.yaml");
Manifest manifest = YamlParser::CreateFromPath(testFile);

// Update the installer path for testing
REQUIRE(1 == manifest.Installers.size());
auto msixFile = TestDataFile(manifest.Installers[0].Url.c_str());
manifest.Installers[0].Url = msixFile.GetPath().u8string();

auto errors = ValidateManifestInstallers(manifest);
REQUIRE(3 == errors.size());

// Package family name
REQUIRE(ValidationError::Level::Error == errors[0].ErrorLevel);
REQUIRE(ManifestError::InstallerMsixInconsistencies == errors[0].Message);
REQUIRE("PackageFamilyName" == errors[0].Field);
REQUIRE("FakeInstallerForTesting_125rzkzqaqjwj" == errors[0].Value);

// Package version
REQUIRE(ValidationError::Level::Error == errors[1].ErrorLevel);
REQUIRE(ManifestError::InstallerMsixInconsistencies == errors[1].Message);
REQUIRE("PackageVersion" == errors[1].Field);
REQUIRE("43690.48059.52428.56797" == errors[1].Value);

// Min OS version
REQUIRE(ValidationError::Level::Error == errors[2].ErrorLevel);
REQUIRE(ManifestError::InstallerMsixInconsistencies == errors[2].Message);
REQUIRE("MinimumOSVersion" == errors[2].Field);
REQUIRE("10.0.0.0" == errors[2].Value);
}

TEST_CASE("ReadManifestAndValidateMsixInstallers_MissingFields", "[ManifestValidation]")
{
auto testFile = TestDataFile("Manifest-Missing-MsixInstallerFields.yaml");
Manifest manifest = YamlParser::CreateFromPath(testFile);

// Update the installer path for testing
REQUIRE(1 == manifest.Installers.size());
auto msixFile = TestDataFile(manifest.Installers[0].Url.c_str());
manifest.Installers[0].Url = msixFile.GetPath().u8string();

for (bool treatErrorAsWarning : { false, true })
{
auto errors = ValidateManifestInstallers(manifest, treatErrorAsWarning);
auto expectedLevel = treatErrorAsWarning ? ValidationError::Level::Warning : ValidationError::Level::Error;
REQUIRE(2 == errors.size());

// Package family name
REQUIRE(expectedLevel == errors[0].ErrorLevel);
REQUIRE(ManifestError::OptionalFieldMissing == errors[0].Message);
REQUIRE("PackageFamilyName" == errors[0].Field);
REQUIRE("FakeInstallerForTesting_125rzkzqaqjwj" == errors[0].Value);

// Min OS version
REQUIRE(expectedLevel == errors[1].ErrorLevel);
REQUIRE(ManifestError::OptionalFieldMissing == errors[1].Message);
REQUIRE("MinimumOSVersion" == errors[1].Field);
REQUIRE("10.0.0.0" == errors[1].Value);
}
}
2 changes: 2 additions & 0 deletions src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="DODownloader.h" />
<ClInclude Include="Public\winget\MsixManifest.h" />
<ClInclude Include="Public\winget\AdminSettings.h" />
<ClInclude Include="Public\winget\Debugging.h" />
<ClInclude Include="Public\winget\DependenciesGraph.h" />
Expand Down Expand Up @@ -379,6 +380,7 @@
<ClCompile Include="MsixInfo.cpp">
<ExcludedFromBuild Condition="'$(Configuration)'=='Fuzzing'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MsixManifest.cpp" />
<ClCompile Include="NameNormalization.cpp" />
<ClCompile Include="Regex.cpp" />
<ClCompile Include="Registry.cpp" />
Expand Down
Loading