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 }