winget-cli

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

SourcePolicy.cpp (9639B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "SourcePolicy.h"
      5 #include "Microsoft/PreIndexedPackageSourceFactory.h"
      6 #include "Rest/RestSourceFactory.h"
      7 
      8 using namespace AppInstaller::Settings;
      9 
     10 
     11 namespace AppInstaller::Repository
     12 {
     13     namespace
     14     {
     15         // Checks whether a default source is enabled with the current settings.
     16         // onlyExplicit determines whether we consider the not-configured state to be enabled or not.
     17         bool IsDefaultSourceEnabled(WellKnownSource sourceToLog, ExperimentalFeature::Feature feature, bool onlyExplicit, TogglePolicy::Policy policy)
     18         {
     19             if (!ExperimentalFeature::IsEnabled(feature))
     20             {
     21                 // No need to log here
     22                 return false;
     23             }
     24 
     25             if (onlyExplicit)
     26             {
     27                 // No need to log here
     28                 return GroupPolicies().GetState(policy) == PolicyState::Enabled;
     29             }
     30 
     31             if (!GroupPolicies().IsEnabled(policy))
     32             {
     33                 AICLI_LOG(Repo, Info, << "The default source " << GetWellKnownSourceName(sourceToLog) << " is disabled due to Group Policy");
     34                 return false;
     35             }
     36 
     37             return true;
     38         }
     39 
     40         template<ValuePolicy P>
     41         std::optional<SourceFromPolicy> FindSourceInPolicy(std::string_view name, std::string_view type, std::string_view arg)
     42         {
     43             auto sourcesOpt = GroupPolicies().GetValueRef<P>();
     44             if (!sourcesOpt.has_value())
     45             {
     46                 return std::nullopt;
     47             }
     48 
     49             const auto& sources = sourcesOpt->get();
     50             auto source = std::find_if(
     51                 sources.begin(),
     52                 sources.end(),
     53                 [&](const SourceFromPolicy& policySource)
     54                 {
     55                     return Utility::ICUCaseInsensitiveEquals(name, policySource.Name) && Utility::ICUCaseInsensitiveEquals(type, policySource.Type) && arg == policySource.Arg;
     56                 });
     57 
     58             if (source == sources.end())
     59             {
     60                 return std::nullopt;
     61             }
     62 
     63             return *source;
     64         }
     65 
     66         template<ValuePolicy P>
     67         bool IsSourceInPolicy(std::string_view name, std::string_view type, std::string_view arg)
     68         {
     69             return FindSourceInPolicy<P>(name, type, arg).has_value();
     70         }
     71     }
     72 
     73     // Checks whether the Group Policy allows this user source.
     74     // If it does it returns None, otherwise it returns which policy is blocking it.
     75     // Note that this applies to user sources that are being added as well as user sources
     76     // that already existed when the Group Policy came into effect.
     77     TogglePolicy::Policy GetPolicyBlockingUserSource(std::string_view name, std::string_view type, std::string_view arg, bool isTombstone)
     78     {
     79         // Reasons for not allowing:
     80         //  1. The source is a tombstone for default source that is explicitly enabled
     81         //  2. The source is a default source that is disabled
     82         //  3. The source has the same name as a default source that is explicitly enabled (to prevent shadowing)
     83         //  4. Allowed sources are disabled, blocking all user sources
     84         //  5. There is an explicit list of allowed sources and this source is not in it
     85         //
     86         // We don't need to check sources added by policy as those have higher priority.
     87         //
     88         // Use the name and arg to match sources as we don't have the identifier before adding.
     89 
     90         // Case 1:
     91         // The source is a tombstone and we need the policy to be explicitly enabled.
     92         if (isTombstone)
     93         {
     94             if (name == GetWellKnownSourceName(WellKnownSource::WinGet) && IsWellKnownSourceEnabled(WellKnownSource::WinGet, true))
     95             {
     96                 return TogglePolicy::Policy::DefaultSource;
     97             }
     98 
     99             if (name == GetWellKnownSourceName(WellKnownSource::MicrosoftStore) && IsWellKnownSourceEnabled(WellKnownSource::MicrosoftStore, true))
    100             {
    101                 return TogglePolicy::Policy::MSStoreSource;
    102             }
    103 
    104             // Any other tombstone is allowed
    105             return TogglePolicy::Policy::None;
    106         }
    107 
    108         // Case 2:
    109         //  - The source is not a tombstone and we don't need the policy to be explicitly enabled.
    110         //  - Check only against the source argument and type as the user source may have a different name.
    111         //  - Do a case-insensitive check as the domain portion of the URL is case-insensitive,
    112         //    and we don't need case sensitivity for the rest as we control the domain.
    113         if (Utility::CaseInsensitiveEquals(arg, GetWellKnownSourceArg(WellKnownSource::WinGet)) &&
    114             Utility::CaseInsensitiveEquals(type, Microsoft::PreIndexedPackageSourceFactory::Type()))
    115         {
    116             return IsWellKnownSourceEnabled(WellKnownSource::WinGet) ? TogglePolicy::Policy::None : TogglePolicy::Policy::DefaultSource;
    117         }
    118 
    119         if (Utility::CaseInsensitiveEquals(arg, GetWellKnownSourceArg(WellKnownSource::MicrosoftStore)) &&
    120             Utility::CaseInsensitiveEquals(type, Rest::RestSourceFactory::Type()))
    121         {
    122             return IsWellKnownSourceEnabled(WellKnownSource::MicrosoftStore) ? TogglePolicy::Policy::None : TogglePolicy::Policy::MSStoreSource;
    123         }
    124 
    125         // Case 3:
    126         // If the source has the same name as a default source, it is shadowing with a different argument
    127         // (as it didn't match above). We only care if Group Policy requires the default source.
    128         if (name == GetWellKnownSourceName(WellKnownSource::WinGet) && IsWellKnownSourceEnabled(WellKnownSource::WinGet, true))
    129         {
    130             AICLI_LOG(Repo, Warning, << "User source is not allowed as it shadows the default source. Name [" << name << "]. Arg [" << arg << "] Type [" << type << ']');
    131             return TogglePolicy::Policy::DefaultSource;
    132         }
    133 
    134         if (name == GetWellKnownSourceName(WellKnownSource::MicrosoftStore) && IsWellKnownSourceEnabled(WellKnownSource::MicrosoftStore, true))
    135         {
    136             AICLI_LOG(Repo, Warning, << "User source is not allowed as it shadows a default MS Store source. Name [" << name << "]. Arg [" << arg << "] Type [" << type << ']');
    137             return TogglePolicy::Policy::MSStoreSource;
    138         }
    139 
    140         // Case 4:
    141         // The guard in the source add command should already block adding.
    142         // This check drops existing user sources.
    143         auto allowedSourcesPolicy = GroupPolicies().GetState(TogglePolicy::Policy::AllowedSources);
    144         if (allowedSourcesPolicy == PolicyState::Disabled)
    145         {
    146             AICLI_LOG(Repo, Warning, << "User sources are disabled by Group Policy");
    147             return TogglePolicy::Policy::AllowedSources;
    148         }
    149 
    150         // Case 5:
    151         if (allowedSourcesPolicy == PolicyState::Enabled)
    152         {
    153             if (!IsSourceInPolicy<ValuePolicy::AllowedSources>(name, type, arg))
    154             {
    155                 AICLI_LOG(Repo, Warning, << "Source is not in the Group Policy allowed list. Name [" << name << "]. Arg [" << arg << "] Type [" << type << ']');
    156                 return TogglePolicy::Policy::AllowedSources;
    157             }
    158         }
    159 
    160         return TogglePolicy::Policy::None;
    161     }
    162 
    163     bool IsUserSourceAllowedByPolicy(std::string_view name, std::string_view type, std::string_view arg, bool isTombstone)
    164     {
    165         return GetPolicyBlockingUserSource(name, type, arg, isTombstone) == TogglePolicy::Policy::None;
    166     }
    167 
    168     bool IsWellKnownSourceEnabled(WellKnownSource source, bool onlyExplicit)
    169     {
    170         switch (source)
    171         {
    172         case AppInstaller::Repository::WellKnownSource::WinGet:
    173             return IsDefaultSourceEnabled(source, ExperimentalFeature::Feature::None, onlyExplicit, TogglePolicy::Policy::DefaultSource);
    174         case AppInstaller::Repository::WellKnownSource::MicrosoftStore:
    175             return IsDefaultSourceEnabled(source, ExperimentalFeature::Feature::None, onlyExplicit, TogglePolicy::Policy::MSStoreSource);
    176         case AppInstaller::Repository::WellKnownSource::DesktopFrameworks:
    177             // No corresponding policy available for this source.
    178             return true;
    179         }
    180 
    181         return false;
    182     }
    183 
    184     void EnsureSourceIsRemovable(const SourceDetailsInternal& source)
    185     {
    186         // Block removing sources added by Group Policy
    187         if (source.Origin == SourceOrigin::GroupPolicy)
    188         {
    189             AICLI_LOG(Repo, Error, << "Cannot remove source added by Group Policy");
    190             throw GroupPolicyException(TogglePolicy::Policy::AdditionalSources);
    191         }
    192 
    193         // Block removing default sources required by Group Policy.
    194         if (source.Origin == SourceOrigin::Default)
    195         {
    196             if (GroupPolicies().GetState(TogglePolicy::Policy::DefaultSource) == PolicyState::Enabled &&
    197                 source.Identifier == GetWellKnownSourceIdentifier(WellKnownSource::WinGet))
    198             {
    199                 throw GroupPolicyException(TogglePolicy::Policy::DefaultSource);
    200             }
    201 
    202             if (GroupPolicies().GetState(TogglePolicy::Policy::MSStoreSource) == PolicyState::Enabled &&
    203                 source.Identifier == GetWellKnownSourceIdentifier(WellKnownSource::MicrosoftStore))
    204             {
    205                 throw GroupPolicyException(TogglePolicy::Policy::MSStoreSource);
    206             }
    207         }
    208     }
    209 }