Skip to content

Commit

Permalink
Add Entra Id authentication support for installer download (#5095)
Browse files Browse the repository at this point in the history
- The entra id support for installer download reuses existing entra id
code for rest source access (parsing, authenticating).
- Added yaml manifest parsing for authentication to make manifest in
parity with rest source. Added validation to disable Authentication in
manifest for community repo.
- Validated manually with private Azure blob storage hosted installer.
- Added manifest tests and installer download authentication unit tests
  • Loading branch information
yao-msft authored Dec 23, 2024
1 parent c8061ea commit b49f784
Show file tree
Hide file tree
Showing 52 changed files with 894 additions and 61 deletions.
1 change: 1 addition & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ mdmp
MDs
megamorf
microsoftentraid
microsoftentraidforazureblobstorage
midl
minidump
MINORVERSION
Expand Down
10 changes: 5 additions & 5 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ pool:

variables:
solution: 'src\AppInstallerCLI.sln'
appxPackageDir: '$(Build.ArtifactStagingDirectory)/AppxPackages/'
EnableDetectorVcpkg: true

# Do not set the build version for a PR build.
Expand Down Expand Up @@ -64,6 +63,7 @@ jobs:
buildOutDir: $(Build.SourcesDirectory)\src\$(buildPlatform)\$(buildConfiguration)
buildOutDirAnyCpu: $(Build.SourcesDirectory)\src\AnyCPU\$(buildConfiguration)
artifactsDir: $(Build.ArtifactStagingDirectory)\$(buildPlatform)
appxPackageDir: $(Build.ArtifactStagingDirectory)\$(buildPlatform)\AppxPackages
packageLayoutDir: $(Build.BinariesDirectory)\WingetPackageLayout

steps:
Expand Down Expand Up @@ -168,11 +168,11 @@ jobs:
SourceFolder: '$(packageLayoutDir)'
TargetFolder: '$(artifactsDir)\DevPackage'

- task: CopyFiles@2
displayName: 'Copy Dev Packages'
- task: DeleteFiles@1
displayName: Clean up Package Layout after copy
inputs:
SourceFolder: '$(appxPackageDir)'
TargetFolder: '$(artifactsDir)\AppxPackages'
SourceFolder: '$(packageLayoutDir)'
Contents: '**/*'

- task: CopyFiles@2
displayName: 'Copy native binaries for Microsoft.WinGet.Client (net8)'
Expand Down
42 changes: 42 additions & 0 deletions schemas/JSON/manifests/v1.10.0/manifest.installer.1.10.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,42 @@
"type": [ "boolean", "null" ],
"description": "Indicates whether the install location should be added directly to the PATH environment variable. Only applies to an archive containing portable packages."
},
"Authentication": {
"type": [ "object", "null" ],
"properties": {
"AuthenticationType": {
"type": "string",
"enum": [
"none",
"microsoftEntraId",
"microsoftEntraIdForAzureBlobStorage"
],
"description": "The authentication type"
},
"MicrosoftEntraIdAuthenticationInfo": {
"type": [ "object", "null" ],
"properties": {
"Resource": {
"type": [ "string", "null" ],
"minLength": 1,
"maxLength": 512,
"description": "The resource value for Microsoft Entra Id authentication."
},
"Scope": {
"type": [ "string", "null" ],
"minLength": 1,
"maxLength": 512,
"description": "The scope value for Microsoft Entra Id authentication."
}
},
"description": "The Microsoft Entra Id authentication info"
}
},
"required": [
"AuthenticationType"
],
"description": "The authentication requirement for downloading the installer."
},
"Installer": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -724,6 +760,9 @@
},
"ArchiveBinariesDependOnPath": {
"$ref": "#/definitions/ArchiveBinariesDependOnPath"
},
"Authentication": {
"$ref": "#/definitions/Authentication"
}
},
"required": [
Expand Down Expand Up @@ -846,6 +885,9 @@
"ArchiveBinariesDependOnPath": {
"$ref": "#/definitions/ArchiveBinariesDependOnPath"
},
"Authentication": {
"$ref": "#/definitions/Authentication"
},
"Installers": {
"type": "array",
"items": {
Expand Down
42 changes: 42 additions & 0 deletions schemas/JSON/manifests/v1.10.0/manifest.singleton.1.10.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,42 @@
"type": [ "boolean", "null" ],
"description": "Indicates whether the install location should be added directly to the PATH environment variable. Only applies to an archive containing portable packages."
},
"Authentication": {
"type": [ "object", "null" ],
"properties": {
"AuthenticationType": {
"type": "string",
"enum": [
"none",
"microsoftEntraId",
"microsoftEntraIdForAzureBlobStorage"
],
"description": "The authentication type"
},
"MicrosoftEntraIdAuthenticationInfo": {
"type": [ "object", "null" ],
"properties": {
"Resource": {
"type": [ "string", "null" ],
"minLength": 1,
"maxLength": 512,
"description": "The resource value for Microsoft Entra Id authentication."
},
"Scope": {
"type": [ "string", "null" ],
"minLength": 1,
"maxLength": 512,
"description": "The scope value for Microsoft Entra Id authentication."
}
},
"description": "The Microsoft Entra Id authentication info"
}
},
"required": [
"AuthenticationType"
],
"description": "The authentication requirement for downloading the installer."
},
"Installer": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -825,6 +861,9 @@
},
"ArchiveBinariesDependOnPath": {
"$ref": "#/definitions/ArchiveBinariesDependOnPath"
},
"Authentication": {
"$ref": "#/definitions/Authentication"
}
},
"required": [
Expand Down Expand Up @@ -1070,6 +1109,9 @@
"ArchiveBinariesDependOnPath": {
"$ref": "#/definitions/ArchiveBinariesDependOnPath"
},
"Authentication": {
"$ref": "#/definitions/Authentication"
},
"Installers": {
"type": "array",
"items": {
Expand Down
4 changes: 2 additions & 2 deletions src/AppInstallerCLICore/Argument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -277,9 +277,9 @@ namespace AppInstaller::CLI

// Authentication arguments
case Execution::Args::Type::AuthenticationMode:
return { type, "authentication-mode"_liv };
return { type, "authentication-mode"_liv, ArgTypeCategory::CopyValueToSubContext };
case Execution::Args::Type::AuthenticationAccount:
return { type, "authentication-account"_liv };
return { type, "authentication-account"_liv, ArgTypeCategory::CopyValueToSubContext };

// Used for demonstration purposes
case Execution::Args::Type::ExperimentalArg:
Expand Down
4 changes: 3 additions & 1 deletion src/AppInstallerCLICore/Commands/COMCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ namespace AppInstaller::CLI
// IMPORTANT: To use this command, the caller should have already retrieved the package manifest (GetManifest()) and added it to the Context Data
void COMDownloadCommand::ExecuteInternal(Context& context) const
{
context <<
context <<
Workflow::InitializeInstallerDownloadAuthenticatorsMap <<
Workflow::ReportExecutionStage(ExecutionStage::Discovery) <<
Workflow::SelectInstaller <<
Workflow::EnsureApplicableInstaller <<
Expand Down Expand Up @@ -51,6 +52,7 @@ namespace AppInstaller::CLI
void COMRepairCommand::ExecuteInternal(Execution::Context& context) const
{
context <<
Workflow::InitializeInstallerDownloadAuthenticatorsMap <<
Workflow::SelectApplicableInstallerIfNecessary <<
Workflow::RepairSinglePackage;
}
Expand Down
4 changes: 3 additions & 1 deletion src/AppInstallerCLICore/Commands/DownloadCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ namespace AppInstaller::CLI

void DownloadCommand::ExecuteInternal(Context& context) const
{
context.SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerDownloadOnly);
context.SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerDownloadOnly);

context << Workflow::InitializeInstallerDownloadAuthenticatorsMap;

if (context.Args.Contains(Execution::Args::Type::Manifest))
{
Expand Down
6 changes: 4 additions & 2 deletions src/AppInstallerCLICore/Commands/ImportCommand.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "pch.h"
#include "ImportCommand.h"
#include "ImportCommand.h"
#include "Workflows/DownloadFlow.h"
#include "Workflows/CompletionFlow.h"
#include "Workflows/ImportExportFlow.h"
#include "Workflows/MultiQueryFlow.h"
Expand Down Expand Up @@ -41,7 +42,8 @@ namespace AppInstaller::CLI

void ImportCommand::ExecuteInternal(Execution::Context& context) const
{
context <<
context <<
Workflow::InitializeInstallerDownloadAuthenticatorsMap <<
Workflow::ReportExecutionStage(Workflow::ExecutionStage::Discovery) <<
Workflow::VerifyFile(Execution::Args::Type::ImportFile) <<
Workflow::ReadImportFile <<
Expand Down
3 changes: 3 additions & 0 deletions src/AppInstallerCLICore/Commands/InstallCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "CheckpointManager.h"
#include "InstallCommand.h"
#include "Workflows/CompletionFlow.h"
#include "Workflows/DownloadFlow.h"
#include "Workflows/InstallFlow.h"
#include "Workflows/UpdateFlow.h"
#include "Workflows/MultiQueryFlow.h"
Expand Down Expand Up @@ -118,6 +119,8 @@ namespace AppInstaller::CLI
{
context.SetFlags(ContextFlag::ShowSearchResultsOnPartialFailure);

context << Workflow::InitializeInstallerDownloadAuthenticatorsMap;

if (context.Args.Contains(Execution::Args::Type::Manifest))
{
context <<
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/Commands/RepairCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "RepairCommand.h"
#include "Workflows/RepairFlow.h"
#include "Workflows/CompletionFlow.h"
#include "Workflows/DownloadFlow.h"
#include "Workflows/InstallFlow.h"

namespace AppInstaller::CLI
Expand Down Expand Up @@ -94,6 +95,7 @@ namespace AppInstaller::CLI
context.SetFlags(Execution::ContextFlag::InstallerExecutionUseRepair);

context <<
Workflow::InitializeInstallerDownloadAuthenticatorsMap <<
Workflow::ReportExecutionStage(ExecutionStage::Discovery) <<
Workflow::OpenSource() <<
Workflow::OpenCompositeSource(DetermineInstalledSource(context));
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/Commands/UpgradeCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "pch.h"
#include "UpgradeCommand.h"
#include "Workflows/CompletionFlow.h"
#include "Workflows/DownloadFlow.h"
#include "Workflows/InstallFlow.h"
#include "Workflows/MultiQueryFlow.h"
#include "Workflows/UpdateFlow.h"
Expand Down Expand Up @@ -157,6 +158,7 @@ namespace AppInstaller::CLI
}

context <<
Workflow::InitializeInstallerDownloadAuthenticatorsMap <<
Workflow::ReportExecutionStage(ExecutionStage::Discovery) <<
Workflow::OpenSource() <<
Workflow::OpenCompositeSource(Workflow::DetermineInstalledSource(context));
Expand Down
12 changes: 12 additions & 0 deletions src/AppInstallerCLICore/ExecutionContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ namespace AppInstaller::CLI::Execution
clone->EnableSignalTerminationHandler();
}
CopyArgsToSubContext(clone.get());
CopyDataToSubContext(clone.get());
return clone;
}

Expand All @@ -342,6 +343,17 @@ namespace AppInstaller::CLI::Execution
}
}

void Context::CopyDataToSubContext(Context* subContext)
{
#define COPY_DATA_IF_EXISTS(dataType) \
if (this->Contains(dataType)) \
{ \
subContext->Add<dataType>(this->Get<dataType>()); \
}

COPY_DATA_IF_EXISTS(Data::InstallerDownloadAuthenticators);
}

void Context::EnableSignalTerminationHandler(bool enabled)
{
SetSignalTerminationHandlerContext(enabled, this);
Expand Down
3 changes: 3 additions & 0 deletions src/AppInstallerCLICore/ExecutionContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ namespace AppInstaller::CLI::Execution
// Copies the args that are also needed in a sub-context. E.g., silent
void CopyArgsToSubContext(Context* subContext);

// Copies the execution data that are also needed in a sub-context. E.g., shared installer download authenticator map
void CopyDataToSubContext(Context* subContext);

// Neither virtual functions nor member fields can be inside AICLI_DISABLE_TEST_HOOKS
// or we could have ODR violations that lead to nasty bugs. So we will simply never
// use this if AICLI_DISABLE_TEST_HOOKS is defined.
Expand Down
9 changes: 9 additions & 0 deletions src/AppInstallerCLICore/ExecutionContextData.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <winget/RepositorySource.h>
#include <winget/Manifest.h>
#include <winget/ARPCorrelation.h>
#include <winget/Authentication.h>
#include <winget/Pin.h>
#include <winget/PinningData.h>
#include "CompletionData.h"
Expand Down Expand Up @@ -67,6 +68,7 @@ namespace AppInstaller::CLI::Execution
ModifyPath,
RepairString,
MsixDigests,
InstallerDownloadAuthenticators,
Max
};

Expand Down Expand Up @@ -290,5 +292,12 @@ namespace AppInstaller::CLI::Execution
// The pair is { URL, Digest }
using value_t = std::vector<std::pair<std::string, std::wstring>>;
};

template<>
struct DataMapping<Data::InstallerDownloadAuthenticators>
{
// The authenticator map shared with sub contexts
using value_t = std::shared_ptr<std::map<Authentication::AuthenticationInfo, Authentication::Authenticator>>;
};
}
}
3 changes: 3 additions & 0 deletions src/AppInstallerCLICore/Resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,11 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(InstalledScopeArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerAbortsTerminal);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerBlockedByPolicy);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadAuthenticationFailed);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadAuthenticationNotSupported);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadCommandProhibited);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloaded);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloadRequiresAuthentication);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerDownloads);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerElevationExpected);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedSecurityCheck);
Expand Down
Loading

0 comments on commit b49f784

Please sign in to comment.