winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

SourceList.cpp (36250B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "SourceList.h"
      5 #include "SourcePolicy.h"
      6 #include "Microsoft/PreIndexedPackageSourceFactory.h"
      7 #include "Rest/RestSourceFactory.h"
      8 
      9 #include <winget/AdminSettings.h>
     10 #include <winget/Certificates.h>
     11 #include <CertificateResources.h>
     12 
     13 using namespace AppInstaller::Settings;
     14 using namespace std::string_view_literals;
     15 
     16 namespace AppInstaller::Repository
     17 {
     18     namespace
     19     {
     20         constexpr std::string_view s_SourcesYaml_Sources = "Sources"sv;
     21         constexpr std::string_view s_SourcesYaml_Source_Name = "Name"sv;
     22         constexpr std::string_view s_SourcesYaml_Source_Type = "Type"sv;
     23         constexpr std::string_view s_SourcesYaml_Source_Arg = "Arg"sv;
     24         constexpr std::string_view s_SourcesYaml_Source_Data = "Data"sv;
     25         constexpr std::string_view s_SourcesYaml_Source_Identifier = "Identifier"sv;
     26         constexpr std::string_view s_SourcesYaml_Source_IsTombstone = "IsTombstone"sv;
     27         constexpr std::string_view s_SourcesYaml_Source_Explicit = "Explicit"sv;
     28         constexpr std::string_view s_SourcesYaml_Source_TrustLevel = "TrustLevel"sv;
     29 
     30         constexpr std::string_view s_MetadataYaml_Sources = "Sources"sv;
     31         constexpr std::string_view s_MetadataYaml_Source_Name = "Name"sv;
     32         constexpr std::string_view s_MetadataYaml_Source_LastUpdate = "LastUpdate"sv;
     33         constexpr std::string_view s_MetadataYaml_Source_DoNotUpdateBefore = "DoNotUpdateBefore"sv;
     34         constexpr std::string_view s_MetadataYaml_Source_AcceptedAgreementsIdentifier = "AcceptedAgreementsIdentifier"sv;
     35         constexpr std::string_view s_MetadataYaml_Source_AcceptedAgreementFields = "AcceptedAgreementFields"sv;
     36 
     37         constexpr std::string_view s_Source_WingetCommunityDefault_Name = "winget"sv;
     38         constexpr std::string_view s_Source_WingetCommunityDefault_Arg = "https://cdn.winget.microsoft.com/cache"sv;
     39         constexpr std::string_view s_Source_WingetCommunityDefault_Data = "Microsoft.Winget.Source_8wekyb3d8bbwe"sv;
     40         constexpr std::string_view s_Source_WingetCommunityDefault_Identifier = "Microsoft.Winget.Source_8wekyb3d8bbwe"sv;
     41 
     42         constexpr std::string_view s_Source_MSStoreDefault_Name = "msstore"sv;
     43         constexpr std::string_view s_Source_MSStoreDefault_Arg = "https://storeedgefd.dsx.mp.microsoft.com/v9.0"sv;
     44         constexpr std::string_view s_Source_MSStoreDefault_Identifier = "StoreEdgeFD"sv;
     45 
     46         constexpr std::string_view s_Source_DesktopFrameworks_Name = "microsoft.builtin.desktop.frameworks"sv;
     47         constexpr std::string_view s_Source_DesktopFrameworks_Arg = "https://cdn.winget.microsoft.com/platform"sv;
     48         constexpr std::string_view s_Source_DesktopFrameworks_Data = "Microsoft.Winget.Platform.Source_8wekyb3d8bbwe"sv;
     49         constexpr std::string_view s_Source_DesktopFrameworks_Identifier = "Microsoft.Winget.Platform.Source_8wekyb3d8bbwe"sv;
     50 
     51         // Attempts to read a single scalar value from the node.
     52         template<typename Value>
     53         bool TryReadScalar(std::string_view settingName, const std::string& settingValue, const YAML::Node& sourceNode, std::string_view name, Value& value, bool required = true)
     54         {
     55             YAML::Node valueNode = sourceNode[std::string{ name }];
     56 
     57             if (!valueNode || !valueNode.IsScalar())
     58             {
     59                 if (required)
     60                 {
     61                     AICLI_LOG(Repo, Error, << "Setting '" << settingName << "' did not contain the expected format (" << name << " is invalid within a source):\n" << settingValue);
     62                 }
     63                 return false;
     64             }
     65 
     66             value = valueNode.as<Value>();
     67             return true;
     68         }
     69 
     70         // Attempts to read the source details from the given stream.
     71         // Results are all or nothing; if any failures occur, no details are returned.
     72         bool TryReadSourceDetails(
     73             std::string_view settingName,
     74             std::istream& stream,
     75             std::string_view rootName,
     76             std::function<bool(SourceDetailsInternal&, const std::string&, const YAML::Node&)> parse,
     77             std::vector<SourceDetailsInternal>& sourceDetails)
     78         {
     79             std::vector<SourceDetailsInternal> result;
     80             std::string settingValue = Utility::ReadEntireStream(stream);
     81 
     82             YAML::Node document;
     83             try
     84             {
     85                 document = YAML::Load(settingValue);
     86             }
     87             catch (const std::exception& e)
     88             {
     89                 AICLI_LOG(YAML, Error, << "Setting '" << settingName << "' contained invalid YAML (" << e.what() << "):\n" << settingValue);
     90                 return false;
     91             }
     92 
     93             try
     94             {
     95                 YAML::Node sources = document[rootName];
     96                 if (!sources)
     97                 {
     98                     AICLI_LOG(Repo, Error, << "Setting '" << settingName << "' did not contain the expected format (missing " << rootName << "):\n" << settingValue);
     99                     return false;
    100                 }
    101 
    102                 if (sources.IsNull())
    103                 {
    104                     // An empty sources is an acceptable thing.
    105                     return true;
    106                 }
    107 
    108                 if (!sources.IsSequence())
    109                 {
    110                     AICLI_LOG(Repo, Error, << "Setting '" << settingName << "' did not contain the expected format (" << rootName << " was not a sequence):\n" << settingValue);
    111                     return false;
    112                 }
    113 
    114                 for (const auto& source : sources.Sequence())
    115                 {
    116                     SourceDetailsInternal details;
    117                     if (!parse(details, settingValue, source))
    118                     {
    119                         return false;
    120                     }
    121 
    122                     result.emplace_back(std::move(details));
    123                 }
    124             }
    125             catch (const std::exception& e)
    126             {
    127                 AICLI_LOG(YAML, Error, << "Setting '" << settingName << "' contained unexpected YAML (" << e.what() << "):\n" << settingValue);
    128                 return false;
    129             }
    130 
    131             sourceDetails = std::move(result);
    132             return true;
    133         }
    134 
    135         // Gets the source details from a particular setting, or an empty optional if no setting exists.
    136         std::optional<std::vector<SourceDetailsInternal>> TryGetSourcesFromSetting(
    137             Settings::Stream& setting,
    138             std::string_view rootName,
    139             std::function<bool(SourceDetailsInternal&, const std::string&, const YAML::Node&)> parse)
    140         {
    141             auto sourcesStream = setting.Get();
    142             if (!sourcesStream)
    143             {
    144                 // Note that this case is different than the one in which all sources have been removed.
    145                 return {};
    146             }
    147             else
    148             {
    149                 std::vector<SourceDetailsInternal> result;
    150                 if (!TryReadSourceDetails(setting.GetName(), *sourcesStream, rootName, parse, result))
    151                 {
    152                     AICLI_LOG(YAML, Error, << "Ignoring corrupted source data.");
    153                 }
    154                 return result;
    155             }
    156         }
    157 
    158         // Gets the source details from a particular setting.
    159         std::vector<SourceDetailsInternal> GetSourcesFromSetting(
    160             Settings::Stream& setting,
    161             std::string_view rootName,
    162             std::function<bool(SourceDetailsInternal&, const std::string&, const YAML::Node&)> parse)
    163         {
    164             return TryGetSourcesFromSetting(setting, rootName, parse).value_or(std::vector<SourceDetailsInternal>{});
    165         }
    166 
    167         // Sets the sources for a particular setting, from a particular origin.
    168         [[nodiscard]] bool SetSourcesToSettingWithFilter(Settings::Stream& setting, SourceOrigin origin, const std::vector<SourceDetailsInternal>& sources)
    169         {
    170             YAML::Emitter out;
    171             out << YAML::BeginMap;
    172             out << YAML::Key << s_SourcesYaml_Sources;
    173             out << YAML::BeginSeq;
    174 
    175             for (const auto& details : sources)
    176             {
    177                 if (details.Origin == origin)
    178                 {
    179                     out << YAML::BeginMap;
    180                     out << YAML::Key << s_SourcesYaml_Source_Name << YAML::Value << details.Name;
    181                     out << YAML::Key << s_SourcesYaml_Source_Type << YAML::Value << details.Type;
    182                     out << YAML::Key << s_SourcesYaml_Source_Arg << YAML::Value << details.Arg;
    183                     out << YAML::Key << s_SourcesYaml_Source_Data << YAML::Value << details.Data;
    184                     out << YAML::Key << s_SourcesYaml_Source_Identifier << YAML::Value << details.Identifier;
    185                     out << YAML::Key << s_SourcesYaml_Source_IsTombstone << YAML::Value << details.IsTombstone;
    186                     out << YAML::Key << s_SourcesYaml_Source_Explicit << YAML::Value << details.Explicit;
    187                     out << YAML::Key << s_SourcesYaml_Source_TrustLevel << YAML::Value << static_cast<int64_t>(details.TrustLevel);
    188                     out << YAML::EndMap;
    189                 }
    190             }
    191 
    192             out << YAML::EndSeq;
    193             out << YAML::EndMap;
    194 
    195             return setting.Set(out.str());
    196         }
    197 
    198         // Assumes that names match already
    199         bool DoSourceDetailsInternalMatch(const SourceDetailsInternal& left, const SourceDetailsInternal& right)
    200         {
    201             return left.Arg == right.Arg &&
    202                 left.Identifier == right.Identifier &&
    203                 Utility::CaseInsensitiveEquals(left.Type, right.Type);
    204         }
    205 
    206         bool ShouldBeHidden(const SourceDetailsInternal& details)
    207         {
    208             return details.IsTombstone || details.Origin == SourceOrigin::Metadata || !details.IsVisible;
    209         }
    210     }
    211 
    212     void SourceDetailsInternal::CopyMetadataFieldsTo(SourceDetailsInternal& target)
    213     {
    214         if (LastUpdateTime > target.LastUpdateTime)
    215         {
    216             target.LastUpdateTime = LastUpdateTime;
    217         }
    218 
    219         if (DoNotUpdateBefore > target.DoNotUpdateBefore)
    220         {
    221             target.DoNotUpdateBefore = DoNotUpdateBefore;
    222         }
    223 
    224         target.AcceptedAgreementFields = AcceptedAgreementFields;
    225         target.AcceptedAgreementsIdentifier = AcceptedAgreementsIdentifier;
    226     }
    227 
    228     void SourceDetailsInternal::CopyMetadataFieldsFrom(const SourceDetails& source)
    229     {
    230         LastUpdateTime = source.LastUpdateTime;
    231         DoNotUpdateBefore = source.DoNotUpdateBefore;
    232     }
    233 
    234     std::string_view GetWellKnownSourceName(WellKnownSource source)
    235     {
    236         switch (source)
    237         {
    238         case WellKnownSource::WinGet:
    239             return s_Source_WingetCommunityDefault_Name;
    240         case WellKnownSource::MicrosoftStore:
    241             return s_Source_MSStoreDefault_Name;
    242         case WellKnownSource::DesktopFrameworks:
    243             return s_Source_DesktopFrameworks_Name;
    244         }
    245 
    246         return {};
    247     }
    248 
    249     std::string_view GetWellKnownSourceArg(WellKnownSource source)
    250     {
    251         switch (source)
    252         {
    253         case WellKnownSource::WinGet:
    254             return s_Source_WingetCommunityDefault_Arg;
    255         case WellKnownSource::MicrosoftStore:
    256             return s_Source_MSStoreDefault_Arg;
    257         case WellKnownSource::DesktopFrameworks:
    258             return s_Source_DesktopFrameworks_Arg;
    259         }
    260 
    261         return {};
    262     }
    263 
    264     std::string_view GetWellKnownSourceIdentifier(WellKnownSource source)
    265     {
    266         switch (source)
    267         {
    268         case WellKnownSource::WinGet:
    269             return s_Source_WingetCommunityDefault_Identifier;
    270         case WellKnownSource::MicrosoftStore:
    271             return s_Source_MSStoreDefault_Identifier;
    272         case WellKnownSource::DesktopFrameworks:
    273             return s_Source_DesktopFrameworks_Identifier;
    274         }
    275 
    276         return {};
    277     }
    278 
    279     std::optional<WellKnownSource> CheckForWellKnownSourceMatch(std::string_view name, std::string_view arg, std::string_view type)
    280     {
    281         if (name == s_Source_WingetCommunityDefault_Name && arg == s_Source_WingetCommunityDefault_Arg && type == Microsoft::PreIndexedPackageSourceFactory::Type())
    282         {
    283             return WellKnownSource::WinGet;
    284         }
    285 
    286         if (name == s_Source_MSStoreDefault_Name && arg == s_Source_MSStoreDefault_Arg && type == Rest::RestSourceFactory::Type())
    287         {
    288             return WellKnownSource::MicrosoftStore;
    289         }
    290 
    291         if (name == s_Source_DesktopFrameworks_Name && arg == s_Source_DesktopFrameworks_Arg && type == Microsoft::PreIndexedPackageSourceFactory::Type())
    292         {
    293             return WellKnownSource::DesktopFrameworks;
    294         }
    295 
    296         return {};
    297     }
    298 
    299     SourceDetailsInternal GetWellKnownSourceDetailsInternal(WellKnownSource source)
    300     {
    301         switch (source)
    302         {
    303         case WellKnownSource::WinGet:
    304         {
    305             SourceDetailsInternal details;
    306             details.Origin = SourceOrigin::Default;
    307             details.Name = s_Source_WingetCommunityDefault_Name;
    308             details.Type = Microsoft::PreIndexedPackageSourceFactory::Type();
    309             details.Arg = s_Source_WingetCommunityDefault_Arg;
    310             details.Data = s_Source_WingetCommunityDefault_Data;
    311             details.Identifier = s_Source_WingetCommunityDefault_Identifier;
    312             details.TrustLevel = SourceTrustLevel::Trusted | SourceTrustLevel::StoreOrigin;
    313             return details;
    314         }
    315         case WellKnownSource::MicrosoftStore:
    316         {
    317             SourceDetailsInternal details;
    318             details.Origin = SourceOrigin::Default;
    319             details.Name = s_Source_MSStoreDefault_Name;
    320             details.Type = Rest::RestSourceFactory::Type();
    321             details.Arg = s_Source_MSStoreDefault_Arg;
    322             details.Identifier = s_Source_MSStoreDefault_Identifier;
    323             details.TrustLevel = SourceTrustLevel::Trusted;
    324             details.SupportInstalledSearchCorrelation = false;
    325 
    326             if (!Settings::IsAdminSettingEnabled(Settings::BoolAdminSetting::BypassCertificatePinningForMicrosoftStore))
    327             {
    328                 using namespace AppInstaller::Certificates;
    329 
    330                 PinningChain chain;
    331                 auto chainElement = chain.Root();
    332                 chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey);
    333                 chainElement = chainElement.Next();
    334                 chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
    335                 chainElement = chainElement.Next();
    336                 chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
    337 
    338                 PinningChain chain2;
    339                 auto chainElement2 = chain2.Root();
    340                 chainElement2->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey);
    341                 chainElement2 = chainElement2.Next();
    342                 chainElement2->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
    343                 chainElement2 = chainElement2.Next();
    344                 chainElement2->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
    345 
    346                 // See https://aka.ms/AzureTLSCAs (internal) for the source of these CAs
    347                 PinningChain chain3;
    348                 chain3.PartialChain().Root()->
    349                     LoadCertificate(IDX_CERTIFICATE_MS_TLS_ECC_ROOT_G2, CERTIFICATE_RESOURCE_TYPE).
    350                     SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf);
    351 
    352                 PinningChain chain4;
    353                 chain4.PartialChain().Root()->
    354                     LoadCertificate(IDX_CERTIFICATE_MS_TLS_RSA_ROOT_G2, CERTIFICATE_RESOURCE_TYPE).
    355                     SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf);
    356 
    357                 details.CertificatePinningConfiguration = PinningConfiguration("Microsoft Store Source");
    358                 details.CertificatePinningConfiguration.AddChain(std::move(chain));
    359                 details.CertificatePinningConfiguration.AddChain(std::move(chain2));
    360                 details.CertificatePinningConfiguration.AddChain(std::move(chain3));
    361                 details.CertificatePinningConfiguration.AddChain(std::move(chain4));
    362             }
    363 
    364             return details;
    365         }
    366         case WellKnownSource::DesktopFrameworks:
    367         {
    368             SourceDetailsInternal details;
    369             details.Origin = SourceOrigin::Default;
    370             details.Name = s_Source_DesktopFrameworks_Name;
    371             details.Type = Microsoft::PreIndexedPackageSourceFactory::Type();
    372             details.Arg = s_Source_DesktopFrameworks_Arg;
    373             details.Data = s_Source_DesktopFrameworks_Data;
    374             details.Identifier = s_Source_DesktopFrameworks_Identifier;
    375             details.TrustLevel = SourceTrustLevel::Trusted | SourceTrustLevel::StoreOrigin;
    376             details.IsVisible = false;
    377             return details;
    378         }
    379         }
    380 
    381         THROW_HR(E_UNEXPECTED);
    382     }
    383 
    384     SourceList::SourceList() : m_userSourcesStream(Stream::UserSources), m_metadataStream(Stream::SourcesMetadata)
    385     {
    386         OverwriteSourceList();
    387         OverwriteMetadata();
    388     }
    389 
    390     std::vector<std::reference_wrapper<SourceDetailsInternal>> SourceList::GetCurrentSourceRefs()
    391     {
    392         std::vector<std::reference_wrapper<SourceDetailsInternal>> result;
    393 
    394         for (auto& s : m_sourceList)
    395         {
    396             if (!ShouldBeHidden(s))
    397             {
    398                 result.emplace_back(std::ref(s));
    399             }
    400             else
    401             {
    402                 AICLI_LOG(Repo, Verbose, << "GetCurrentSourceRefs: Source named '" << s.Name << "' from origin " << ToString(s.Origin) << " is hidden and is dropped.");
    403             }
    404         }
    405 
    406         return result;
    407     }
    408 
    409     auto SourceList::FindSource(std::string_view name, bool includeHidden)
    410     {
    411         return std::find_if(m_sourceList.begin(), m_sourceList.end(),
    412             [name, includeHidden](const SourceDetailsInternal& sd)
    413             {
    414                 return Utility::ICUCaseInsensitiveEquals(sd.Name, name) &&
    415                     (includeHidden || !ShouldBeHidden(sd));
    416             });
    417     }
    418 
    419     SourceDetailsInternal* SourceList::GetCurrentSource(std::string_view name)
    420     {
    421         auto itr = FindSource(name);
    422         return itr == m_sourceList.end() ? nullptr : &(*itr);
    423     }
    424 
    425     SourceDetailsInternal* SourceList::GetSource(std::string_view name)
    426     {
    427         auto itr = FindSource(name, true);
    428         return itr == m_sourceList.end() ? nullptr : &(*itr);
    429     }
    430 
    431     void SourceList::AddSource(const SourceDetailsInternal& details)
    432     {
    433         bool sourcesSet = false;
    434 
    435         for (size_t i = 0; !sourcesSet && i < 10; ++i)
    436         {
    437             auto itr = FindSource(details.Name, true);
    438             THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS,
    439                 itr != m_sourceList.end() && itr->Origin != SourceOrigin::Metadata && !itr->IsTombstone);
    440 
    441             // Erase the source's entry if applicable
    442             if (itr != m_sourceList.end())
    443             {
    444                 m_sourceList.erase(itr);
    445             }
    446 
    447             m_sourceList.emplace_back(details);
    448 
    449             sourcesSet = SetSourcesByOrigin(SourceOrigin::User, m_sourceList);
    450 
    451             if (!sourcesSet)
    452             {
    453                 OverwriteSourceList();
    454                 OverwriteMetadata();
    455             }
    456         }
    457 
    458         THROW_HR_IF_MSG(E_UNEXPECTED, !sourcesSet, "Too many attempts at SetSourcesByOrigin");
    459 
    460         SaveMetadataInternal(details);
    461     }
    462 
    463     void SourceList::RemoveSource(const SourceDetailsInternal& detailsRef)
    464     {
    465         // Copy the incoming details because we might destroy the referenced structure
    466         // when reloading the source details from settings.
    467         SourceDetailsInternal details = detailsRef;
    468         bool sourcesSet = false;
    469 
    470         for (size_t i = 0; !sourcesSet && i < 10; ++i)
    471         {
    472             switch (details.Origin)
    473             {
    474             case SourceOrigin::Default:
    475             {
    476                 auto target = FindSource(details.Name, true);
    477                 if (target == m_sourceList.end())
    478                 {
    479                     THROW_HR_MSG(E_UNEXPECTED, "Default source not in SourceList");
    480                 }
    481 
    482                 if (!target->IsTombstone)
    483                 {
    484                     SourceDetailsInternal tombstone;
    485                     tombstone.Name = details.Name;
    486                     tombstone.IsTombstone = true;
    487                     tombstone.Origin = SourceOrigin::User;
    488                     m_sourceList.emplace_back(std::move(tombstone));
    489                 }
    490             }
    491                 break;
    492             case SourceOrigin::User:
    493             {
    494                 auto target = FindSource(details.Name);
    495                 if (target == m_sourceList.end())
    496                 {
    497                     // Assumed that an update to the sources removed it first
    498                     return;
    499                 }
    500 
    501                 m_sourceList.erase(target);
    502             }
    503                 break;
    504             case SourceOrigin::GroupPolicy:
    505                 // This should have already been blocked higher up.
    506                 AICLI_LOG(Repo, Error, << "Attempting to remove Group Policy source: " << details.Name);
    507                 THROW_HR(E_UNEXPECTED);
    508             default:
    509                 THROW_HR(E_UNEXPECTED);
    510             }
    511 
    512             sourcesSet = SetSourcesByOrigin(SourceOrigin::User, m_sourceList);
    513 
    514             if (!sourcesSet)
    515             {
    516                 OverwriteSourceList();
    517                 OverwriteMetadata();
    518             }
    519         }
    520 
    521         THROW_HR_IF_MSG(E_UNEXPECTED, !sourcesSet, "Too many attempts at SetSourcesByOrigin");
    522 
    523         SaveMetadataInternal(details, true);
    524     }
    525 
    526     void SourceList::SaveMetadata(const SourceDetailsInternal& details)
    527     {
    528         SaveMetadataInternal(details);
    529     }
    530 
    531     bool SourceList::CheckSourceAgreements(std::string_view sourceName, std::string_view agreementsIdentifier, ImplicitAgreementFieldEnum agreementFields)
    532     {
    533         if (agreementFields == ImplicitAgreementFieldEnum::None && agreementsIdentifier.empty())
    534         {
    535             // No agreements to be accepted.
    536             return true;
    537         }
    538 
    539         auto detailsInternal = GetCurrentSource(sourceName);
    540         if (!detailsInternal)
    541         {
    542             // Source not found.
    543             return false;
    544         }
    545 
    546         return static_cast<int>(agreementFields) == detailsInternal->AcceptedAgreementFields &&
    547             agreementsIdentifier == detailsInternal->AcceptedAgreementsIdentifier;
    548     }
    549 
    550     void SourceList::SaveAcceptedSourceAgreements(std::string_view sourceName, std::string_view agreementsIdentifier, ImplicitAgreementFieldEnum agreementFields)
    551     {
    552         if (agreementFields == ImplicitAgreementFieldEnum::None && agreementsIdentifier.empty())
    553         {
    554             // No agreements to be accepted.
    555             return;
    556         }
    557 
    558         auto detailsInternal = GetCurrentSource(sourceName);
    559         if (!detailsInternal)
    560         {
    561             // No source to update.
    562             return;
    563         }
    564 
    565         detailsInternal->AcceptedAgreementFields = static_cast<int>(agreementFields);
    566         detailsInternal->AcceptedAgreementsIdentifier = agreementsIdentifier;
    567 
    568         SaveMetadataInternal(*detailsInternal);
    569     }
    570 
    571     void SourceList::RemoveSettingsStreams()
    572     {
    573         Stream{ Stream::UserSources }.Remove();
    574         Stream{ Stream::SourcesMetadata }.Remove();
    575     }
    576 
    577     void SourceList::OverwriteSourceList()
    578     {
    579         m_sourceList.clear();
    580 
    581         for (SourceOrigin origin : { SourceOrigin::GroupPolicy, SourceOrigin::User, SourceOrigin::Default })
    582         {
    583             auto forOrigin = GetSourcesByOrigin(origin);
    584 
    585             for (auto&& source : forOrigin)
    586             {
    587                 auto foundSource = GetSource(source.Name);
    588                 if (!foundSource)
    589                 {
    590                     // Name not already defined, add it
    591                     m_sourceList.emplace_back(std::move(source));
    592                 }
    593                 else
    594                 {
    595                     AICLI_LOG(Repo, Info, << "Source named '" << foundSource->Name << "' is already defined at origin " << ToString(foundSource->Origin) <<
    596                         ". The source from origin " << ToString(origin) << " is dropped.");
    597                 }
    598             }
    599         }
    600     }
    601 
    602     void SourceList::OverwriteMetadata()
    603     {
    604         auto metadata = GetMetadata();
    605         for (auto& metaSource : metadata)
    606         {
    607             auto source = GetSource(metaSource.Name);
    608             if (source)
    609             {
    610                 metaSource.CopyMetadataFieldsTo(*source);
    611             }
    612             else
    613             {
    614                 m_sourceList.emplace_back(std::move(metaSource));
    615             }
    616         }
    617     }
    618 
    619     // Gets the sources from a particular origin.
    620     std::vector<SourceDetailsInternal> SourceList::GetSourcesByOrigin(SourceOrigin origin)
    621     {
    622         std::vector<SourceDetailsInternal> result;
    623 
    624         switch (origin)
    625         {
    626         case SourceOrigin::Default:
    627         {
    628             if (IsWellKnownSourceEnabled(WellKnownSource::MicrosoftStore))
    629             {
    630                 result.emplace_back(GetWellKnownSourceDetailsInternal(WellKnownSource::MicrosoftStore));
    631             }
    632 
    633             if (IsWellKnownSourceEnabled(WellKnownSource::WinGet))
    634             {
    635                 result.emplace_back(GetWellKnownSourceDetailsInternal(WellKnownSource::WinGet));
    636             }
    637 
    638             // Since the source is not visible outside, this is added just to have the source in the internal
    639             // list for tracking updates.  Thus there is no need to check a policy.
    640             result.emplace_back(GetWellKnownSourceDetailsInternal(WellKnownSource::DesktopFrameworks));
    641         }
    642         break;
    643         case SourceOrigin::User:
    644         {
    645             std::vector<SourceDetailsInternal> userSources = GetSourcesFromSetting(
    646                 m_userSourcesStream,
    647                 s_SourcesYaml_Sources,
    648                 [&](SourceDetailsInternal& details, const std::string& settingValue, const YAML::Node& source)
    649                 {
    650                     std::string_view name = m_userSourcesStream.GetName();
    651                     if (!TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Name, details.Name)) { return false; }
    652                     if (!TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Type, details.Type)) { return false; }
    653                     if (!TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Arg, details.Arg)) { return false; }
    654                     if (!TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Data, details.Data)) { return false; }
    655                     if (!TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_IsTombstone, details.IsTombstone)) { return false; }
    656                     TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Explicit, details.Explicit, false);
    657                     TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Identifier, details.Identifier, false);
    658 
    659                     int64_t trustLevelValue;
    660                     if (TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_TrustLevel, trustLevelValue, false))
    661                     {
    662                         details.TrustLevel = static_cast<Repository::SourceTrustLevel>(trustLevelValue);
    663                     }
    664 
    665                     return true;
    666                 });
    667 
    668             for (auto& source : userSources)
    669             {
    670                 // Check source against list of allowed sources and drop tombstones for required sources
    671                 if (!IsUserSourceAllowedByPolicy(source.Name, source.Type, source.Arg, source.IsTombstone))
    672                 {
    673                     AICLI_LOG(Repo, Warning, << "User source " << source.Name << " dropped because of group policy");
    674                     continue;
    675                 }
    676 
    677                 result.emplace_back(std::move(source));
    678             }
    679         }
    680         break;
    681         case SourceOrigin::GroupPolicy:
    682         {
    683             if (GroupPolicies().GetState(TogglePolicy::Policy::AdditionalSources) == PolicyState::Enabled)
    684             {
    685                 AICLI_LOG(Repo, Verbose, << "Additional sources GP is enabled...");
    686                 auto additionalSourcesOpt = GroupPolicies().GetValueRef<ValuePolicy::AdditionalSources>();
    687                 if (additionalSourcesOpt.has_value())
    688                 {
    689                     const auto& additionalSources = additionalSourcesOpt->get();
    690                     for (const auto& additionalSource : additionalSources)
    691                     {
    692                         AICLI_LOG(Repo, Verbose, << "... with configured source " << additionalSource.Name);
    693                         SourceDetailsInternal details;
    694                         details.Name = additionalSource.Name;
    695                         details.Type = additionalSource.Type;
    696                         details.Arg = additionalSource.Arg;
    697                         details.Data = additionalSource.Data;
    698                         details.Identifier = additionalSource.Identifier;
    699                         details.Origin = SourceOrigin::GroupPolicy;
    700                         details.Explicit = additionalSource.Explicit;
    701 #ifndef AICLI_DISABLE_TEST_HOOKS
    702                         details.CertificatePinningConfiguration = additionalSource.PinningConfiguration;
    703 #endif
    704                         try
    705                         {
    706                             details.TrustLevel = Repository::ConvertToSourceTrustLevelFlag(additionalSource.TrustLevel);
    707                         }
    708                         catch (...)
    709                         {
    710                             details.TrustLevel = Repository::SourceTrustLevel::None;
    711                             AICLI_LOG(Repo, Verbose, << "Invalid source trust level from policy. Trust level set to None.");
    712                         }
    713 
    714                         result.emplace_back(std::move(details));
    715                     }
    716                 }
    717                 else
    718                 {
    719                     AICLI_LOG(Repo, Verbose, << "... but has no values.");
    720                 }
    721             }
    722             else
    723             {
    724                 AICLI_LOG(Repo, Verbose, << "Additional sources GP is not enabled.");
    725             }
    726         }
    727         break;
    728         default:
    729             THROW_HR(E_UNEXPECTED);
    730         }
    731 
    732         for (auto& source : result)
    733         {
    734             source.Origin = origin;
    735         }
    736 
    737         return result;
    738     }
    739 
    740     bool SourceList::SetSourcesByOrigin(SourceOrigin origin, const std::vector<SourceDetailsInternal>& sources)
    741     {
    742         switch (origin)
    743         {
    744         case SourceOrigin::User:
    745             return SetSourcesToSettingWithFilter(m_userSourcesStream, SourceOrigin::User, sources);
    746         }
    747 
    748         THROW_HR(E_UNEXPECTED);
    749     }
    750 
    751     std::vector<SourceDetailsInternal> SourceList::GetMetadata()
    752     {
    753         return GetSourcesFromSetting(
    754             m_metadataStream,
    755             s_MetadataYaml_Sources,
    756             [&](SourceDetailsInternal& details, const std::string& settingValue, const YAML::Node& source)
    757             {
    758                 details.Origin = SourceOrigin::Metadata;
    759                 std::string_view name = m_metadataStream.GetName();
    760                 if (!TryReadScalar(name, settingValue, source, s_MetadataYaml_Source_Name, details.Name)) { return false; }
    761 
    762                 int64_t lastUpdateInEpoch{};
    763                 if (!TryReadScalar(name, settingValue, source, s_MetadataYaml_Source_LastUpdate, lastUpdateInEpoch)) { return false; }
    764                 details.LastUpdateTime = Utility::ConvertUnixEpochToSystemClock(lastUpdateInEpoch);
    765 
    766                 int64_t doNotUpdateBeforeInEpoch{};
    767                 if (TryReadScalar(name, settingValue, source, s_MetadataYaml_Source_DoNotUpdateBefore, doNotUpdateBeforeInEpoch, false))
    768                 {
    769                     details.DoNotUpdateBefore = Utility::ConvertUnixEpochToSystemClock(doNotUpdateBeforeInEpoch);
    770                 }
    771 
    772                 TryReadScalar(name, settingValue, source, s_MetadataYaml_Source_AcceptedAgreementsIdentifier, details.AcceptedAgreementsIdentifier, false);
    773                 TryReadScalar(name, settingValue, source, s_MetadataYaml_Source_AcceptedAgreementFields, details.AcceptedAgreementFields, false);
    774                 return true;
    775             });
    776     }
    777 
    778     bool SourceList::SetMetadata(const std::vector<SourceDetailsInternal>& sources)
    779     {
    780         YAML::Emitter out;
    781         out << YAML::BeginMap;
    782         out << YAML::Key << s_MetadataYaml_Sources;
    783         out << YAML::BeginSeq;
    784 
    785         for (const auto& details : sources)
    786         {
    787             out << YAML::BeginMap;
    788             out << YAML::Key << s_MetadataYaml_Source_Name << YAML::Value << details.Name;
    789             out << YAML::Key << s_MetadataYaml_Source_LastUpdate << YAML::Value << Utility::ConvertSystemClockToUnixEpoch(details.LastUpdateTime);
    790             out << YAML::Key << s_MetadataYaml_Source_DoNotUpdateBefore << YAML::Value << Utility::ConvertSystemClockToUnixEpoch(details.DoNotUpdateBefore);
    791             out << YAML::Key << s_MetadataYaml_Source_AcceptedAgreementsIdentifier << YAML::Value << details.AcceptedAgreementsIdentifier;
    792             out << YAML::Key << s_MetadataYaml_Source_AcceptedAgreementFields << YAML::Value << details.AcceptedAgreementFields;
    793             out << YAML::EndMap;
    794         }
    795 
    796         out << YAML::EndSeq;
    797         out << YAML::EndMap;
    798 
    799         return m_metadataStream.Set(out.str());
    800     }
    801 
    802     void SourceList::SaveMetadataInternal(const SourceDetailsInternal& detailsRef, bool remove)
    803     {
    804         // Copy the incoming details because we might overwrite the metadata
    805         // when reloading the source details from settings.
    806         SourceDetailsInternal details = detailsRef;
    807         bool metadataSet = false;
    808 
    809         for (size_t i = 0; !metadataSet && i < 10; ++i)
    810         {
    811             metadataSet = SetMetadata(m_sourceList);
    812 
    813             if (!metadataSet)
    814             {
    815                 OverwriteMetadata();
    816 
    817                 auto target = FindSource(details.Name, true);
    818                 if (target == m_sourceList.end())
    819                 {
    820                     // Didn't find the metadata, so we consider this a success
    821                     return;
    822                 }
    823 
    824                 if (remove)
    825                 {
    826                     // The remove will have removed the source but not the metadata.
    827                     // Remove it again here.
    828                     m_sourceList.erase(target);
    829                 }
    830                 else
    831                 {
    832                     // Update the freshly read metadata with the update that was requested.
    833                     details.CopyMetadataFieldsTo(*target);
    834                 }
    835             }
    836         }
    837 
    838         THROW_HR_IF_MSG(E_UNEXPECTED, !metadataSet, "Too many attempts at SetMetadata");
    839     }
    840 }