Avoid merging user cloud policies when merging multiple source policies

On desktop, the user cloud policy potentially comes from a different
domain/administrator than e.g. GPO policy or machine-level cloud policy,
 so prevent merging user cloud policy with other policy sources.
Note: In the future, merging may be allowed if an affiliation check
is possible and says that the user cloud policy and machine-level cloud
policy come from affiliated domains.

Bug: 950068
Change-Id: Id0d77671d682802f84054acf92ac74b7fd63ad64
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1572700
Commit-Queue: Yann Dago <ydago@chromium.org>
Reviewed-by: Pavol Marko <pmarko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#653752}
diff --git a/components/policy/core/common/policy_map_unittest.cc b/components/policy/core/common/policy_map_unittest.cc
index 93a5d4c..e65fc95 100644
--- a/components/policy/core/common/policy_map_unittest.cc
+++ b/components/policy/core/common/policy_map_unittest.cc
@@ -314,13 +314,14 @@
   std::vector<base::Value> cd = GetListStorage({"c", "d"});
   std::vector<base::Value> ef = GetListStorage({"e", "f"});
 
-  // Case 1 - kPolicyName1
+  // Case 1 - kTestPolicyName1
+  // Enterprise default policies should not be merged with other sources.
   PolicyMap::Entry platform_user_mandatory(
       POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_PLATFORM,
       std::make_unique<base::Value>(abc), nullptr);
 
   platform_user_mandatory.AddConflictingPolicy(PolicyMap::Entry(
-      POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+      POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_ACTIVE_DIRECTORY,
       std::make_unique<base::Value>(cd), nullptr));
 
   platform_user_mandatory.AddConflictingPolicy(
@@ -338,7 +339,9 @@
       std::make_unique<base::Value>(abcd), nullptr);
   merged_user_mandatory.AddConflictingPolicy(platform_user_mandatory);
 
-  // Case 2 - kPolicyName2
+  // Case 2 - kTestPolicyName2
+  // Policies should only be merged with other policies with the same target,
+  // level and scope.
   PolicyMap::Entry cloud_machine_recommended(
       POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
       POLICY_SOURCE_PRIORITY_CLOUD, std::make_unique<base::Value>(ab), nullptr);
@@ -356,7 +359,8 @@
       std::make_unique<base::Value>(abcd), nullptr);
   merged_machine_recommended.AddConflictingPolicy(cloud_machine_recommended);
 
-  // Case 3 - ExtensionsInstallBlacklist
+  // Case 3 - kTestPolicyName3
+  // Trivial case with 2 sources.
   PolicyMap::Entry cloud_machine_mandatory(
       POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
       POLICY_SOURCE_PRIORITY_CLOUD, std::make_unique<base::Value>(ab), nullptr);
@@ -370,13 +374,17 @@
       std::make_unique<base::Value>(abcd), nullptr);
   merged_cloud_machine_mandatory.AddConflictingPolicy(cloud_machine_mandatory);
 
-  // Case 4 - ExtensionsInstallWhitelist
+  // Case 4 - kTestPolicyName4
+  // Policies with a single source should stay the same.
   PolicyMap::Entry ad_machine_mandatory(
       POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
       POLICY_SOURCE_ACTIVE_DIRECTORY, std::make_unique<base::Value>(ef),
       nullptr);
 
-  // Case 4 - Non-list policy
+  // Case 5 - kTestPolicyName5
+  // Policies that are not lists should not be merged.
+  // If such a policy is explicitly in the list of policies to merge, an error
+  // is added to the entry and the policy stays intact.
   PolicyMap::Entry bad_stuff(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
                              POLICY_SOURCE_ACTIVE_DIRECTORY,
                              std::make_unique<base::Value>("bad stuff"),
@@ -389,24 +397,35 @@
   expected_bad_stuff.AddError(
       IDS_POLICY_LIST_MERGING_WRONG_POLICY_TYPE_SPECIFIED);
 
+  // Case 6 - kTestPolicyName6
+  // User cloud policies should not be merged with other sources.
+  PolicyMap::Entry user_not_merged(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+                                   POLICY_SOURCE_PLATFORM,
+                                   std::make_unique<base::Value>(ab), nullptr);
+  user_not_merged.AddConflictingPolicy(PolicyMap::Entry(
+      POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+      std::make_unique<base::Value>(cd), nullptr));
+  user_not_merged.AddConflictingPolicy(PolicyMap::Entry(
+      POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_PRIORITY_CLOUD,
+      std::make_unique<base::Value>(ef), nullptr));
+
   PolicyMap policy_not_merged;
   policy_not_merged.Set(kTestPolicyName1, platform_user_mandatory.DeepCopy());
   policy_not_merged.Set(kTestPolicyName2, cloud_machine_recommended.DeepCopy());
-  policy_not_merged.Set(policy::key::kExtensionInstallBlacklist,
-                        cloud_machine_mandatory.DeepCopy());
-  policy_not_merged.Set(policy::key::kExtensionInstallWhitelist,
-                        ad_machine_mandatory.DeepCopy());
-  policy_not_merged.Set(kTestPolicyName3, bad_stuff.DeepCopy());
+  policy_not_merged.Set(kTestPolicyName3, cloud_machine_mandatory.DeepCopy());
+  policy_not_merged.Set(kTestPolicyName4, ad_machine_mandatory.DeepCopy());
+  policy_not_merged.Set(kTestPolicyName5, bad_stuff.DeepCopy());
+  policy_not_merged.Set(kTestPolicyName6, user_not_merged.DeepCopy());
 
   PolicyMap expected_list_merged;
   expected_list_merged.Set(kTestPolicyName1, merged_user_mandatory.DeepCopy());
   expected_list_merged.Set(kTestPolicyName2,
                            merged_machine_recommended.DeepCopy());
-  expected_list_merged.Set(policy::key::kExtensionInstallBlacklist,
+  expected_list_merged.Set(kTestPolicyName3,
                            merged_cloud_machine_mandatory.DeepCopy());
-  expected_list_merged.Set(policy::key::kExtensionInstallWhitelist,
-                           ad_machine_mandatory.DeepCopy());
-  expected_list_merged.Set(kTestPolicyName3, expected_bad_stuff.DeepCopy());
+  expected_list_merged.Set(kTestPolicyName4, ad_machine_mandatory.DeepCopy());
+  expected_list_merged.Set(kTestPolicyName5, expected_bad_stuff.DeepCopy());
+  expected_list_merged.Set(kTestPolicyName6, user_not_merged.DeepCopy());
 
   PolicyMap list_merged;
   list_merged.CopyFrom(policy_not_merged);
@@ -426,16 +445,15 @@
 
   // Merging lists restrictions specified
   PolicyListMerger good_policy_list({kTestPolicyName1, kTestPolicyName2,
-                                     kTestPolicyName3,
-                                     policy::key::kExtensionInstallBlacklist,
-                                     policy::key::kExtensionInstallWhitelist});
+                                     kTestPolicyName3, kTestPolicyName4,
+                                     kTestPolicyName5, kTestPolicyName6});
   PolicyListMerger wildcard_policy_list({"*"});
   list_merged.MergeValues({&good_policy_list});
   EXPECT_TRUE(list_merged.Equals(expected_list_merged));
 
   PolicyMap expected_list_merged_wildcard;
   expected_list_merged_wildcard.CopyFrom(expected_list_merged);
-  expected_list_merged_wildcard.Set(kTestPolicyName3, bad_stuff.DeepCopy());
+  expected_list_merged_wildcard.Set(kTestPolicyName5, bad_stuff.DeepCopy());
   list_merged_wildcard.MergeValues({&wildcard_policy_list});
   EXPECT_TRUE(list_merged_wildcard.Equals(expected_list_merged_wildcard));
 }
diff --git a/components/policy/core/common/policy_merger.cc b/components/policy/core/common/policy_merger.cc
index fa6a9d81..37b95f4 100644
--- a/components/policy/core/common/policy_merger.cc
+++ b/components/policy/core/common/policy_merger.cc
@@ -58,8 +58,16 @@
   }
 
   for (const auto& it : policy.conflicts) {
+    // On desktop, the user cloud policy potentially comes from a different
+    // domain than e.g. GPO policy or machine-level cloud policy, so prevent
+    // merging user cloud policy with other policy sources.
+    const bool is_user_cloud_policy =
+        it.scope == POLICY_SCOPE_USER &&
+        (it.source == POLICY_SOURCE_CLOUD ||
+         it.source == POLICY_SOURCE_PRIORITY_CLOUD);
     if (it.IsBlocked() || it.source == POLICY_SOURCE_ENTERPRISE_DEFAULT ||
-        it.level != policy.level || it.scope != policy.scope) {
+        is_user_cloud_policy || it.level != policy.level ||
+        it.scope != policy.scope) {
       continue;
     }